Android系统默认Home(Launcher)的启动过程小结


       Android系统开机,各个应用是如何加载并被显示到桌面上的呢?带着这份好奇,阅读了在

Android应用程序安装过程源代码分析 一文中,我们看到应用程序的apk归档文件中的配置文件

AndroidManifest.xml 会被解析,解析得到的application,service和activity等信息保存在

PackageManagerService中。


      但是我们进入HOME界面,是要看到各个Android app的快捷图标和名称的。显示app的这些信息,

就是我们的HOME,也就是Launcher干的事情了。代码流程是从SystemServer 开始的,调用栈为:

ServerThread::run ( SystemServer.java) ——>  ActivityManagerService::main (ActivityManagerService.java)

——> ActivityManagerService:: startRunning ——> ActivityManagerService::systemReady ——> 

ActivityStack::resumeTopActivityLocked ——> ActivityManagerService::startHomeActivityLocked

 其中startHomeActivityLocked函数首先创建一个CATEGORY_HOME类型的Intent,然后通过

Intent.resolveActivityInfo函数向PackageManagerService查询Category类型为HOME的Activity。

这里只有系统自带的Launcher应用程序注册了HOME类型的Activity

(见packages/apps/Launcher2/AndroidManifest.xml文件):


<activity
            android:name="com.android.launcher2.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:theme="@style/Theme"
            android:configChanges="mcc|mnc"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="nosensor">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
</activity>

      最终,com.android.launcher2.Launcher被启动起来,其onCreate函数被调用。具体可参考

Android应用程序启动过程源代码分析  一文。 在activity start流程中,performLaunchActivity

调用。里面的mInstrumentation.callActivityOnCreate(activity, r.state); 调用的就是Instrumentation

类的callActivityOnCreate方法。调用堆栈为:ActivityThread::performLaunchActivity ——> 

Instrumentation::callActivityOnCreate ——>  Activity::performCreate ——> onCreate(icicle); 

最后这个就是创建的Launcher 这个Activity覆盖的onCreate方法。至此,Launcher.onCreate

被调用了。接下来的调用流程为:Launcher.onCreate ——> LauncherModel.startLoader ——> 

LoaderTask.run ——> LoaderTask.loadAndBindAllApps ——> LoaderTask.loadAllAppsByBatch

 

      函数首先构造一个CATEGORY_LAUNCHER类型的Intent:

final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

      接着从mContext变量中获得PackageManagerService的接口:

final PackageManager packageManager = mContext.getPackageManager();

       下一步就是通过这个PackageManagerService.queryIntentActivities接口来查询所有Action

类型为Intent.ACTION_MAIN,并且Category类型为Intent.CATEGORY_LAUNCHER的Activity了。

final PackageManager packageManager = mContext.getPackageManager();

List<ResolveInfo> apps = null;

...
...
apps = packageManager.queryIntentActivities(mainIntent, 0);

      PackageManagerService会把系统中的应用程序都解析一遍,然后把解析得到的Activity都保存在

mActivities变量中,这里通过这个mActivities变量的queryIntent函数返回符合条件intent的Activity,这里

返回的便是Action类型为Intent.ACTION_MAIN,并且Category类型为Intent.CATEGORY_LAUNCHER

的Activity了。

   终于知道我们自己写的app的入口activity为啥要设置这样的action和Category了吧??     

for (int j=0; i<N && j<batchSize; j++) {
     // This builds the icon bitmaps.
     mBgAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
     mIconCache, mLabelCache));
     i++;
 
}
final ArrayList<ApplicationInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<ApplicationInfo>();
 if (first) {
     callbacks.bindAllApplications(added);
} else {
      callbacks.bindAppsAdded(added);
}
      各个app的入口activity信息将会被用于构造ApplicationInfo对象。上面的new ApplicationInfo

