編輯:關於Android編程
《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狀態,狀態改變促使界面更新;
在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;
當Phone第一次啟動時便會執行PhoneFactory中的makeDefaultPhone()方法,用於完成Phone的初始化,在初始化過程中即完成了mRingRegistrant的注冊。
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的注冊流程始於Phone啟動並初始化時,需要關注的一點是CallManager中的registerForPhoneStates()方法。為什麼這裡直接從CallManager跳轉到PhoneBase呢?實際上targetPhone對象是通過PhoneProxy傳遞過來的,而PhoneProxy是GSMPhone和CDMAPhone的代理,GSMPhone和CDMAPhone都繼承自PhoneBase,最終的實現也在PhoneBase中,這裡省略了部分跳轉,請讀者知悉。
根據前面的分析,在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;
整個注冊流程是從TeleService啟動時開始的,TeleService有監聽BOOT_COMPLETE的廣播,在隨機啟動之後便開始了整個注冊流程。在第8步需要注意,雙卡執行CallManager.getInstance().registerForIncomingRingEx(),單卡則執行CallManager.getInstance().registerForIncomingRing()。
@Override public void handleMessage(Message msg) { for (Handler handler : registeredHandlers) { handler.handleMessage(msg); } }這裡會根據registerHandler觸發對應的回調,在前面來電(MT)流程的分析過程有看到,CallNotifier和CallModeler注冊了CallStateMonitor的Handler回調,但通過進一步分析後發現,只有CallNotifier中才處理了PHONE_INCOMING_RING的事件,所以接著我們需要查看CallNotifier對響鈴事件的處理。
@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()方法,從而開始響鈴。
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的緣由。
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;
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網友分析)
前言在微信是的處理方法是讓用戶滑動,但最終還是回滾到最初的地方,這樣的效果很生動(畢竟成功還是取決於細節)。那麼在安卓我們要怎麼弄呢。下面為大家介紹一下JellyScro
項目中需要用到類似公告欄的控件,能用的基本不支持多行顯示,於是只好自己動手,苦於沒有自定義過一個像樣的控件,借鑒Android公告條demo,實現了多行向上滾動的控件。在
有時候想在EditText左邊放一個圖片,如圖所示:就可以在xml布局文件中的EditText定義代碼中,添加入下面的代碼,即可實現: android:draw
前不久由於項目的需要,要做一個自定義的軟鍵盤,我也上網看了很多,都覺得很繁瑣,所以想自己動手實現個。以備不時之需把。我選擇了參考百度錢包的軟鍵盤,看起來還不錯:publi