首页 > 代码库 > 深入分析Android开机找网延迟

深入分析Android开机找网延迟

一般来说,phone的属性是persistent为true,而因此phone进程也是较早被叫起,被android:presistent修饰的应用在系统启动之后会被AM启动,即便没有运行,AM也会调用startProcessLocked启动该进程。启动package com.android.phone 即phone application,这会直接call到PhoneApp的onCreate(),执行初始化找网的动作。

我们查看源码如下:

alps\packages\services\Telephony\AndroidManifest.xml 

<application android:name="PhoneApp" 

android:persistent="true" 

......

那么phone被正常叫起的log应该如下:

01-01 14:01:47.890: I/ActivityManager(717): Start proc com.android.phone for added application com.android.phone: pid=991 uid=1001 gids={41001, 3002, 3001, 3003, 1028, 1015, 1004, 2002, 1023}
从上面log中可以看到Phone进程正常叫起。但是我们去查看找网延迟的手机去复现的时候发现,Phone进程并不是正常叫起,而是被run在Phone进程中的组件所启动,参考log如下:

01-29 14:08:29.842: I/ActivityManager(723): Start proc com.android.phone for service com.mediatek.CellConnService/.PhoneStatesMgrService: pid=990 uid=1001 gids={41001, 3002, 3001, 3003, 1028, 1015, 1004, 2002, 1023}
从上面log中可以看到当前phone进程是由CellConnService.PhoneStatesMgrService服务叫起,我们查看PhoneStatesMgrService源码发现,该服务作为phone的一个组件运行在phone进程中。参考源码如下:

 <application android:process="com.android.phone"//通过这个android:process="com.android.phone"属性,我们可以指定某个组件运行的进程。我们可以通过设置这个属性,让每个组件运行在它自己的进程中,也可以只让某些组件共享一个进程。我们要可以通过设置“android:process”属性,让不同应用程序中的组件运行在相同的进程中。这里com.mediatek.CellConnService作为一个组件运行在com.android.phone中
                 android:allowClearUserData=http://www.mamicode.com/"false"
                ......
<service android:name=".PhoneStatesMgrService">
<intent-filter>
<action android:name="android.intent.action.CELLCONNSERVICE" />
<action android:name="com.mediatek.CellConnService.IPhoneStatesMgrService" />
</intent-filter>
</service>

那么如果phone进程是由phone的接口叫起,那么就无法执行初始化找网的操作,而是等到alps\packages\services\telephony\AndroidManifest.xml 中OtaStartUpReceiver接收BOOT_COMPLETED后才能叫起PhoneApp,进而才执行它的onCreate()进行初始化找网的操作。这样无疑会导致找网延迟。那么此时我们需要排查哪里启动了PhoneStagtesMgrService。这里我们需要关注一下上面加粗标红的一个Action---CELLCONNSERVICE,然后我们接着分析log,参考log如下:

01-29 14:08:29.764: W/ContextImpl(882): Implicit intents with startService are not safe: Intent { act=android.intent.action.CELLCONNSERVICE } android.content.ContextWrapper.startService:494 com.mediatek.CellConnService.CellConnMgr.register:159 com.android.systemui.huawei.MobileStateManager.<init>:106 
01-29 14:08:29.852: W/ContextImpl(882): Implicit intents with startService are not safe: Intent { act=android.intent.action.CELLCONNSERVICE } android.content.ContextWrapper.bindService:517 com.mediatek.CellConnService.CellConnMgr.register:160 com.android.systemui.huawei.MobileStateManager.<init>:106 
01-29 14:08:32.294: W/ContextImpl(882): Implicit intents with startService are not safe: Intent { act=android.intent.action.CELLCONNSERVICE } android.content.ContextWrapper.startService:494 com.mediatek.CellConnService.CellConnMgr.register:159 com.android.systemui.huawei.MobileStateManager.<init>:106 
01-29 14:08:32.307: W/ContextImpl(882): Implicit intents with startService are not safe: Intent {act=android.intent.action.CELLCONNSERVICE } android.content.ContextWrapper.bindService:517 com.mediatek.CellConnService.CellConnMgr.register:160 com.android.systemui.huawei.MobileStateManager.<init>:106 

从上面的log我们看到MobileStateManager通过Intent启动了一个服务,而接收该Intent的服务恰恰是PhoneStagtesMgrService,参考代码如下:

