Android Telephony分析(三) ---- RILJ详解
前言
本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程。
这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\android\internal\telephony) ,
RILC指的是Ril.cpp (hardware\ril\libril)
1. RILJ的创建
RILJ的继承关系如下:
可以看到RILJ继承自BaseCommands并且实现了CommandsInterface接口,RILJ中有两个子线程RILSender和RILReceiver。
再看看RILJ的构造函数:
public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
super(context);
//发送子线程,mInstanceId就是PhoneID
mSenderThread = new HandlerThread("RILSender" + mInstanceId);
mSenderThread.start(); //接收子线程
mReceiver = new RILReceiver();
mReceiverThread = new Thread(mReceiver, "RILReceiver" + mInstanceId);
mReceiverThread.start();
}
在RILJ初始化的时候,启动了RILSender线程用于发送数据,启动了RILReceiver线程用于接收数据。
在《Android Telephony分析(一) — Phone详解 》的第二小节中曾经说到,在创建Phone实例之前会先创建RILJ,一个Phone实例对应一个RILJ实例。
在CallTracker.java、Phone.java、ServiceStateTracker.java我们常常看到的
public CommandsInterface mCi;
mCi对象都是RILJ实例。
2. RILJ的工作原理
RILJ、RILC、Modem的工作流程:
RILJ里有RILSender线程用于向RILC发送数据和RILReceiver用于接收来自RILC的数据,但是这些数据的发送和接收是一个异步的过程。
结合同步,才能更好地理解异步:
同步:发送方发出数据后,等接收方发回响应以后才发下一个数据包。
异步:发送方发出数据后,不等接收方发回响应,接着发送下个数据包。
理解这个概念之后,我们再去分析代码,我们就以打电话为例吧。
2.1 RILSender发送Request
前面的拨号流程省略,我们直接从GsmCdmaCallTracker.java的dial()方法开始分析:
public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
Bundle intentExtras)throws CallStateException {
...
//先通过obtainCompleteMessage方法得到一个Message
mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
...
} private Message
obtainCompleteMessage() {
//该消息类型是EVENT_OPERATION_COMPLETE
return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
}
在调用RILJ的方法发起拨号请求之前,先创建一个Message对象,这个Message对象主要用于,当RILJ发起拨号请求,modem返回消息之后,RILJ再通过Message.sendToTarget,这样回调就可以通知GsmCdmaCallTracker,后文2.2.1小节会详细讲。
接着在RILJ中:
//有一个RILRequest列表
SparseArray<RILRequest> mRequestList = new SparseArray<RILRequest>(); @Override
public void
dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
//得到一个RILRequest对象,需要留意result这个Message被存储在哪里
RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
//将参数放到RILRequest对象中
rr.mParcel.writeString(address);
rr.mParcel.writeInt(clirMode); if (uusInfo == null) {
rr.mParcel.writeInt(0); // UUS information is absent
} else {
rr.mParcel.writeInt(1); // UUS information is present
rr.mParcel.writeInt(uusInfo.getType());
rr.mParcel.writeInt(uusInfo.getDcs());
rr.mParcel.writeByteArray(uusInfo.getUserData());
}
//输出标志性log,"> "代表RILJ向RILC发送请求。
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
//Android N新增,作用是打印log?
mEventLog.writeRilDial(rr.mSerial, clirMode, uusInfo);
//发送请求
send(rr);
} static RILRequest obtain(int request, Message result) {
RILRequest rr = null;
......
//【重点】外面传递进来的Message对象最终赋值给了rr.mResult
rr.mResult = result;
......
return rr;
} send(RILRequest rr) {
Message msg;
if (mSocket == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
}
msg = mSender.obtainMessage(EVENT_SEND, rr);
acquireWakeLock();
msg.sendToTarget();
} @Override public void
handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SEND:
......
synchronized (mRequestList) {
//把RILRequest对象也会被添加到mRequestList列表中
//等到RILC回应RILJ时,再把RILRequest对象取出来
mRequestList.append(rr.mSerial, rr);
}
byte[] data;
//将数据转换成byte
data = rr.mParcel.marshall();
......
//向socket写入数据
s.getOutputStream().write(dataLength);
s.getOutputStream().write(data);
}
}
就这样,整个主动向RILC发出请求的流程就将完了。
2.2 RILReceiver接收Response
在RILReceiver线程中
class RILReceiver implements Runnable { @Override
public void
run() {
......
processResponse(p);
......
}
} private void
processResponse (Parcel p) {
int type; type = p.readInt();
//对上报的消息分类处理
if (type == RESPONSE_UNSOLICITED || type == RESPONSE_UNSOLICITED_ACK_EXP) {
//对modem主动上报消息的处理
processUnsolicited (p, type);
} else if (type == RESPONSE_SOLICITED || type == RESPONSE_SOLICITED_ACK_EXP) {
//对之前RILJ发出的Request的回应消息的处理
RILRequest rr = processSolicited (p, type);
if (rr != null) {
if (type == RESPONSE_SOLICITED) {
decrementWakeLock(rr);
}
rr.release();
}
}
}
RILC上报给RILJ的消息可以分成两类:
1. Solicited Response—>对之前RILJ发出的Request进行回应的消息。(一个Request对应一个Response)
2. UnSolicited Response—>modem主动上报的消息。(单方向,由RILC发给RILJ)
2.2.1 处理Solicited Response
继续上面拨号的例子,在RILJ发起拨号请求后,modem处理完之后,返回消息给RILC,最后通知到RILJ。
private RILRequest
processSolicited (Parcel p, int type) {
RILRequest rr; //把RILRequest对象从mRequestList列表中取出来
rr = findAndRemoveRequestFromList(serial);
//省略对数据的处理
.....
//输出标志性log,"< "代表RILC向RILJ反馈信息。
if (RILJ_LOGD) riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+ " " + retToString(rr.mRequest, ret)); if (rr.mResult != null) {
AsyncResult.forMessage(rr.mResult, ret, null);
//是否还记得上面2.1小节中说到rr.mResult存储的是什么对象吗?
//这就是在调用RILJ的dial方法前创建的Message对象!
//Message.sendToTarget,这样通过回调,流程就回到调用RILJ的dial方法的地方了。
rr.mResult.sendToTarget();
}
return rr;
}
2.2.2 处理Solicited Response
这里以拨打电话后,modem上报call的状态变化消息RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED为例
private void
processUnsolicited (Parcel p, int type) {
int response;
Object ret;
//读取当前上报消息的号码
response = p.readInt();
//根据号码找到相应的逻辑处理
switch(response) {
case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: ret = responseVoid(p); break;
.......
}
//根据号码找到相应的逻辑处理
switch(response) {
case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
if (RILJ_LOGD) unsljLog(response);
//【重点】通过RegistrantList机制,继续上报消息
mCallStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
break;
}
}
}
关于RegistrantList机制,请看上一篇文章《Android Telephony分析(二) —- RegistrantList详解》
接着会通知到注册监听Call状态变化的人:
public GsmCdmaCallTracker (GsmCdmaPhone phone) {
this.mPhone = phone;
mCi = phone.mCi;
//注册监听Call状态变化,GsmCdmaCallTracker本质上是Handler
//所以第一个参数传递this
mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
} public void registerForCallStateChanged(Handler h, int what, Object obj) {
Registrant r = new Registrant (h, what, obj);
//加入mCallStateRegistrants这个RegistrantList中
mCallStateRegistrants.add(r);
}
最终会在GsmCdmaCallTracker的handleMessage方法中对EVENT_CALL_STATE_CHANGE进行处理。
modem主动上报消息的流程也讲解完了。
3 .学以致用
学习完本篇博客的知识,怎么去分析调用RILJ的方法主动发起Request的流程和modem主动上报消息的流程呢?
这里还是以第二小节拨号的代码为例,其他业务流程都可以举一反三。
1.主动发起Request这类代码流程,核心是谁创建Message,之后还是谁对该Message进行处理。
2.modem主动上报消息这类代码流程,核心是谁注册监听了这个消息,那么还是谁对该消息进行处理。
最后可以通过log中的“>”和“<”判断消息的方向。
D/RILJ ( 2795): [5655]> DIAL
D/RILJ ( 2795): [5655]< DIAL
1
2
“>”:是RILJ发请求给modem。
“<”:是modem上报消息给RILJ。
————————————————
版权声明:本文为CSDN博主「linyongan」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/linyongan/article/details/52066306
Android Telephony分析(三) ---- RILJ详解的更多相关文章
- Android Telephony分析(五) ---- TelephonyRegistry详解
本文紧接着上一篇文章<Android Telephony分析(四) —- TelephonyManager详解 >的1.4小节.从TelephonyRegistry的大部分方法中: 可以看 ...
- Android Telephony分析(二) ---- RegistrantList详解
前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.在Telephony模块中,在RIL.Tracker(ServiceStateTrac ...
- Android Telephony分析(四) ---- TelephonyManager详解
前言 TelephonyManager主要提供Telephony相关信息的查询/修改功能,以及Phone状态监听功能,封装的方法主要是提供给APP上层使用.TelephonyManager.java ...
- Android Telephony分析(一) ---- Phone详解
目录: Phone的继承关系与PhoneFactory(GsmCdmaPhone.ImsPhone.SipPhone) Phone进程的启动 Phone对象的初始化(DefaultPhoneNotif ...
- Android Telephony分析(六) ---- 接口扩展(实践篇)
本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能.如果还没有阅读过前面五篇文章的内容,请先阅读:<Android Telephony分析(一) — Phone详解 >& ...
- Android 之窗口小部件详解(三) 部分转载
原文地址:http://blog.csdn.net/iefreer/article/details/4626274. (一) 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget ...
- Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送
Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...
- Android项目刮刮奖详解(三)
Android项目刮刮奖详解(二) 前言 上一期我们已经实现了一个简易的刮刮卡功能,这一期我们来将其完善一下 目标 将刮刮奖的宽高改为合适高度 将刮刮奖位置居中 将信息层的图片换成文字(重点) 实现 ...
- [Android新手区] SQLite 操作详解--SQL语法
该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法 :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...
随机推荐
- centos coreseek
下载稳定版 coreseek wget http://www.coreseek.cn/uploads/csft/3.2/coreseek-3.2.14.tar.gz 解压 .tar.gz cd cor ...
- 调试Bochs在Linux Mint下面symbol not found的问题
在我的Linux Mint上使用Bochs时出现了很奇怪的问题,按照http://www.cnblogs.com/long123king/p/3568575.html步骤 会提示: symbol no ...
- vs设置html的模板快
打开vs编辑器,点击文件-->首选项-->用户代码片段 之后选择先对应的编辑器模板 进入里面编写相对应的代码块 之后直接在编辑器中调用.
- 第一章 Linux是什么
Linux是核心与系统调用接口两层中间的操作系统 不同硬件的功能函数并不相同,IBM的Power CPU与Inter的x86架构不同,所以同一套操作系统是不能在不同的硬件平台上面运行的.也就是说,每种 ...
- Pandas分类数据和顺序数据转换为标志变量
#导入pandas库 import pandas as pd #OneHotEncoder用来将数值型类别变量转换为0-1的标志性变量 #LabelEncoder用来将字符串型变量转换为数值型变量 f ...
- js 实现图片懒加载
搬运自其他大神,因为找不到链接了就没放,找到了补上. 个人情况:页面超过一屏,下方是大量图片数据(后台传来的html数据) ,想做到一开始不加载下方图片,滚动到进入可视区再加载图片. html:(需先 ...
- 第二天:PowerShell别名
1.查询别名: Get-Alias -name ls Get-Alias -name dir Get-Alias -name fl Get-Alias -name ft 2.查看可用的别名 查看可用的 ...
- node-express(1)建立post、get、跨域问题解决方案
首先下载express:npm i express let ess=require('express'); let app=ess(); let bodyParser=require('body-pa ...
- re.match与re.search的区别
re.match与re.search的区别 re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None:而re.search匹配整个字符串,直到找到一个匹配. 实 ...
- __init__初始化方法
使用场景:多个对象(由同一个类产生)的属性同名且值都一样,这时就需要使用init()方法. # 多个对象(由同一个类产生)的属性同名且值都一样,这时就需要使用__init__()方法. # class ...