Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程
前置文章:
《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处理响铃事件
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注冊流程
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
当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注冊流程
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
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:
@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注冊流程
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
整个注冊流程是从TeleService启动时開始的,TeleService有监听BOOT_COMPLETE的广播,在随机启动之后便開始了整个注冊流程。
在第8步须要注意。双卡运行CallManager.getInstance().registerForIncomingRingEx()。单卡则运行CallManager.getInstance().registerForIncomingRing()。
TeleService处理响铃
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWlob25neXVlbGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
CallStateMonitor
@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
@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
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呢?
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()主要做了三件事:
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线程来做铃声播放/停止操作。
Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程的更多相关文章
- Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象.与Google原生AOSP有些许差异.请读者知悉. ...
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...
- 以太网驱动的流程浅析(二)-Ifconfig的详细代码流程【原创】
以太网驱动流程浅析(二)-ifconfig的详细代码流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<L ...
- 以太网驱动的流程浅析(一)-Ifconfig主要流程【原创】
以太网驱动的流程浅析(一)-Ifconfig主要流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<Lin ...
- Android 4.4(KitKat)中VSync信号的虚拟化
原文地址:http://blog.csdn.net/jinzhuojun/article/details/17293325 Android 4.1(Jelly Bean)引入了Vsync(Vertic ...
- Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(二)__原理分析
前置文章: <Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释> 转载请务必注明出处:http://b ...
- Android 4.4 KitKat 新特性
New in Android 4.4 KitKat 本文是一个概览,关于KitKat,也即Android4.4的新东西,先是功能型的,之后是设计上的. 很多特性本文并没有提到,很多提到的特性也只是简短 ...
- 让你的短信应用迎接Android 4.4(KitKat)
原文地址:Getting Your SMS Apps Ready for KitKat 发送和接收短信是手机最基本的功能,很多的开发者也开发了很多成功的应用来增强Android这一方面的体验.你们当中 ...
- PHP容器--Pimple运行流程浅析
需要具备的知识点 闭包 闭包和匿名函数在PHP5.3.0中引入的. 闭包是指:创建时封装周围状态的函数.即使闭包所处的环境不存在了,闭包中封装的状态依然存在. 理论上,闭包和匿名函数是不同的概念.但是 ...
随机推荐
- web模拟telnet
https://blog.csdn.net/midion9/article/details/51781402 https://blog.csdn.net/hackstoic/article/detai ...
- [scrapy] spider object has no attribute '_rules'
这是因为__init__方法没有继承父类 解决办法: # -*- coding:utf-8 -*- from selenium import webdriver from scrapy.contrib ...
- thinkphp函数学习(0)——开篇
因为新公司都使用thinkphp,所以就想通读一遍源码,可是在读的过程中,时常半路杀出个自定义函数,然后又要跳到函数定义的地方先看具体的函数定义,感觉特别的难受,好几次都是看到runtime.php就 ...
- (1) Flutter android studio安装
Flutter由两部分组成 Flutter引擎和Flutter框架 Flutter引擎由C++编写,在android上通过NDK编译,在ios上通过llvm编译 Flutter框架由dart编写 1. ...
- 6、Django实战第6天:用户登录
今天开始,我们需要来写后台逻辑了.... 后台逻辑代码都是编写在views.py文件里面,今天要完成的登录功能,因此来编辑users.views.py 这里我们根据请求方法来判断分为2种情况,网页默认 ...
- Centos7 下安装mysql
#prepare for install yum install http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm ...
- Exercise02_11
import javax.swing.JOptionPane; public class Population{ public static void main(String[] args){ int ...
- 【R笔记】R语言中的字符串处理函数
内容概览 尽管R是一门以数值向量和矩阵为核心的统计语言,但字符串同样极为重要.从医疗研究数据里的出生日期到文本挖掘的应用,字符串数据在R程序中使用的频率非常高.R语言提供了很多字符串操作函数,本文仅简 ...
- Java高级架构师(一)第14节:新增和列表页面和分页tag
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding= ...
- Linux下Shell脚本替换换行符(转)
说明:过滤换行符确实有点吭,不是那么好弄.处理文本时需要将换行符替换为空格,若使用sed命令会比较麻烦,而使用tr命令非常方便. 输入文本(country.txt) China America Fra ...