通过调用构造函数,将icon设置。

 public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
            HashMap<Object, CharSequence> labelCache) {
        final String packageName = info.activityInfo.applicationInfo.packageName;

        this.componentName = new ComponentName(packageName, info.activityInfo.name);
        this.container = ItemInfo.NO_ID;
        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        try {
            int appFlags = pm.getApplicationInfo(packageName, 0).flags;
            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
                flags |= DOWNLOADED_FLAG;

                if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
                    flags |= UPDATED_SYSTEM_APP_FLAG;
                }
            }
            firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;
        } catch (NameNotFoundException e) {
            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
        }

        iconCache.getTitleAndIcon(this, info, labelCache);
    }

 public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
            HashMap<Object, CharSequence> labelCache) {
        synchronized (mCache) {
            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);

            application.title = entry.title;
            application.iconBitmap = entry.icon;
        }
    }

 private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
            HashMap<Object, CharSequence> labelCache) {
        if (LauncherLog.DEBUG_LAYOUT) {
            LauncherLog.d(TAG, "cacheLocked: componentName = " + componentName
                    + ", info = " + info + ", HashMap<Object, CharSequence>:size = "
                    +  ((labelCache == null) ? "null" : labelCache.size()));
        }

        CacheEntry entry = mCache.get(componentName);
        if (entry == null) {
            entry = new CacheEntry();

            mCache.put(componentName, entry);

            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
            if (labelCache != null && labelCache.containsKey(key)) {
                entry.title = labelCache.get(key).toString();
                if (LauncherModel.DEBUG_LOADERS) {
                    LauncherLog.d(TAG, "CacheLocked get title from cache: title = " + entry.title);
                }                
            } else {
                entry.title = info.loadLabel(mPackageManager).toString();
                if (LauncherModel.DEBUG_LOADERS) {
                    LauncherLog.d(TAG, "CacheLocked get title from pms: title = " + entry.title);
                }                
                if (labelCache != null) {
                    labelCache.put(key, entry.title);
                }
            }
            if (entry.title == null) {
                entry.title = info.activityInfo.name;
                if (LauncherModel.DEBUG_LOADERS) {
                    LauncherLog.d(TAG, "CacheLocked get title from activity information: entry.title = " + entry.title);
                }
            }

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);
        }
        return entry;
    }
       看来是通过在cacheLocked里调用这个createIconBitmap实现的啊。

    有了这些ApplicationInfo实例之后,就可以在桌面上展示系统中所有的应用程序了。当我们点击

“HOME"按键的时候,各个应用图标就会被显示。现在我们来看看Launcher::onClick的处理流程:

public void onClick(View v) {
...
...

if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));

            boolean success = startActivitySafely(v, intent, tag);

            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
            if (isAllAppsVisible()) {
                showWorkspace(true);
            } else {
                onClickAllAppsButton(v);
            }
}

      这里我们点击的是HOME按键,对应的是 v == mAllAppsButton这个case。且看onClickAllAppsButton :

public void onClickAllAppsButton(View v) {
    showAllApps(true);
}
void showAllApps(boolean animated) {
...
...
 /// M: Call the appropriate callback for the IMTKWidget on the current page when enter all apps list.
        mWorkspace.startCovered(mWorkspace.getCurrentPage());
        showAppsCustomizeHelper(animated, false);
        mAppsCustomizeTabHost.requestFocus();

...
...
}
      里面具体怎么画出来的,我还真不清楚。只能帮大家引路到这里了。

     当我们点击应用程序图标的时候,执行的是tag instanceof ShortcutInfo这个case。最终通过调用

 final Intent intent = ((ShortcutInfo) tag).intent; 和 boolean success = startActivitySafely(v, intent, tag);

来启动对应app的入口activity。


    Launcher的流程暂且分析到这里。我们回过头来看,总共有

(1)PackageManagerService解析app的AndroidManifest.xml。PackageManagerService将应用程序

的apk归档文件中的配置文件AndroidManifest.xml 解析,得到的application,service和activity等信息

保存在PackageManagerService中。

(2)启动Launcher这个app的入口activity,调用其onCreate方法。调用startHomeActivityLocked流程中,

向PackageManagerService查询Category类型为HOME的Activity,发现只有Launcher;接着进入

startActivity的流程。在performLaunchActivity调用Instrumentation类的callActivityOnCreate方法。

最后,调用到Launcher 这个app对应的onCreate方法。

(3)构造每个app的入口activity信息对应的ApplicationInfo对象,设置应用程序图标。

Launcher.onCreate调用流程中,通过调用PackageManagerService.queryIntentActivities接口来查询

所有Action类型为Intent.ACTION_MAIN,并且Category类型为Intent.CATEGORY_LAUNCHER的Activity。

(4)Launcher::onClick中调用onClickAllAppsButton来显示布满app的页面(HOME)。

(5)点击应用程序图标时,在Launcher::onClick中调用startActivitySafely启动该应用的入口activity。

    

    要是给咱们自己整个简单的Launcher,只需要保存各个app配置文件AndroidManifest.xml 的各个

重要信息(例如入口activity),然后通过读取配置文件,将应用程序的图标和名称读出来保存起来,

当响应HOME按键时,画出各个应用程序图标和名称等信息。当点击应用程序图标时,获取其

入口Activity等信息,调用startActivity等函数去启动入口Activity。



  更多源码分析,请参考:Android系统默认Home应用程序(Launcher)的启动过程源代码分析


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值