前言

本文主要讲解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. Service6

    rsync同步操作 同步 : 只传输变化的数据     复制:完整的传输 • 命令用法– rsync [选项...] 源目录 目标目录 • 同步与复制的差异– 复制:完全拷贝源到目标– 同步:增量拷贝 ...

  2. Spring源码剖析4:懒加载的单例Bean获取过程分析

    本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...

  3. asp.net core容器&mysql容器network互联 & docker compose方式编排启动多个容器

    文章简介 asp.net core webapi容器与Mysql容器互联(network方式) docker compose方式编排启动多个容器 asp.net core webapi容器与Mysql ...

  4. vue组件通信之父组件主动获取子组件数据和方法

    ref 可以用来获取到dom节点,如果在组件中应用,也可以用来获取子组件的数据和方法. 比如,我定义了一个home组件,一个head组件,home组件中引用head组件. 此时,home组件是head ...

  5. java IO 类概述表

    列举常用的类方便查看,温故知新! byte input byte output character input character output Basic InputStream OutputStr ...

  6. JSON.toJSONString()null值转“”

    public static void main(String[] s) { CybWmsCommoditiesVo cybWmsCommoditiesVo = new CybWmsCommoditie ...

  7. 内网端口转发[netsh]

    一.利用场景 当前获取目标内网边界区域一台机器,可以通外网和内网也就是存在两块网卡,又通过其他手段获取到内网另外一台机器,但是这台机器不能出外网,所以我们可以使用windows自带netsh命令通过边 ...

  8. Java—Map浅入

    写支付签名的时候遇到了Map一家,就简单的比较了一下,于是乎先打印看看结果 Map<String,String> hashMap1 = new HashMap<>();hash ...

  9. session复制

    环境描述:(三台服务器 系统:7.6)192.168.200.111 nginx192.168.200.112 tomcat192.168.200.113 tomcat环境配置:192.168.200 ...

  10. nginx防DDOS、cc、爬虫攻击

    一.防止DDOS.CC攻击 http { limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_nam ...