目录
1. 概述
最近客户反馈使用我们的RIL库进行基站定位失败。这个FAE提需求过来时,就比较奇怪,基站定位这个是GNSS框架,跟RIL库没有关系,对比竟品说是正常。最后看log才清楚是有RIL消息获取小区注册信息,应用通过获取小区信息,客户那边进行基站定位。RIL这边只需要处理小区信息查询的消息就行。
2.流程分析
2.1 类图
在画完类图时,个人比较喜欢把类图的层次进行整理,这样就比较好理解。
从上图来看获取小区信息是Andorid典型的AIDL框架,也就是通过AIDL提供接口。
相关类的路径及功能说明如下:
- PhoneApp.java: packages\services\Telephony\src\com\android\phone
TeleService启动的入口类。
- TelephonyGlobals.java: packages\services\Telephony\src\com\android\services\telephony
Telephony相关类的创建。
- PhoneInterfaceManager.java: packages\services\Telephony\src\com\android\phone
phone的AIDL服务
- PhoneFactory.java: frameworks\opt\telephony\src\java\com\android\internal\telephony
Phone的工厂类
- TelephonyComponentFactory.java: frameworks\opt\telephony\src\java\com\android\internal\telephony
TelephonyComponent的工厂类
- GsmCdmaPhone.java: frameworks\opt\telephony\src\java\com\android\internal\telephony
GsmCdma 电话管理类
- ServiceStateTracker.java: frameworks\opt\telephony\src\java\com\android\internal\telephony
服务状态跟踪
- RIL.java: frameworks\opt\telephony\src\java\com\android\internal\telephony
Radio interface layer
- TelephonyManager.java: frameworks\base\telephony\java\android\telephony
对外提供telephony接口
- SystemServiceRegistry.java: frameworks\base\core\java\android\app
服务注册类
2.2.1 AIDL服务分析
服务是对外提供接口,关于小区信息获取,对外提供的接口是getCellLocation函数。而服务里面是如何实现这个函数的,先从初始化来讲。
TeleService.apk是个后台服务,从manifest配置里面可看到初始化的java文件是PhoneApp,然后调用onCreate函数:
public void onCreate() {
if (UserHandle.myUserId() == 0) {
// We are running as the primary user, so should bring up the
// global phone state.
mPhoneGlobals = new PhoneGlobals(this);
mPhoneGlobals.onCreate();
mTelephonyGlobals = new TelephonyGlobals(this);
mTelephonyGlobals.onCreate();
}
}
接着调用PhoneGlobals的onCreate函数,然后创建PhoneInterfaceManager对象phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
PhoneInterfaceManager类是继承了ITelephony.Stub类,所以这个是AIDL的服务,对外提供了小区信息的接口。对应的初始化代码如下:
/**
* Initialize the singleton PhoneInterfaceManager instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
/* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
sInstance = new PhoneInterfaceManager(app, phone);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
/** Private constructor; @see init() */
private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
mApp = app;
mPhone = phone;
mCM = PhoneGlobals.getInstance().mCM;
mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
mMainThreadHandler = new MainThreadHandler();
mTelephonySharedPreferences =
PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
mSubscriptionController = SubscriptionController.getInstance();
mNetworkScanRequestTracker = new NetworkScanRequestTracker();
publish();
}
private void publish() {
if (DBG) log("publish: " + this);
ServiceManager.addService("phone", this);
}
代码一路初始化下来,最终调用publish,把当前服务添加到ServiceManager里面,这样在多进程中调用此服务提供的接口就方便了,TelephonyManager类就是个很好的例子。接下来看下getCellLocation的实现流程。
@Override
public Bundle getCellLocation(String callingPackage) {
mPhone.getContext().getSystemService(AppOpsManager.class)
.checkPackage(Binder.getCallingUid(), callingPackage);
if (!LocationAccessPolicy.canAccessCellLocation(mPhone.getContext(),
callingPackage, Binder.getCallingUid(), Binder.getCallingPid(), true)) {
return null;
}
if (DBG_LOC) log("getCellLocation: is active user");
Bundle data = new Bundle();
Phone phone = getPhone(mSubscriptionController.getDefaultDataSubId());
if (phone == null) {
return null;
}
WorkSource workSource = getWorkSource(Binder.getCallingUid());
phone.getCellLocation(workSource).fillInNotifierBundle(data);
return data;
}
这里就是代码一步一步的跟下去,调用的类依次是GsmCdmaPhoneà ServiceStateTrackerà RIL。最终在RIL.java里面调用如下函数,并发送RIL_REQUEST_GET_CELL_INFO_LIST消息给rild来获取。
public void getCellInfoList(Message result, WorkSource workSource) {
workSource = getDeafultWorkSourceIfInvalid(workSource);
IRadio radioProxy = getRadioProxy(result);
if (radioProxy != null) {
RILRequest rr = obtainRequest(RIL_REQUEST_GET_CELL_INFO_LIST, result,
workSource);
if (RILJ_LOGD) {
riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
}
try {
radioProxy.getCellInfoList(rr.mSerial);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "getCellInfoList", e);
}
}
}
2.2.2 API接口
从类图来看,TelephonyManager是调用AIDL接口的应用层,这个角度是从AIDL框架理解是没错。但如果从应用角度来看的话,TelephonyManager又是一个提供接口的服务类,因为应用调用Telephony相关接口都是由他来提供。而这里面又是怎么实现。先看下TelephonyManager的初始化。
在SystemServiceRegistry类里进行了注册,此机制应用如果要调用TelephonyManager接口,通过getSystemService函数就行,所以此时TelephonyManager就是个服务类。
registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
new CachedServiceFetcher<TelephonyManager>() {
@Override
public TelephonyManager createService(ContextImpl ctx) {
return new TelephonyManager(ctx.getOuterContext());
}});
而接下看下TelephonyManager关于getCellLocation的封装,他其实是获取ITelephony 的客户端,然后调用ITelephony 提供的接口getCellLocation。所以对于应用角度来看,TelephonyManager就是个服务。
public CellLocation getCellLocation() {
android.util.SeempLog.record(49);
try {
ITelephony telephony = getITelephony();
if (telephony == null) {
Rlog.d(TAG, "getCellLocation returning null because telephony is null");
return null;
}
Bundle bundle = telephony.getCellLocation(mContext.getOpPackageName());
......
}
}
综上所述,其实TelephonyManager类似一个代理,他不实现具体功能,帮应用间接提供接口。其实为何这么做,继续看下下面的这段代码就清楚了。TelephonyManager里面有如下4个获取AIDL客户端的函数,这么做就是让应用层少涉及业务层的逻辑,直接封装一个类给应用层处理。
/**
* @hide
*/
private ITelephony getITelephony() {
return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}
/**
* @hide
*/
private ITelecomService getTelecomService() {
return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
}
private ITelephonyRegistry getTelephonyRegistry() {
return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
}
/**
* @hide
*/
private IPhoneSubInfo getSubscriberInfo() {
// get it each time because that process crashes a lot
return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
}
2.2 时序图
关于getCellLocation的调用流程,在类图章节已分析过,这里画了个时序图做了总结。
3. 接口说明
RIL消息:RIL_REQUEST_GET_CELL_INFO_LIST
请求参数:无
返回参数:
typedef struct {
RIL_CellInfoType cellInfoType; /* cell type for selecting from union CellInfo */
int registered; /* !0 if this cell is registered 0 if not registered */
RIL_TimeStampType timeStampType; /* type of time stamp represented by timeStamp */
uint64_t timeStamp; /* Time in nanos as returned by ril_nano_time */
union {
RIL_CellInfoGsm_v12 gsm;
RIL_CellInfoCdma cdma;
RIL_CellInfoLte_v12 lte;
RIL_CellInfoWcdma_v12 wcdma;
RIL_CellInfoTdscdma tdscdma;
} CellInfo;
} RIL_CellInfo_v12;
具体查看ril.h文件
4总结
本文档其实关于小区信息的获取跟踪并不难,重点是google的设计逻辑,多看,多学习。