Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 4.4 Kitkat Phone工作流程淺析(七)__來電(MT)響鈴流程

Android 4.4 Kitkat Phone工作流程淺析(七)__來電(MT)響鈴流程

編輯:關於Android編程


本文來自http://blog.csdn.net/yihongyuelan 轉載請務必注明出處
本文代碼以MTK平台Android 4.4為分析對象,與Google原生AOSP有些許差異,請讀者知悉。

前置文章:

《Android 4.4 Kitkat Phone工作流程淺析(一)__概要和學習計劃》

《Android 4.4 Kitkat Phone工作流程淺析(二)__UI結構分析》

《Android 4.4 Kitkat Phone工作流程淺析(三)__MO(去電)流程分析》

《Android 4.4 Kitkat Phone工作流程淺析(四)__RILJ工作流程簡析》

《Android 4.4 Kitkat Phone工作流程淺析(五)__MT(來電)流程分析》

《Android 4.4 Kitkat Phone工作流程淺析(六)__InCallActivity顯示更新流程》

概述

本系列文章以MT/MO為主線流程,並對其中的細枝末節進行補充說明,比如來電響鈴流程。在MT流程的分析中已經涵蓋了流程的發起與終止,本文所描述的響鈴流程始於MT流程的發起,如對MT流程不熟悉的童鞋請查看文章《Android 4.4 Kitkat Phone工作流程淺析(五)__MT(來電)流程分析》以及《Android 4.4 Kitkat Phone工作流程淺析(六)__InCallActivity顯示更新流程》。

Android 4.4對於響鈴流程有所改動,把響鈴觸發放到了TeleService中,這也符合4.4 Phone的設計風格即UI和Logic分離。當來電流程發起時,抓取radio_log進行分析後,可以看到整個來電過程狀態改變如下:

handleMessage (EVENT_VOICE_CALL_INCOMING_INDICATION) //設置voice call的標志
handleMessage (EVENT_NEW_RINGING_CONNECTION)         //新來電標志
handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)     //狀態改變
handleMessage (EVENT_INCOMING_RING)                  //響鈴
handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION) //收到AT指令CLIP後,更新通話信息(CLIP即來電顯示作用)
handleMessage (EVENT_INCOMING_RING)
handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)
handleMessage (EVENT_INCOMING_RING)
handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)
handleMessage (EVENT_INCOMING_RING)
handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)
handleMessage (EVENT_INCOMING_RING)
handleMessage (EVENT_CRSS_SUPP_SERVICE_NOTIFICATION)
handleMessage (EVENT_DISCONNECT)                     //斷開連接
handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)     //狀態改變
handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)
這些log是CallManager中打印出來的,當然我們也可以在RIL.java中去查看對應的log。

當Modem側收到來電消息後所做的操作如下

1. 根據來電類型設置INCOMING_INDICATION;

2. 發起NEW_RINGING_CONNECTION,新來電標志

3. 觸發CALL_STATE_CHANGED狀態,狀態改變促使界面更新;

4. 發起響鈴通知INCOMING_RING,響鈴流程由此發起;

5. 根據CLIP返回信息觸發CRSS_SUPP_SERVICE_NOTIFICATION,這是由MTK加入的,其作用是根據CLIP的返回更新Call的信息;

6. 循環上面4和5步,持續響鈴;

7. 斷開連接DISCONNECT,本次MT流程結束(未接聽);

8. 觸發CALL_STATE_CHANGED狀態,狀態改變促使界面更新;

Telephony Framework處理響鈴事件

當來電消息到來時,經過Modem以及RILD的處理後便傳遞到Telephony framework中,並由RILJ繼續發起。流程圖如下: \

RILJ

在MT流程發起後,會有相關的unsolicited信息反饋到RILJ中,這裡主要關注響鈴流程(ringing_flow),因此可以找到以下AT指令返回的log信息:

01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +CRING: VOICE
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: 
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +CLIP: "13800138000",0,"",0,"",0
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: 
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: +ECPI: 1,4,0,1,1,0,"13800138000",129,""
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: AT< +CRING: VOICE
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: RIL_URC_READER:+CRING: VOICE
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-AT: RIL_URC_READER Enter processLine
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: Nw URC:+CRING: VOICE
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: receiving RING!!!!!!
01-01 02:46:01.154  4753  4768 D use-Rlog/RLOG-RIL: receiving first RING!!!!!!

