本文主要包括以下內(nèi)容:
創(chuàng)新互聯(lián)是專業(yè)的赤坎網(wǎng)站建設(shè)公司,赤坎接單;提供網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行赤坎網(wǎng)站開發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!
通過生產(chǎn)者-消費(fèi)者模式保證數(shù)據(jù)鏈路的魯棒性
改進(jìn)音頻錄制及播放,提高語(yǔ)音通信質(zhì)量
采用多播實(shí)現(xiàn)設(shè)備發(fā)現(xiàn)及跨路由通信
實(shí)現(xiàn)對(duì)講進(jìn)程與UI進(jìn)程的通信(AIDL)
在《實(shí)時(shí)Android語(yǔ)音對(duì)講系統(tǒng)架構(gòu)》對(duì)語(yǔ)音對(duì)講系統(tǒng)的數(shù)據(jù)鏈路的分析中提到,數(shù)據(jù)包要經(jīng)過Record、Encoder、Transmission、Decoder、Play這一鏈條的處理,這種數(shù)據(jù)流轉(zhuǎn)就是對(duì)講機(jī)核心抽象,鑒于這種場(chǎng)景,采用了責(zé)任鏈設(shè)計(jì)模式。
在后續(xù)實(shí)踐中發(fā)現(xiàn)這樣的結(jié)構(gòu)存在一些問題,責(zé)任鏈模式適用于數(shù)據(jù)即時(shí)流轉(zhuǎn),需要整個(gè)鏈路沒有阻塞、等待。而在本應(yīng)用場(chǎng)景中,編解碼及錄制播放均可能存在時(shí)間延遲,責(zé)任鏈模式無法兼顧網(wǎng)絡(luò)、編解碼的延時(shí)。
事實(shí)上,通過緩存隊(duì)列則可以保證數(shù)據(jù)鏈路的穩(wěn)定性,分別在編解碼和數(shù)據(jù)發(fā)送接收時(shí)加入阻塞隊(duì)列,可以實(shí)現(xiàn)數(shù)據(jù)包的緩沖,同時(shí)降低丟包的可能。因此,在本系統(tǒng)場(chǎng)景下,基于阻塞隊(duì)列實(shí)現(xiàn)了生產(chǎn)者-消費(fèi)者模式,是對(duì)責(zé)任鏈模式的優(yōu)化,意在提高數(shù)據(jù)鏈路的魯棒性。
本節(jié)包括以下內(nèi)容:
阻塞隊(duì)列(數(shù)據(jù)結(jié)構(gòu))
阻塞隊(duì)列實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式
阻塞隊(duì)列(數(shù)據(jù)結(jié)構(gòu))
阻塞隊(duì)列(BlockingQueue)是一個(gè)支持兩個(gè)附加操作的隊(duì)列。這兩個(gè)附加的操作是:
在隊(duì)列為空時(shí),獲取元素的線程會(huì)等待隊(duì)列變?yōu)榉强铡?/p>
當(dāng)隊(duì)列滿時(shí),存儲(chǔ)元素的線程會(huì)等待隊(duì)列可用。
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景,生產(chǎn)者是往隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里拿元素的線程。阻塞隊(duì)列就是生產(chǎn)者存放元素的容器,而消費(fèi)者也只從容器里拿元素。
阻塞隊(duì)列提供了四種處理方法:
方法\處理方式 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時(shí)退出 | |
---|---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) | |
移除方法 | remove() | poll() | take() | poll(time,unit) | |
檢查方法 | element() | peek() | 不可用 | 不可用 |
拋出異常:是指當(dāng)阻塞隊(duì)列滿時(shí)候,再往隊(duì)列里插入元素,會(huì)拋出IllegalStateException("Queue full")異常。當(dāng)隊(duì)列為空時(shí),從隊(duì)列里獲取元素時(shí)會(huì)拋出NoSuchElementException異常 。
返回特殊值:插入方法會(huì)返回是否成功,成功則返回true。移除方法,則是從隊(duì)列里拿出一個(gè)元素,如果沒有則返回null
一直阻塞:當(dāng)阻塞隊(duì)列滿時(shí),如果生產(chǎn)者線程往隊(duì)列里put元素,隊(duì)列會(huì)一直阻塞生產(chǎn)者線程,直到拿到數(shù)據(jù),或者響應(yīng)中斷退出。當(dāng)隊(duì)列空時(shí),消費(fèi)者線程試圖從隊(duì)列里take元素,隊(duì)列也會(huì)阻塞消費(fèi)者線程,直到隊(duì)列可用。
超時(shí)退出:當(dāng)阻塞隊(duì)列滿時(shí),隊(duì)列會(huì)阻塞生產(chǎn)者線程一段時(shí)間,如果超過一定的時(shí)間,生產(chǎn)者線程就會(huì)退出。
本文通過LinkedBlockingQueue的put和take方法實(shí)現(xiàn)線程阻塞。LinkedBlockingQueue是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列。此隊(duì)列的默認(rèn)和最大長(zhǎng)度為Integer.MAX_VALUE。此隊(duì)列按照先進(jìn)先出的原則對(duì)元素進(jìn)行排序。
首先看下LinkedBlockingQueue中核心的域:
static class Node{ E item; Node next; Node(E x) { item = x; } }private final int capacity;private final AtomicInteger count = new AtomicInteger();transient Node head;private transient Node last;private final ReentrantLock takeLock = new ReentrantLock();private final Condition notEmpty = takeLock.newCondition();private final ReentrantLock putLock = new ReentrantLock();private final Condition notFull = putLock.newCondition();
LinkedBlockingQueue和LinkedList類似,通過靜態(tài)內(nèi)部類Node
capacity表示阻塞隊(duì)列所能存儲(chǔ)的最大容量,在創(chuàng)建時(shí)可以手動(dòng)指定最大容量,默認(rèn)的最大容量為Integer.MAX_VALUE;
count表示當(dāng)前隊(duì)列中的元素?cái)?shù)量,LinkedBlockingQueue的入隊(duì)列和出隊(duì)列使用了兩個(gè)不同的lock對(duì)象,因此無論是在入隊(duì)列還是出隊(duì)列,都會(huì)涉及對(duì)元素?cái)?shù)量的并發(fā)修改,因此這里使用了一個(gè)原子操作類來解決對(duì)同一個(gè)變量進(jìn)行并發(fā)修改的線程安全問題。
head和last分別表示鏈表的頭部和尾部;
takeLock表示元素出隊(duì)列時(shí)線程所獲取的鎖,當(dāng)執(zhí)行take、poll等操作時(shí)線程獲??;notEmpty當(dāng)隊(duì)列為空時(shí),通過該Condition讓獲取元素的線程處于等待狀態(tài);
putLock表示元素入隊(duì)列時(shí)線程所獲取的鎖,當(dāng)執(zhí)行put、offer等操作時(shí)獲??;notFull當(dāng)隊(duì)列容量達(dá)到capacity時(shí),通過該Condition讓加入元素的線程處于等待狀態(tài)。
其次,LinkedBlockingQueue有三個(gè)構(gòu)造方法,分別如下:
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node(null); }public LinkedBlockingQueue(Collection extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node (e)); ++n; } count.set(n); } finally { putLock.unlock(); } }
默認(rèn)構(gòu)造函數(shù)直接調(diào)用LinkedBlockingQueue(int capacity),LinkedBlockingQueue(int capacity)會(huì)初始化首尾節(jié)點(diǎn),并置位null。LinkedBlockingQueue(Collection extends E> c)在初始化隊(duì)列的同時(shí),將一個(gè)集合的全部元素加入隊(duì)列。
最后,重點(diǎn)分析下put和take的過程:
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); int c = -1; Nodenode = new Node (e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); }
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
之所以把put和take放在一起,是因?yàn)樗鼈兪且粚?duì)互逆的過程:
put在插入元素前首先獲得putLock和當(dāng)前隊(duì)列的元素?cái)?shù)量,take在去除元素錢首先獲得takeLock和當(dāng)前隊(duì)列的元素?cái)?shù)量;
put時(shí)需要判斷當(dāng)前隊(duì)列是否已滿,已滿時(shí)當(dāng)前線程進(jìn)行等待,take時(shí)需要判斷隊(duì)列是否已空,隊(duì)列為空時(shí)當(dāng)前線程進(jìn)行等待;
put調(diào)用enqueue在隊(duì)尾插入元素,并修改尾指針,take調(diào)用dequeue將head指向原來first的位置,并將first的數(shù)據(jù)域置位null,實(shí)現(xiàn)刪除原first指針,并產(chǎn)生新的head,同時(shí),切斷原h(huán)ead節(jié)點(diǎn)的引用,便于垃圾回收。
private void enqueue(Nodenode) { last = last.next = node; }private E dequeue() { Node h = head; Node first = h.next; h.next = h; // help GChead = first; E x = first.item; first.item = null;return x; }
最后,put根據(jù)count決定是否觸發(fā)隊(duì)列未滿和隊(duì)列空;take根據(jù)count決定是否觸發(fā)隊(duì)列未空和隊(duì)列滿。
LinkedBlockingQueue在入隊(duì)列和出隊(duì)列時(shí)使用的是不同的Lock,這也意味著它們之間的操作不會(huì)存在互斥。在多個(gè)CPU的情況下,可以做到在同一時(shí)刻既消費(fèi)、又生產(chǎn),做到并行處理。
阻塞隊(duì)列實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式
通過對(duì)LinkedBlockingQueue主要源碼的分析,實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式就變得簡(jiǎn)單了。
public class MessageQueue { private static MessageQueue messageQueue1, messageQueue2, messageQueue3, messageQueue4; private BlockingQueueaudioDataQueue = null; private MessageQueue() { audioDataQueue = new LinkedBlockingQueue<>(); } @Retention(SOURCE) @IntDef({ENCODER_DATA_QUEUE, SENDER_DATA_QUEUE, DECODER_DATA_QUEUE, TRACKER_DATA_QUEUE}) public @interface DataQueueType { } public static final int ENCODER_DATA_QUEUE = 0; public static final int SENDER_DATA_QUEUE = 1; public static final int DECODER_DATA_QUEUE = 2; public static final int TRACKER_DATA_QUEUE = 3; public static MessageQueue getInstance(@DataQueueType int type) { switch (type) { case ENCODER_DATA_QUEUE: if (messageQueue1 == null) { messageQueue1 = new MessageQueue(); } return messageQueue1; case SENDER_DATA_QUEUE: if (messageQueue2 == null) { messageQueue2 = new MessageQueue(); } return messageQueue2; case DECODER_DATA_QUEUE: if (messageQueue3 == null) { messageQueue3 = new MessageQueue(); } return messageQueue3; case TRACKER_DATA_QUEUE: if (messageQueue4 == null) { messageQueue4 = new MessageQueue(); } return messageQueue4; default: return new MessageQueue(); } } public void put(AudioData audioData) { try { audioDataQueue.put(audioData); } catch (InterruptedException e) { e.printStackTrace(); } } public AudioData take() { try { return audioDataQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }
這里通過@IntDef來實(shí)現(xiàn)限定輸入類型的功能,同時(shí),阻塞隊(duì)列保持單實(shí)例,然后將隊(duì)列分別應(yīng)用到各個(gè)生產(chǎn)者-消費(fèi)者線程中。在本文的語(yǔ)音對(duì)講系統(tǒng)中,以音頻錄制線程和編碼線程為例,錄制線程是音頻數(shù)據(jù)包的生產(chǎn)者,編碼線程是音頻數(shù)據(jù)包的消費(fèi)者。
音頻錄制線程:
@Overridepublic void run() { while (isRecording) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) { audioRecord.startRecording(); } // 實(shí)例化音頻數(shù)據(jù)緩沖 short[] rawData = new short[inAudioBufferSize]; audioRecord.read(rawData, 0, inAudioBufferSize); AudioData audioData = new AudioData(rawData); MessageQueue.getInstance(MessageQueue.ENCODER_DATA_QUEUE).put(audioData); } }
編碼線程:
@Overridepublic void run() { AudioData data; // 在MessageQueue為空時(shí),take方法阻塞 while ((data = MessageQueue.getInstance(MessageQueue.ENCODER_DATA_QUEUE).take()) != null) { data.setEncodedData(AudioDataUtil.raw2spx(data.getRawData())); MessageQueue.getInstance(MessageQueue.SENDER_DATA_QUEUE).put(data); }}
同樣的,編碼線程和發(fā)送線程,接收線程和解碼線程,解碼線程和播放線程同樣存在生產(chǎn)者-消費(fèi)者的關(guān)系。
錄制,改變了音頻輸入源,將直接從麥克風(fēng)(MIC)獲取改為MediaRecorder.AudioSource.VOICE_COMMUNICATION,VOICE_COMMUNICATION能自動(dòng)回聲消除和增益,因此,屏蔽了speex在C層的降噪和增益。
播放,改變了音頻輸出端,將STREAM_MUSIC換成STREAM_VOICE_CALL,因?yàn)?,?duì)講機(jī)應(yīng)用更類似于語(yǔ)音通信。換成STREAM_VOICE_CALL之后,遇到的問題是只能從聽筒聽到聲音,于是設(shè)置免提功能。
AudioManager audioManager =(AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); audioManager.setSpeakerphoneOn(true);
該設(shè)置必須要開放修改音頻的權(quán)限,不然沒有效果。
目前的語(yǔ)音通信質(zhì)量,個(gè)人感覺仍然需要繼續(xù)優(yōu)化,如果您有這方面的經(jīng)驗(yàn)(包括但不限于Java層和Speex音頻處理),不吝賜教!
《通過UDP廣播實(shí)現(xiàn)Android局域網(wǎng)Peer Discovering》中從編程的角度說明了TCP與UDP的區(qū)別,主要分析了TCP是面向連接的、可靠的服務(wù),建立連接需要經(jīng)過三次握手、銷毀連接需要四次揮手;UDP是無連接的傳輸層協(xié)議,提供面向事務(wù)的簡(jiǎn)單不可靠信息傳送服務(wù)。
IP地址分為三類:?jiǎn)尾?、廣播和多播。廣播和多播僅用于UDP,它們用于將報(bào)文同時(shí)傳送給多個(gè)接收者。廣播分為:受限廣播、指向網(wǎng)絡(luò)的廣播、指向子網(wǎng)的廣播、指向所有子網(wǎng)的廣播。
舉個(gè)栗子:當(dāng)前IP為10.13.200.16/22,首先廣播地址為255.255.255.255,子網(wǎng)廣播地址為10.13.203.255。
《通過UDP廣播實(shí)現(xiàn)Android局域網(wǎng)Peer Discovering》采用子網(wǎng)廣播實(shí)現(xiàn)局域網(wǎng)Android設(shè)備的發(fā)現(xiàn),但在實(shí)踐中,一般路由器會(huì)禁止所有廣播跨路由器傳輸。所以,如果子網(wǎng)內(nèi)有多個(gè)路由器,那么就無法實(shí)現(xiàn)設(shè)備發(fā)現(xiàn)了。因此,本文將設(shè)備發(fā)現(xiàn)也改為多播實(shí)現(xiàn)。多播組地址包括為1110的最高4bit和多播組號(hào),范圍為224.0.0.0到239.255.255.255。能夠接收發(fā)往一個(gè)特定多播組地址數(shù)據(jù)的主機(jī)集合稱為主機(jī)組,主機(jī)組可以跨越多個(gè)網(wǎng)絡(luò)。
IANA 把224.0.0.0 到 224.0.0.255 范圍內(nèi)的地址全部都保留給了路由協(xié)議和其他網(wǎng)絡(luò)維護(hù)功能。該范圍內(nèi)的地址屬于局部范疇,不論生存時(shí)間字段(TTL)值是多少,都不會(huì)被路由器轉(zhuǎn)發(fā);D類保留地址的完整的列表可以參見RFC1700。
224.0.1.0 到 238.255.255.255 地址范圍作為用戶組播地址,在全網(wǎng)范圍內(nèi)有效。其中233/8 為 GLOP 地址。GLOP 是一種自治系統(tǒng)之間的組播地址分配機(jī)制,將 AS 號(hào)直接填入組播地址的中間兩個(gè)字節(jié)中,每個(gè)自治系統(tǒng)都可以得到 255 個(gè)組播地址;
239.0.0.0 到 239.255.255.255 地址范圍為本地管理組播地址(administratively scoped addresses),僅在特定的本地范圍內(nèi)有效。
本文對(duì)比了子網(wǎng)廣播和多播,子網(wǎng)廣播地址為:192.168.137.255,多播組地址為:224.5.6.7。
發(fā)送接收采用同一MulticastSocket,MulticastSocket設(shè)置TTL,TTL表示跨網(wǎng)絡(luò)的級(jí)數(shù)。
try { inetAddress = InetAddress.getByName(Constants.MULTI_BROADCAST_IP); multicastSocket = new MulticastSocket(Constants.MULTI_BROADCAST_PORT); multicastSocket.setLoopbackMode(true); multicastSocket.joinGroup(inetAddress); multicastSocket.setTimeToLive(4); } catch (IOException e) { e.printStackTrace(); }
joinGroup涉及到另一個(gè)協(xié)議:網(wǎng)路群組管理協(xié)議(Internet Group Management Protocol或簡(jiǎn)寫IGMP),通過抓包可以觀察到初始化MulticastSocket時(shí)加入組協(xié)議的報(bào)文。
setTimeToLive用于設(shè)置生存時(shí)間字段。默認(rèn)情況下,多播數(shù)據(jù)報(bào)的TTL設(shè)置為1,使得多播數(shù)據(jù)報(bào)僅限于在同一個(gè)子網(wǎng)內(nèi)傳送,更大的TTL值能夠被多播路由器轉(zhuǎn)發(fā)。在實(shí)際傳輸過程中,多播組地址仍然需要轉(zhuǎn)換為以太網(wǎng)地址。實(shí)際轉(zhuǎn)換規(guī)則這里不再贅述。
上述多播地址224.5.6.7轉(zhuǎn)換后為01:00:5e:05:06:07。
代碼層面上,探測(cè)線程將子網(wǎng)廣播改為多播實(shí)現(xiàn)。
if (command != null) { byte[] data = command.getBytes(); DatagramPacket datagramPacket = new DatagramPacket( data, data.length, Multicast.getMulticast().getInetAddress(), Constants.MULTI_BROADCAST_PORT); try { Multicast.getMulticast().getMulticastSocket().send(datagramPacket); } catch (IOException e) { e.printStackTrace(); } }
并且在接收端區(qū)分指令和音頻數(shù)據(jù)。
while (true) { // 設(shè)置接收緩沖段 byte[] receivedData = new byte[512]; DatagramPacket datagramPacket = new DatagramPacket(receivedData, receivedData.length); try { // 接收數(shù)據(jù)報(bào)文 Multicast.getMulticast().getMulticastSocket().receive(datagramPacket); } catch (IOException e) { e.printStackTrace(); } // 判斷數(shù)據(jù)報(bào)文類型,并做相應(yīng)處理 if (datagramPacket.getLength() == Command.DISC_REQUEST.getBytes().length || datagramPacket.getLength() == Command.DISC_LEAVE.getBytes().length || datagramPacket.getLength() == Command.DISC_RESPONSE.getBytes().length) { handleCommandData(datagramPacket); } else { handleAudioData(datagramPacket); } }
在實(shí)際工程應(yīng)用場(chǎng)景中,需要對(duì)講機(jī)進(jìn)程即使切換到后臺(tái),也依然能收到信息。因此,為了提高進(jìn)程的優(yōu)先級(jí),降低被系統(tǒng)回收的概率,采用了在Service中訪問網(wǎng)絡(luò)服務(wù),處理語(yǔ)音信息的發(fā)送和接收的方案。前臺(tái)Activity負(fù)責(zé)顯示組播組內(nèi)用戶(上線和下線,更新頁(yè)面),通過AIDL與Service進(jìn)行跨進(jìn)程通信和回調(diào)。Service的清單說明如下:
:intercom表示定義子進(jìn)程intercom。
使用多進(jìn)程相比于常見的單進(jìn)程,有一些需要注意的點(diǎn):
靜態(tài)成員和單例模式失效。因?yàn)槊總€(gè)進(jìn)程都會(huì)分配一個(gè)獨(dú)立的虛擬機(jī),不同的虛擬機(jī)對(duì)應(yīng)不同的地址空間;
線程同步機(jī)制失效。因此不同進(jìn)程鎖的并不是同一個(gè)對(duì)象;
Application會(huì)多次創(chuàng)建。進(jìn)程與Application對(duì)應(yīng),多進(jìn)程會(huì)啟動(dòng)多個(gè)Application。
因此,通過process定義了多進(jìn)程之后,一定要避免單進(jìn)程模式下對(duì)象共享的思路。另外,在AS中調(diào)試多進(jìn)程應(yīng)用的時(shí)候,斷點(diǎn)一定要針對(duì)不同的進(jìn)程,以本文為例,添加斷點(diǎn)需要選擇主進(jìn)程和intercom進(jìn)程。給兩個(gè)進(jìn)程分別添加調(diào)試斷點(diǎn)后,可以看到有兩個(gè)Debugger:3156和3230(由于存在Jni代碼,所以顯示了Hybrid Debugger)。
由于既存在Activity到Service的通信,也存在Service接收到消息之后更新Activity頁(yè)面的需求,所以這里采用了跨進(jìn)程回調(diào)的方式。首先,AIDL方法如下:
package com.jd.wly.intercom.service;import com.jd.wly.intercom.service.IIntercomCallback;interface IIntercomService { void startRecord(); void stopRecord(); void registerCallback(IIntercomCallback callback); void unRegisterCallback(IIntercomCallback callback); }
package com.jd.wly.intercom.service;interface IIntercomCallback { void findNewUser(String ipAddress); void removeUser(String ipAddress); }
IIntercomService定義了Activity到Service的通信方法,包含啟動(dòng)和停止音頻錄制,以及注冊(cè)和解除回調(diào)接口;IIntercomCallback定義了從Service到Activity的回調(diào)接口,用于在Service發(fā)現(xiàn)用戶上線、下線時(shí)通知前臺(tái)Activity的顯示。
AIDL文件的定義涉及一些規(guī)范:比如變量在同一包內(nèi)也需要import,非基本數(shù)據(jù)類型參數(shù)列表需要指明in、out,自定義參數(shù)類型需要同時(shí)編寫java文件和aidl文件等,本文篇幅有限,就不具體展開AIDL跨進(jìn)程通信的細(xì)節(jié)了。
Activity檢測(cè)用戶的按鍵操作,然后將事件傳遞給Service進(jìn)行對(duì)應(yīng)的邏輯處理。
將Service綁定到Activity首先需要定義ServiceConnection:
/** * onServiceConnected和onServiceDisconnected運(yùn)行在UI線程中 */private IIntercomService intercomService;private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { intercomService = IIntercomService.Stub.asInterface(service); try { intercomService.registerCallback(intercomCallback); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { intercomService = null; } };
在onStart()時(shí)綁定Service,onStop()時(shí)解除回調(diào)和綁定。
@Overrideprotected void onStart() { super.onStart(); Intent intent = new Intent(AudioActivity.this, IntercomService.class); bindService(intent, serviceConnection, BIND_AUTO_CREATE); }
@Overrideprotected void onStop() { super.onStop(); if (intercomService != null && intercomService.asBinder().isBinderAlive()) { try { intercomService.unRegisterCallback(intercomCallback); } catch (RemoteException e) { e.printStackTrace(); } unbindService(serviceConnection); } }
Activity獲取了Service的服務(wù)后,分別在按鍵事件處理中進(jìn)行調(diào)用。
@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_F2 || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { try { intercomService.startRecord(); } catch (RemoteException e) { e.printStackTrace(); } return true; } return super.onKeyDown(keyCode, event); }@Overridepublic boolean onKeyUp(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_F2 || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { try { intercomService.stopRecord(); } catch (RemoteException e) { e.printStackTrace(); } return true; } return super.onKeyUp(keyCode, event); }
startRecord和stopRecord的具體實(shí)現(xiàn)定義在Service中:
public IIntercomService.Stub mBinder = new IIntercomService.Stub() { @Override public void startRecord() throws RemoteException { if (!recorder.isRecording()) { recorder.setRecording(true); tracker.setPlaying(false); threadPool.execute(recorder); } } @Override public void stopRecord() throws RemoteException { if (recorder.isRecording()) { recorder.setRecording(false); tracker.setPlaying(true); } } @Override public void registerCallback(IIntercomCallback callback) throws RemoteException { mCallbackList.register(callback); } @Override public void unRegisterCallback(IIntercomCallback callback) throws RemoteException { mCallbackList.unregister(callback); } };
Service通過RemoteCallbackList保持回調(diào)方法,使用時(shí)首先定義RemoteCallbackList對(duì)象,泛型類型為IIntercomCallback。
private RemoteCallbackListmCallbackList = new RemoteCallbackList<>();
RemoteCallbackList并不是List,內(nèi)部通過Map來保存,Key和Value分別為IBinder和Callback。
ArrayMapmCallbacks = new ArrayMap ();
使用RemoteCallbackList回調(diào)Activity方法時(shí),通過beginBroadcast獲取數(shù)量,
/** * 發(fā)現(xiàn)新的組播成員 * * @param ipAddress IP地址 */private void findNewUser(String ipAddress) { final int size = mCallbackList.beginBroadcast(); for (int i = 0; i < size; i++) { IIntercomCallback callback = mCallbackList.getBroadcastItem(i); if (callback != null) { try { callback.findNewUser(ipAddress); } catch (RemoteException e) { e.printStackTrace(); } } } mCallbackList.finishBroadcast(); }
removeUser(String ipAddress)方法與findNewUser(String ipAddress)方法類似。它們具體的實(shí)現(xiàn)在Activity中:
/** * 被調(diào)用的方法運(yùn)行在Binder線程池中,不能更新UI */private IIntercomCallback intercomCallback = new IIntercomCallback.Stub() { @Override public void findNewUser(String ipAddress) throws RemoteException { sendMsg2MainThread(ipAddress, FOUND_NEW_USER); } @Override public void removeUser(String ipAddress) throws RemoteException { sendMsg2MainThread(ipAddress, REMOVE_USER); } };
需要注意的是,IIntercomCallback中的回調(diào)方法實(shí)現(xiàn)并不在UI線程中執(zhí)行,如果需要更新UI,需要實(shí)現(xiàn)多線程調(diào)用,多線程依然通過Handler來實(shí)現(xiàn)