前言

本文主要讲解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详解的更多相关文章

  1. Android Telephony分析(五) ---- TelephonyRegistry详解

    本文紧接着上一篇文章<Android Telephony分析(四) —- TelephonyManager详解 >的1.4小节.从TelephonyRegistry的大部分方法中: 可以看 ...

  2. Android Telephony分析(二) ---- RegistrantList详解

    前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.在Telephony模块中,在RIL.Tracker(ServiceStateTrac ...

  3. Android Telephony分析(四) ---- TelephonyManager详解

    前言 TelephonyManager主要提供Telephony相关信息的查询/修改功能,以及Phone状态监听功能,封装的方法主要是提供给APP上层使用.TelephonyManager.java ...

  4. Android Telephony分析(一) ---- Phone详解

    目录: Phone的继承关系与PhoneFactory(GsmCdmaPhone.ImsPhone.SipPhone) Phone进程的启动 Phone对象的初始化(DefaultPhoneNotif ...

  5. Android Telephony分析(六) ---- 接口扩展(实践篇)

    本文将结合前面五篇文章所讲解的知识,综合起来,实现一个接口扩展的功能.如果还没有阅读过前面五篇文章的内容,请先阅读:<Android Telephony分析(一) — Phone详解 >& ...

  6. Android 之窗口小部件详解(三)  部分转载

    原文地址:http://blog.csdn.net/iefreer/article/details/4626274. (一) 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget ...

  7. Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

    Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...

  8. Android项目刮刮奖详解(三)

    Android项目刮刮奖详解(二) 前言 上一期我们已经实现了一个简易的刮刮卡功能,这一期我们来将其完善一下 目标 将刮刮奖的宽高改为合适高度 将刮刮奖位置居中 将信息层的图片换成文字(重点) 实现 ...

  9. [Android新手区] SQLite 操作详解--SQL语法

    该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法  :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...

随机推荐

  1. 3-基于LoG的特征检测子

  2. iptables对请求的URL作IP访问控制

    服务器运行环境是Tomcat,现在要实现的目的是,只允许特定的IP访问某个目录,一种方法是在tomcat配置文件server.conf中,使用RemoteAddrValve对虚拟主机做访问控制.另外一 ...

  3. XSS的原理分析与解剖:第四章(编码与绕过)*******************未看**********************

    0×01前言 很抱歉,这第四章被我推了几个月,今天是元旦难得有空,就把第四章写下.我先把主要使用的编码说下,介绍完会说下绕过. 本文建议与<杂谈如何绕过WAF>一同阅读. 0×02 URL ...

  4. share memory

    header for public argument:shmdata.h #define TEXT_SZ 2048 struct shared_use_st { int written; char t ...

  5. 杂项:SVN -u

    ylbtech-杂项:SVN 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   7.返回顶部   8.返回顶部   9.返回顶部   10 ...

  6. (2)centos7 图形界面

    1.登录 2.先安装MATE可视化桌面 yum groups install "MATE Desktop" 选择y 3.安装X Window System:图形接口 yum gro ...

  7. HTTP六大请求

    标准Http协议支持六种请求方法,即: 1.GET 2.POST 3.PUT 4.Delete 5.HEAD 6.Options 但其实我们大部分情况下只用到了GET和POST.如果想设计一个符合RE ...

  8. jmter 二次开发 IDEA 项目5.1

    jmter 二次开发 IDEA 项目5.1 IDEA 编译 Jmeter 5.0(二次开发) 1. Java环境配置 1.1     步骤1 1.2     步骤2 1.3     步骤3 1.4   ...

  9. git分布式版本控制系统权威指南学习笔记(四):git reset

    文章目录 git reset目录树重写 git reset 重置 git reset目录树重写 git reset --soft 暂存区工作区不变 git reset --hard git reset ...

  10. 引入scss(@import)和其中易错点

    1.引入文件方式 @import 'url'; ./ :当前目录 ../ :上级目录 src/api/styles: 绝对路径 2.一般在main.js中引用当做全局样式 import 'styles ...