代码一:此处时启动PhoneStatesMgrService的接口,位于CellConnServic下的CellConnMgr的类中。
public void register(Context ctx) {
Log.d(TAG, "register");


mCtx = ctx;


Intent it = new Intent("android.intent.action.CELLCONNSERVICE");
mCtx.startService(it);
mCtx.bindService(it, mConnection, Context.BIND_AUTO_CREATE);
}
代码二: 此处是systemui下的MobileStateManager中构造函数的调用
   public MobileStateManager(Context context) {
        mContext = context;
        mCellConnMgr = new CellConnMgr(null);
        //mCellConnMgr.register(mContext);//这里调用了启动PhoneStatesMgrService服务的接口
        mITelephony = getITelephony();
        mConnManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        updateSIMInfoList();
    }

因此我们在这里进行代码的优化,从而问题得到解决。

需要注意的是作为一个组件run在com.android.phone进程中的不只是com.mediatek.CellConnService,例如还有com.android.providers.telephony等,这里我也把com.android.providers.telephony叫起com.android.phone的分析方法与大家分享一下。

下面是通过调用com.android.providers.telephony叫起com.android.phone进程的log,如下:

01-30 11:02:44.750: I/ActivityManager(714): Start proc com.android.phone for content provider com.android.providers.telephony/.TelephonyProvider: pid=999 uid=1001 gids={41001, 3002, 3001, 3003, 1028, 1015, 1004, 2002, 1023}
从上面log中可以看到此次phone进程是由com.android.providers.telephony/.TelephonyProvider叫起,下面排查TelephonyProvider如何叫起Phone进程的。
首先通过查看源码确认可以如下:
<application android:process="com.android.phone"
                 android:allowClearUserData=http://www.mamicode.com/"false"
                 android:allowBackup="false"
                 android:label="@string/app_label"
                 android:icon="@drawable/ic_launcher_phone">


        <provider android:name="TelephonyProvider"
                  android:authorities="telephony"
                  android:exported="true"
                  android:multiprocess="false" />
        <!-- M: Code analyze 002, new feature, declear CbProvider. @{ -->
        <provider android:name="CbProvider"
                  android:authorities="cb"
                  android:exported="true"
                  android:multiprocess="false" />
        <!-- @} -->
        <provider android:name="SmsProvider"
                  android:authorities="sms"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS" />
        <!-- M: Code analyze 003, new feature, declear WapPushProvider. @{ -->
        <provider android:name="WapPushProvider"
                  android:authorities="wappush"
                  android:exported="true"
                  android:multiprocess="false" />
        <!-- @} -->
        <provider android:name="MmsProvider"
                  android:authorities="mms"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS">
            <grant-uri-permission android:pathPrefix="/part/" />
            <grant-uri-permission android:pathPrefix="/drm/" />
        </provider>
        <provider android:name="MmsSmsProvider"
                  android:authorities="mms-sms"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS" />
        <!-- M: add for MTK_ONLY_OWNER_SIM_SUPPORT -->
        <provider android:name="UserSmsProvider"
                  android:authorities="usersms"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS" />
        <provider android:name="UserCBProvider"
                  android:authorities="usercb"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS" />
        <provider android:name="UserMmsProvider"
                  android:authorities="usermms"
                  android:exported="true"
                  android:multiprocess="false"
                  android:readPermission="android.permission.READ_SMS"
                  android:writePermission="android.permission.WRITE_SMS">
            <grant-uri-permission android:pathPrefix="/part/" />
            <grant-uri-permission android:pathPrefix="/drm/" />
        </provider>          
        <!-- @} -->          
    </application>