整個radio_log中每隔3877ms便會打印一次+CRING: VOICE的log,通話掛斷前一共有5次。根據AT返回信息可以知道,來電類型為Voice,並且是一次響鈴事件。緊接著在RILJ中收到了與之對應的事件RIL_UNSOL_CALL_RING:

01-01 02:46:02.155 1443 1837 D RILJ : RIL(2) :[UNSL RIL]< UNSOL_CALL_RING [C@422899d0

這裡是UnSolicited事件,觸發processUnsolicited方法,並執行到以下代碼處:

case RIL_UNSOL_CALL_RING:
    if (RILJ_LOGD) unsljLogRet(response, ret);
    if (mRingRegistrant != null) {
	//觀察者模式
        mRingRegistrant.notifyRegistrant(
                new AsyncResult (null, ret, null));
    }
前面的文章中我們已經分析過Registrant這種觸發模式,這裡再次分析下notifyRegistrant()方法觸發後的跳轉地點。

首先查看到mRingRegistrant的定義在SourceCode/frameworks/opt/telephony/src/java/com/android/internal/telephony/BaseCommands.java中,且賦值在setOnCallRing方法中,如下:

    @Override
    public void setOnCallRing(Handler h, int what, Object obj) {
        mRingRegistrant = new Registrant (h, what, obj);
    }
這裡的setOnCallRing方法在SourceCode/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneBase.java的構造方法中調用,如下:

mCi.setOnCallRing(this, EVENT_CALL_RING, null);
mCi是CommandsInterface的對象,而BaseCommands implements CommandsInterface,也就是說在PhoneBase的構造方法被調用時,就應經完成了對mRingRegistrant的賦值。
需要注意兩點:

(1). PhoneBase extends Handler,因此setOnCallRing中的this即代表了PhoneBase本身。換句話說,當觸發notifyRegistrant時,便會回調到PhoneBase的handleMessage中;

(2). setOnCallRing中的EVENT_CALL_RING表示當觸發notifyRegistrant時,回調handleMessage中的相應case;

mRingRegistrant注冊流程

\

當Phone第一次啟動時便會執行PhoneFactory中的makeDefaultPhone()方法,用於完成Phone的初始化,在初始化過程中即完成了mRingRegistrant的注冊。

PhoneBase

當執行mRingRegistrant.notifyRegistrant()方法後,跳轉到PhoneBase的handleMessage方法中,代碼如下:

case EVENT_CALL_RING:
    ar = (AsyncResult)msg.obj;
    if (ar.exception == null) {
        PhoneConstants.State state = getState();
        if ((!mDoesRilSendMultipleCallRing)
                && ((state == PhoneConstants.State.RINGING) ||
                        (state == PhoneConstants.State.IDLE))) {
            mCallRingContinueToken += 1;
            sendIncomingCallRingNotification(mCallRingContinueToken);
        } else {
            //執行這裡
            notifyIncomingRing();
        }
    }
    break;
這裡繼續查看notifyIncomingRing()方法:
private void notifyIncomingRing() {
    if (!mIsVoiceCapable)
        return;
    AsyncResult ar = new AsyncResult(null, this, null);
    mIncomingRingRegistrants.notifyRegistrants(ar);
}
同樣使用了觀察者模式,查找相應的registerXXX方法,如下:
    @Override
    public void registerForIncomingRing(
            Handler h, int what, Object obj) {
        checkCorrectThread(h);
        mIncomingRingRegistrants.addUnique(h, what, obj);
    }
可以找到在CallManager的registerForPhoneStates方法中,調用了IncomingRing的register方法:
if (FeatureOption.MTK_GEMINI_SUPPORT == true && !(phone instanceof SipPhone)) {
    if(phone instanceof GeminiPhone) {
        int offset;        	
        int count = (MAXIMUM_SIM_COUNT < PhoneConstants.GEMINI_SIM_NUM) ? MAXIMUM_SIM_COUNT : PhoneConstants.GEMINI_SIM_NUM;
        Phone targetPhone;
        for (int i = 0; i < count; i++) {
            offset = i * NOTIFICATION_ID_OFFSET;
            targetPhone = ((GeminiPhone)phone).getPhonebyId(PhoneConstants.GEMINI_SIM_1 + i);    
            //... ...省略
            targetPhone.registerForIncomingRing(mHandler, EVENT_INCOMING_RING + offset, null);
這裡涉及到MTK的雙卡機制,同時可能大家會有疑惑如何斷定是這裡調用的呢?我們可以反向思考,當發現這裡調用之後,我們反過來看是哪裡調用了registerForPhoneStates,然後依次查看。最終我們可以看到這些都是在Phone初始化時候順序調用的,我們只是反過來查找而已。

通過上面CallManager中的代碼可以知道:

(1). mHandler在CallManager中定義,相關handleMessage即可找到;

(2). case對應事件為:EVENT_INCOMING_RING;

mIncomingRingRegistrants注冊流程

\

mIncomingRingRegistrants的注冊流程始於Phone啟動並初始化時,需要關注的一點是CallManager中的registerForPhoneStates()方法。為什麼這裡直接從CallManager跳轉到PhoneBase呢?實際上targetPhone對象是通過PhoneProxy傳遞過來的,而PhoneProxy是GSMPhone和CDMAPhone的代理,GSMPhone和CDMAPhone都繼承自PhoneBase,最終的實現也在PhoneBase中,這裡省略了部分跳轉,請讀者知悉。

CallManager

根據前面的分析,在PhoneBase的notifyIncomingRing()方法中會調用mIncomingRingRegistrants.notifyRegistrants()方法,可以找到在CallManager中對應的handleMessage方法以及對應的處理事件EVENT_INCOMING_RING:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;">@Override public void handleMessage(Message msg) { int index; switch (msg.what) { //... ...省略 case EVENT_INCOMING_RING: case EVENT_INCOMING_RING + NOTIFICATION_ID_OFFSET: case EVENT_INCOMING_RING + (NOTIFICATION_ID_OFFSET * 2): case EVENT_INCOMING_RING + (NOTIFICATION_ID_OFFSET * 3): if (!hasActiveFgCall()) { index = (msg.what - EVENT_INCOMING_RING) / NOTIFICATION_ID_OFFSET; mIncomingRingRegistrantsGemini[index].notifyRegistrants((AsyncResult) msg.obj); mIncomingRingRegistrants.notifyRegistrants((AsyncResult) msg.obj); } break;看到這裡繼續查看mIncomingRingRegistrantsGemini和mIncomingRingRegistrants的registerXXX方法,代碼如下:

//mIncomingRingRegistrantsGemini的registe方法
public void registerForIncomingRingEx(Handler h, int what, Object obj, int simId){
	  int index = getRegistrantsArrayIndex(simId);
	  if (index != -1) {
        mIncomingRingRegistrantsGemini[index].addUnique(h, what, obj);
    }
}
//mIncomingRingRegistrants的registe方法
public void registerForIncomingRing(Handler h, int what, Object obj){
    mIncomingRingRegistrants.addUnique(h, what, obj);
}
以上方法會根據手機制式來調用,如果是雙卡則調用Gemini,在CallManagerWrapper中可以找到:
public static void registerForIncomingRing(Handler handler, int what, Object obj) {
    if (GeminiUtils.isGeminiSupport()) {
        final int[] geminiSlots = GeminiUtils.getSlots();
        for (int geminiSlot : geminiSlots) {
	    //雙卡
            CallManager.getInstance().registerForIncomingRingEx(handler, what, obj,
                    geminiSlot);
        }
    } else {
	//單卡
        CallManager.getInstance().registerForIncomingRing(handler, what, obj);
    }
}
而以上方法在CallManagerWrapper中還經過了一層包裝:
public static void registerForIncomingRing(Handler handler, int what) {
    registerForIncomingRing(handler, what, null);
}
那麼後續調用是在哪裡呢?我們可以在CallStateMonitor的registerForNotifications()方法中找到:
CallManagerWrapper.registerForIncomingRing(this, PHONE_INCOMING_RING);
而registerForNotifications()方法在CallStateMonitor初始化以及Radio狀態改變的時候會調用。

通過以上分析我們可以知道:

(1). 在CallStateMonitor中注冊了來電響鈴回調,也就是這裡的this。CallStateMonitor繼承自Handler,那麼CallManager中的notifyRegistrants()方法會跳轉到CallStateMonitor中;

(2). 對應的case事件為:PHONE_INCOMING_RING;

mIncomingRingRegistrantsGemini注冊流程

\

整個注冊流程是從TeleService啟動時開始的,TeleService有監聽BOOT_COMPLETE的廣播,在隨機啟動之後便開始了整個注冊流程。在第8步需要注意,雙卡執行CallManager.getInstance().registerForIncomingRingEx(),單卡則執行CallManager.getInstance().registerForIncomingRing()。

TeleService處理響鈴

當Telephony Framework處理完響鈴事件之後,會將響鈴事件上報到CallStateMonitor中,並最終在TeleService中發起響鈴操作,流程圖如下: \

CallStateMonitor

經過framework的層層處理之後,響鈴事件傳遞到了TeleService的CallStateMonitor中。通過前面的分析可以知道,在CallManager的handleMessage中,通過mIncomingRingRegistrantsGemini[index].notifyRegistrants()方法跳轉到了CallStateMonitor的handleMessage中,如下:
@Override
public void handleMessage(Message msg) {
    for (Handler handler : registeredHandlers) {
        handler.handleMessage(msg);
    }
}
這裡會根據registerHandler觸發對應的回調,在前面來電(MT)流程的分析過程有看到,CallNotifier和CallModeler注冊了CallStateMonitor的Handler回調,但通過進一步分析後發現,只有CallNotifier中才處理了PHONE_INCOMING_RING的事件,所以接著我們需要查看CallNotifier對響鈴事件的處理。

CallNotifier

在CallNotifier的handleMessage方法中找到PHONE_INCOING_RING的處理事件如下:
@Override
public void handleMessage(Message msg) {
    //... ...省略
    case CallStateMonitor.PHONE_INCOMING_RING:
        log("PHONE_INCOMING_RING !");
        if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
                //... ...省略
                if (provisioned && !isRespondViaSmsDialogShowing()) {
                    mRinger.ring();
                }
            } else {
                if (DBG) log("RING before NEW_RING, skipping");
            }
        }
    break;
通過這裡會調用Ringer的ring()方法,從而開始響鈴。

Ringer

到這裡就應該准備播放鈴聲了,但ring()方法中還有一系列事情需要處理:
void ring() {
    synchronized (this) {
        //... ...省略
        //創建Looper線程,用於播放/停止鈴聲,通過handleMessage接收播放/停止請求
        makeLooper();
        //如果是第一次播放則mFirstRingEventTime = -1
        if (mFirstRingEventTime < 0) {
	    //這裡獲取系統開機後經過的時間,包括休眠
            mFirstRingEventTime = SystemClock.elapsedRealtime();
            if (mRingHandler != null) {
	        //發起播放鈴聲的請求
                mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);
            }
        } else {
            //如果不是第一次播放
            if (mFirstRingStartTime > 0) {
                if (mRingHandler != null) {
     //延遲發送播放請求,延遲時間為第一次啟動播放時間減去第一次發送PLAY_RING_ONCE的時間(133ms)
                    mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,
                        mFirstRingStartTime - mFirstRingEventTime);
                }
            } else {
                mFirstRingEventTime = SystemClock.elapsedRealtime();
            }
        }
    }
}
在這裡就要特別注意了,通過makeLooper()方法創建了一個Looper線程,用於播放/停止鈴聲,那這裡為什麼要用Looper呢? 後面通過sendEmptyMessage()和sendEmptyMessageDelayed()方法發起播放鈴聲的請求,接下來分析一下makeLooper()的構造以及使用Looper的緣由。
makeLooper()方法主要創建一個ringer的線程,用於播放/停止鈴聲,代碼如下:
private void makeLooper() {
    //如果第一響鈴mRingThread==null
    if (mRingThread == null) {
        //Worker實現了Runnable接口,在其構造方法Worker(String name)
        //中創建並啟動了名為"ringer"的工作線程
        mRingThread = new Worker("ringer");
        //若還未獲取到ringer線程的Looper對象則返回
        if (mRingThread.getLooper() == null) {
            return ;
        }
        //創建Handler並依附於ringer線程的Looper對象
        mRingHandler = new Handler(mRingThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Ringtone r = null;
                switch (msg.what) {
                    case PLAY_RING_ONCE:
                        if (DBG) log("mRingHandler: PLAY_RING_ONCE...");
                        if (mRingtone == null && !hasMessages(STOP_RING)) {
                            // create the ringtone with the uri
                            if (DBG) log("creating ringtone: " + mCustomRingtoneUri);
                            r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);
                            synchronized (Ringer.this) {
                                if (!hasMessages(STOP_RING)) {
                                    mRingtone = r;
                                }
                            }
                        }
                        r = mRingtone;
                        if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) {
                            PhoneLog.d(LOG_TAG, "play ringtone... ");
                            PhoneUtils.setAudioMode();
                            //播放鈴聲
                            r.play();
                            synchronized (Ringer.this) {
               //將第一次播放時間(開機後的時間包括休眠)賦值給mFirstRingStartTime
                                if (mFirstRingStartTime < 0) {
                                    mFirstRingStartTime = SystemClock.elapsedRealtime();
                                }
                            }
                        }
                        break;
                    case STOP_RING:
                        if (DBG) log("mRingHandler: STOP_RING...");
                        r = (Ringtone) msg.obj;
                        if (r != null) {
                            //停止播放鈴聲
                            r.stop();
                        } else {
                            if (DBG) log("- STOP_RING with null ringtone!  msg = " + msg);
                        }
                        //退出Looper循環
                        getLooper().quit();
                        break;

makeLooper()主要做了三件事: (1). 創建並啟動名為ringer的Looper線程; (2). 實例化mHandler並依附於ringer的Looper對象; (3). 接收並處理播放/停止鈴聲的Message; 可以看到makeLooper做了相當重要的事情,特別是對於第三點,後續處理播/停止鈴聲便在handleMessage中。
接下來需要看看Looper,我們看到Worker類的實現,如下:
private class Worker implements Runnable {
    //創建mLock鎖
    private final Object mLock = new Object();
    private Looper mLooper;

    Worker(String name) {
	//創建並啟動名為"name"的線程
        Thread t = new Thread(null, this, name);
        t.start();
        synchronized (mLock) {
            while (mLooper == null) {
                try {
	            //阻塞直到前面的"name"線程已成功運行(執行了run方法),最大阻塞時間5s
                    mLock.wait(5000);
                } catch (InterruptedException ex) {
                }   
            }   
        }   
    }
    public Looper getLooper() {
	//返回"name"線程的Looper對象
        return mLooper;
    }
    public void run() {
        synchronized (mLock) {
	    //啟用Looper
            Looper.prepare();
	    //返回當前線程(也就是這裡的子線程"name")的Looper對象
            mLooper = Looper.myLooper();
	    //喚醒鎖,不再阻塞
            mLock.notifyAll();
        }
	//開啟Looper循環
        Looper.loop();
    }
    public void quit() {
	//退出Looper循環
        mLooper.quit();
    }   
}
Looper用於在線程中開啟消息循環,普通線程默認是沒有消息循環的,在線程中通過調用Looper.prepare()開啟消息循環,通過Looper.loop()處理消息循環直到線程循環終止,我們可以調用Looper的quit()方法退出循環。 (關於Looper官方解釋,StackOverFlow上的回答,或者查看CSDN網友分析)
回到前面的問題,這裡為什麼要使用Looper呢?使用線程來播放鈴聲,同時又需要根據不同的狀態,停止鈴聲的播放,也就是說當持續需要線程來做某件事情,同時又想控制該事情的啟動與停止,這便可以使用Looper
當第一次收到響鈴請求時,開啟線程啟動Loop,並播放鈴聲。通過對Radio_log的分析,RILJ每隔3.877s收到一次響鈴請求,此時上報並傳到到TeleService中,如果此時鈴聲已經播放完畢,那麼則繼續執行ringtone.play()方法啟動鈴聲的播放,如果之前的鈴聲並未播放完畢,則不會再次啟動ringtone.play()方法。如果此時對方/己方掛斷了來電,那麼響鈴需要立即終止,此時只需在子線程中處理STOP_RING方法即可。

總結

整個響鈴流程跟來電(MT)流程類似,但響鈴並未涉及到界面的變換,在TeleService中調用到media中的ringtone.play()方法進行鈴聲的播放。特別需要注意的是整個過程中,大量的使用了觀察者模式,最後使用Looper線程來做鈴聲播放/停止操作。
文中涉及的時序圖資源免積分下載,戳這裡。
整個流程圖如下:
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved