真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

改進(jìn)Android語(yǔ)音對(duì)講系統(tǒng)的方法

本文主要包括以下內(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è)前來合作!

  1. 通過生產(chǎn)者-消費(fèi)者模式保證數(shù)據(jù)鏈路的魯棒性

  2. 改進(jìn)音頻錄制及播放,提高語(yǔ)音通信質(zhì)量

  3. 采用多播實(shí)現(xiàn)設(shè)備發(fā)現(xiàn)及跨路由通信

  4. 實(shí)現(xiàn)對(duì)講進(jìn)程與UI進(jìn)程的通信(AIDL)

一、通過生產(chǎn)者-消費(fèi)者模式保證數(shù)據(jù)鏈路的魯棒性

1. 從責(zé)任鏈到生產(chǎn)者-消費(fèi)者

在《實(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ù)鏈路的魯棒性。

2. 基于阻塞隊(duì)列實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式

本節(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進(jìn)行元素的存儲(chǔ);

  • 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 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 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;    Node node = 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(Node node) {
    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 BlockingQueue audioDataQueue = 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)系。

二、改進(jìn)音頻錄制及播放,提高語(yǔ)音通信質(zhì)量

  • 錄制,改變了音頻輸入源,將直接從麥克風(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音頻處理),不吝賜教!

三、采用多播實(shí)現(xiàn)設(shè)備發(fā)現(xiàn)及跨路由通信

《通過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í)現(xiàn)對(duì)講進(jìn)程與UI進(jìn)程的通信(AIDL)

在實(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)。

1. 定義AIDL文件

由于既存在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é)了。

2. 從Activity到Service的通信

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);
    }
};
3. 從Service到Activity的通信

Service通過RemoteCallbackList保持回調(diào)方法,使用時(shí)首先定義RemoteCallbackList對(duì)象,泛型類型為IIntercomCallback。

private RemoteCallbackList mCallbackList = new RemoteCallbackList<>();

RemoteCallbackList并不是List,內(nèi)部通過Map來保存,Key和Value分別為IBinder和Callback。

ArrayMap mCallbacks = 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)


本文標(biāo)題:改進(jìn)Android語(yǔ)音對(duì)講系統(tǒng)的方法
標(biāo)題URL:http://weahome.cn/article/jhopio.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部