編輯:關於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網友分析)

Android實現果凍滑動效果的控件
前言在微信是的處理方法是讓用戶滑動,但最終還是回滾到最初的地方,這樣的效果很生動(畢竟成功還是取決於細節)。那麼在安卓我們要怎麼弄呢。下面為大家介紹一下JellyScro
Android新聞廣告條滾動效果
項目中需要用到類似公告欄的控件,能用的基本不支持多行顯示,於是只好自己動手,苦於沒有自定義過一個像樣的控件,借鑒Android公告條demo,實現了多行向上滾動的控件。在
的Android進階之旅------)android:drawableLeft的用法
有時候想在EditText左邊放一個圖片,如圖所示:就可以在xml布局文件中的EditText定義代碼中,添加入下面的代碼,即可實現: android:draw
Android自定義軟鍵盤
前不久由於項目的需要,要做一個自定義的軟鍵盤,我也上網看了很多,都覺得很繁瑣,所以想自己動手實現個。以備不時之需把。我選擇了參考百度錢包的軟鍵盤,看起來還不錯:publi