android源码探索----多用户下phone进程问题
android4.2增加了多用户功能,终于在迟迟之后与linux保持了一致。但是手机上的多用户其实是相当鸡肋的,试想手机这种移动设备基本 上就是每一个人的唯一id,所以基本上不存在多用户共用设备的情况。也正因为此以及专利的原因,所以电话上的多用户功能是关闭的,只有平板上的多用户是打 开的。但还是要感谢谷歌开发人员引入多用户机制,这样可以帮助开发一些安全系统有极大帮助。
但打开多用户之后,有一个比较蛋疼的地方是无法在多用户中打电话发短信。查看源码的知,这是android对于电话通信这一块根本没有做多用户兼容性适 配。只是在PhoneApp中简单粗暴的做了一个单用户判断if (UserHandle.myUserId() == UserInfo.ROOT_USER_ID ),在其它用户中根本无法使用电话和短信。而平板上又无这种通信需求,所以谷歌开发人员根本没在这块做代码适配。唉,无奈我们公司项目需要这方面的功能, 即在多用户下也要能打电话发短信。所以只能硬这头皮上了,去做谷歌人员未竟的工作。这个过程是痛苦的前期后后后找了好几个phone方面比较熟悉的兄弟帮 忙分析代码,加起来有快10天的工作量,终于初步满足了需求,能打电话发短信了。下面是将这一过程中遇到的问题,做个摘录以备忘。
首先理解多用户原理,就算多用户到底是一个什么东西以及是一个什么样的实现机制。多用户固名思意,就是在同一台设备上隔离出另一个用户空间,这个空间里 面运行的程序与普通空间运行的程序是隔离的,完全是在两个进程中,当然数据的存储,比如数据库也完全是独立的。最直观的感受就是,两个用户中装上同一个应 用,那么这两个应用的导航页是需要走两遍的。当然这里面涉及到很多隔离,比如install,锁屏,某些设置等,所以在看4.2之后的源代码,会发现基本 上所有的模块都涉及到多用户的判断,这说明android在加多用户这个功能的时候,谷歌开发人员也做了一个海量的工作啊,这里表示钦佩和感激。
好了,闲话少说,继续说多用户下phone进程问题。
一。首先创建多用户之后,该用户下会有一个com.android.phone进程,但是该进程是完全没有东西的,因为在进程入口PhoneApp中已经做了判断:
public void onCreate() {
if (UserHandle.myUserId() == 0) {
// We are running as the primary user, so should bring up the
// global phone state.
if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {
mPhoneGlobals = new MSimPhoneGlobals(this);
} else {
mPhoneGlobals = new PhoneGlobals(this);
}
mPhoneGlobals.onCreate();
}
}
看到了吧,只有在0用户(根用户)下,才能进行phone模块的初始化。所以这里做了第一个修改就是拿掉这个判断,这个很容易,不再赘述。
代码位置android/packages/services/Telephony/src/com/android/phone/PhoneApp.java中的onCreate()方法
二。去掉判断之后,当然希望能直接打电话了,但事情永远不会这么顺利,接下来下面这个异常挡住了前路:
E/AndroidRuntime( 3452): Caused by: java.lang.SecurityException
E/AndroidRuntime( 3452): at android.os.BinderProxy.transact(Native Method)
E/AndroidRuntime( 3452): at android.os.ServiceManagerProxy.addService(ServiceManagerNative.java:150)
E/AndroidRuntime( 3452): at android.os.ServiceManager.addService(ServiceManager.java:72)
E/AndroidRuntime( 3452): at com.android.internal.telephony.IccSmsInterfaceManager.<init>(IccSmsInterfaceManager.java:128)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneProxy.init(PhoneProxy.java:95)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneProxy.<init>(PhoneProxy.java:84)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneFactory.makeDefaultPhone(PhoneFactory.java:134)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneFactory.makeDefaultPhones(PhoneFactory.java:59)
E/AndroidRuntime( 3452): at com.android.phone.PhoneGlobals.onCreate(PhoneGlobals.java:416)
E/AndroidRuntime( 3452): at com.android.phone.PhoneApp.onCreate(PhoneApp.java:45)
E/AndroidRuntime( 3452): at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1009)
E/AndroidRuntime( 3452): at android.app.LoadedApk.makeApplication(LoadedApk.java:526)
E/AndroidRuntime( 3452): ... 12 more
分析异常log,发现是在添加一个与短信相关的服务isms时失败,报了一个 SecurityException。但是在普通用户下这个服务是添加成功 了的,所以在这里有一个初步的猜测,就是添加系统服务这个动作并不是任何进程都能做的,因为在普通用户下phone进程uid=1001,而在多用户下 phone的uid=901001(9用户),所以猜测是底层c代码中对addservice的调用者做了权限控制。果不其然,跟踪addservice 的源码最后跟到c层,在service_manager.c中有一个do_add_service()方法,这个方法就是ServiceManager的 addService方法的最终实现,在该方法中对调用者的uid做了一个判断:
int svc_can_register(unsigned uid, uint16_t *name)
{
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
可以发现,只有root,system,以及系统基础的phone,短信等进程才能通过判断,而9用户中的phone进程的uid==901001(多用 户进程uid==userId*100000+uid),显然不在这个范围之内,当然添加系统服务失败。修改方法是在这里面加上一句多用户兼容性适配 uid = uid % 100000。这样多用户下相关进程也是可以添加系统服务了。
代码位置:android/frameworks/native/cmds/servicemanager/service_manager.c的svc_can_register()方法.
三。服务添加完了,事情还远远没有结束。接下来,在多用户中,打电话时提示“无法访问移动网络”.
显然手机没有连上信号,查看log
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
I/RILJ ( 3839): Couldn‘t find ‘rild‘ socket; retrying after timeout
E/RILJ ( 3839): Couldn‘t find ‘rild‘ socket after 8 times, continuing to retry silently
这些log是在RIL.java中报出来的。这是java层与ril层通信的类,而这个异常就是在与ril链接的时候报出来的,说明在多用户下是链接不上ril的。
事情到这里就很不好搞了,本人对于ril通信这一块完全没接触过啊。但是项目工期紧急,只能去求助公司的phone这块研发人员。最终,黄天不负苦 心人,经过与phone这块人员的讨论之后,找到了一个解决方案:就是在用户切换的时候,对ril进行一个断开重连的过程,因为ril链接同时只能被一个 客户端链接,而多用户里是会有多个phone进程的。所以在这里,必须将之前普通用户下的ril链接断开,在新用户里重新链接ril。当然,这里肯定需要 一个对phone很熟悉的人员找到这样一个切换链接的地方,而我们公司正好各方面的人才都有,实现这样一个方案不是太困难。
同时,为了连上ril还要在ActivityManagerService的startProcessLocked()方法里开启phone进程的时候,将进程的gid=1001.这里是一个权限判断,对于gid!=1001的进程是不能连上ril服务的。
四。用户切换时要替换两个系统服务
在PhoneInterfaceManager里切换phone的service(打电话相关)IccSmsInterfaceManager里切换isms(发短信相关)
这两个修改主要是保证来回切换用户时,其它进程通过这两个服务获取电话短信相关状态操作时能正确获取。
好了,整体上主要是做了这四个修改。虽然这里说的很简单,但整个过程其实是很漫长纠结的,一度以为解决不了了,毕竟google人员这一块也是简单粗暴的没有处理。但万幸最终在同僚帮助下解决了。
感谢同僚!
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。