</manifest>
从上面源码来看TelephonyProvider属于com.android.phone进程,可通过调用TelephonyProvider来启动phone进程,因此进一步排查,从下面log来看可以判断出进程id为878的com.mediatek.systemui中ActivityManager调用TelephonyProvider,而使TelephonyProvider Run在phone进程,进而启动了com.andorid.phone进程,也就是说我们通过叫起了TelephonyProvider间接叫起了phone进程,叫起TelephonyProvider的常规方法就是通过“content://telephony",这里要重点关注android:authorities="telephony";
01-30 11:02:44.640: D/SignalClusterView(878): setWifiIndicators, visible=false, strengthIcon=com.mediatek.systemui.ext.IconIdWrapper@426584f0, activityIcon=com.mediatek.systemui.ext.IconIdWrapper@42658508, contentDescription=Wi-Fi 连接已断开
01-30 11:02:44.643: V/PhoneStatusBar(878): carrierlabel for Gemini=android.widget.LinearLayout{42563b40 I.E..... ......I. 0,0-0,0 #7f080013 app:id/carrier_label_gemini} show=true
01-30 11:02:44.658: D/ActivityManager(714): getContentProviderImpl: from callerandroid.app.ApplicationThreadProxy@42ab1f60 (pid=878) to get content provider telephony
01-30 11:02:44.750: I/ActivityManager(714): Start proc com.android.phone for content provider com.android.providers.telephony/.TelephonyProvider: pid=999 uid=1001 gids={41001, 3002, 3001, 3003, 1028, 1015, 1004, 2002, 1023}
从上面log来看是878进程(systemui)叫起了com.android.providers.telephony/.TelephonyProvider(进程id为999),也就是说是systemui叫起了TelephonyProvider,进而叫起了com.android.phone进程。参考log如下:

01-30 11:02:42.133: I/SurfaceFlinger(146): EventThread Client Pid (714) created

因此我们需要排查systemui中的的代码,参考排查方法如下:

01-30 11:02:44.640: D/SignalClusterView(878): setWifiIndicators, visible=false, strengthIcon=com.mediatek.systemui.ext.IconIdWrapper@426584f0, activityIcon=com.mediatek.systemui.ext.IconIdWrapper@42658508, contentDescription=Wi-Fi 连接已断开
01-30 11:02:44.643: V/PhoneStatusBar(878): carrierlabel for Gemini=android.widget.LinearLayout{42563b40 I.E..... ......I. 0,0-0,0 #7f080013 app:id/carrier_label_gemini} show=true
01-30 11:02:44.658: D/ActivityManager(714): getContentProviderImpl: from callerandroid.app.ApplicationThreadProxy@42ab1f60 (pid=878) to get content provider telephony
01-30 11:02:44.750: I/ActivityManager(714): Start proc com.android.phone for content provider com.android.providers.telephony/.TelephonyProvider: pid=999 uid=1001 gids={41001, 3002, 3001, 3003, 1028, 1015, 1004, 2002, 1023}
从上面log来看是878进程(systemui)叫起了com.android.providers.telephony/.TelephonyProvider(进程id为999),也就是说依然是systemui叫起了TelephonyProvider,进而叫起了com.android.phone进程。
然后根据log中来看在叫起TelephonyProvider进程前,上面标红的地方(878为systemui进程)作了操作,下面是源码中的调用关系,从下面源码来看的确有很多地方调用了TelephonyProvider,如HuaweiQuickSettingsController等。需要排查一下这些地方为何调用。
 
SimInfoManager类中
    public static final Uri CONTENT_URI = 
            Uri.parse("content://telephony/siminfo");
    ......
    public static List<SimInfoRecord> getInsertedSimInfoList(Context ctx) {
        logd("[getInsertedSimInfoList]+");
        ArrayList<SimInfoRecord> simList = new ArrayList<SimInfoRecord>();
        Cursor cursor = ctx.getContentResolver().query(CONTENT_URI, 
                null, SLOT + "!=" + SLOT_NONE, null, null);//这里调用了TelephonyProvider
        try {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    simList.add(fromCursor(cursor));
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        logd("[getInsertedSimInfoList]- " + simList.size() + " infos return");
        return simList;
    }
    
    SIMHelper类中
      private static List<SimInfoManager.SimInfoRecord> getSortedSIMInfoList(Context context) {
        List<SimInfoManager.SimInfoRecord> simInfoList = SimInfoManager.getInsertedSimInfoList(context);
        Collections.sort(simInfoList, new Comparator<SimInfoManager.SimInfoRecord>() {
        .....
    PhoneStatusBar类中
        
     public void showSimIndicator(String businessType) {
        if (mIsSimIndicatorShowing) {
            hideSimIndicator();
        }
        mBusinessType = businessType;
        long simId = SIMHelper.getDefaultSIM(mContext, businessType);
        Xlog.d(TAG, "showSimIndicator, show SIM indicator which business is " + businessType + "  simId = "+simId+".");
        if (simId == android.provider.Settings.System.DEFAULT_SIM_SETTING_ALWAYS_ASK) {
            List<SimInfoManager.SimInfoRecord> simInfos = SIMHelper.getSIMInfoList(mContext);
        ....
在源码中有很多调用的地方,或许是为了获取sim的状态而调用了TelephonyProvider,具体繁琐的log和代码就不解释了。

到此整个分析过程都在这里了,如有疑问可以发消息给我,一起来讨论。



深入分析Android开机找网延迟