Android Launcher分析和修改4——初始化加载数据

  • Post author:
  • Post category:其他



上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来,


界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Launcher如何加载数据。


在Launcher.java的onCreate()方法里面,调用了开始加载数据接口:

//加载启动数据
if (!mRestoring) 
{ mModel.startLoader(
this, true); }


mModel是LauncherModel的对象,由此可见,数据加载主要是在LauncherModel类里面实现的。




1、Callbacks接口


LauncherModel里面,需要先分析一个Callbacks接口。

    public interface Callbacks {
        public boolean setLoadOnResume();
        public int getCurrentWorkspaceScreen();
        public void startBinding();
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
        public void bindFolders(HashMap<Long,FolderInfo> folders);
        public void finishBindingItems();
        public void bindAppWidget(LauncherAppWidgetInfo info);
        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
        public void bindPackagesUpdated();
        public boolean isAllAppsVisible();
        public void bindSearchablesChanged();
    }


Callbacks接口提供了很多接口,用于返回相关的数据给Launcher模块,下面我们对每个接口作用做个阐释。




setLoadOnResume()



当Launcher.java类的Activity处于onPause的时候,如果重新恢复,需要调用onResume,此时需要在onResume调用这个接口,恢复Launcher数据。



getCurrentWorkspace():

获取屏幕序号(0~4)



startBinding():

通知Launcher开始加载数据。清空容器数据,重新加载



bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):

加载App shortcut、Live Folder、widget到Launcher相关容器。



bindFolders(HashMap<Long, FolderInfo> folders):

加载folder的内容



finishBindingItems():

数据加载完成。



bindAppWidget(LauncherAppWidgetInfo item)

:workspace加载APP 快捷方式



bindAllApplications(final ArrayList<ApplicationInfo> apps):

所有应用列表接着APP图标数据



bindAppsAdded(ArrayList<ApplicationInfo> apps):

通知Launcher新安装了一个APP,更新数据。



bindAppsUpdated(ArrayList<ApplicationInfo> apps):

通知Launcher一个APP更新了。(覆盖安装)



bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):

通知Launcher,应用被删除



bindPackagesUpdated()

:多个应用更新。



isAllAppsVisible():

返回所有应用列表是否可见状态。



bindSearchablesChanged():

Google搜索栏或者删除区域发生变化时通知Launcher




2、数据加载流程


Launcher.java类继承了Callbacks接口,并实现了该接口。LauncherModel里面会调用这些接口,反馈数据和状态给Launcher。数据加载总体分为两部分,一部分是加载workspace的数据,另一部分是加载All APP界面的数据。


下面是一个加载数据流程图:






3、startLoader()


下面我们先分析startLoader()接口,startLoader主要是启动了一个线程,用于加载数据。

    public void startLoader(Context context, boolean isLaunching) {
        synchronized (mLock) 
        {
            //...............
            if (mCallbacks != null && mCallbacks.get() != null) {
                isLaunching = isLaunching || stopLoaderLocked();
                mLoaderTask = new LoaderTask(context, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask); 
            }
        }
    }


startLoader主要是启动

LoaderTask线程里面的run方法。


sWorker是一个Handle对象,用于启动线程的run方法。




4、LoaderTask的run()方法

  public void run() {
       //............

            keep_running: {
         //...............

         //加载当前页面的数据,先把一页的数据加载完成,
//主要是为了增加程序流畅性,提高用户体验
if (loadWorkspaceFirst) { if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); loadAndBindWorkspace(); } else { if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); loadAndBindAllApps(); } if (mStopped) { break keep_running; } // THREAD_PRIORITY_BACKGROUND设置线程优先级为后台,
         //这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
synchronized (mLock) { if (mIsLaunching) { if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } }
         //等待线程空闲的时候,继续加载其他页面数据 waitForIdle();
//加载剩余页面的数据,包含workspace和all app页面 if (loadWorkspaceFirst) { if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); loadAndBindAllApps(); } else { if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); loadAndBindWorkspace(); } // Restore the default thread priority after we are done loading items synchronized (mLock) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } } }


上面是经过简化的LoaderTask的run方法代码,其实主要就两部分操作,第一部分操作,加载当前页面的数据


(当前workspace页面或者当前All APP页面的数据)然后等待线程空闲的时候,再加载剩余的页面数据。


代码上面加了关键注释,可以结合代码分析。这样做主要目的是增加Launcher启动的速度,让用户觉得系统初始化速度


较快,有较好的用户体验。先把用户看见的界面初始化完毕,然后再开一个后台线程慢慢加载其他的数据。


下面我们分别分析workspace和All APP加载和绑定。




5、workspace加载数据

loadAndBindWorkspace()方法主要就是执行loadWorkspace()和 bindWorkspace()方法。
下面分别对这两个方法进行分析。
        private void loadWorkspace() {
       //..........

       //清空容器,存放界面不同的元素,App快捷方式、widget、folder synchronized (sBgLock) { sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgDbIconCache.clear(); final ArrayList
<Long> itemsToRemove = new ArrayList<Long>(); final Cursor c = contentResolver.query( LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); // +1 for the hotseat (it can be larger than the workspace) // Load workspace in reverse order to ensure that latest items are loaded first (and // before any earlier duplicates)
        
  //表示屏幕上的位置,
         //第一维表示分屏的序号,其中最后一个代表Hotseat             
         //第二维表示x方向方格的序号         
         //第三维表示y方向方格的序号
                final ItemInfo occupied[][][] =
                        new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
          //读取数据库响应键值列序号
                try {
                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
                    final int intentIndex = c.getColumnIndexOrThrow
                            (LauncherSettings.Favorites.INTENT);
                    final int titleIndex = c.getColumnIndexOrThrow
                            (LauncherSettings.Favorites.TITLE);
                    final int iconTypeIndex = c.getColumnIndexOrThrow(
                            LauncherSettings.Favorites.ICON_TYPE);
            //...........
          
while (!mStopped && c.moveToNext()) { try { int itemType = c.getInt(itemTypeIndex); switch (itemType) {
                
 //item类型为ITEM_TYPE_APPLICATION或者ITEM_TYPE_SHORTCUT  
                //container为CONTAINER_DESKTOP或者CONTAINER_HOTSEAT
                //把当前的item添加到sWorkspaceItems中
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                      emType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
                                            titleIndex, mLabelCache);
                                } else {
                                    info = getShortcutInfo(c, context, iconTypeIndex,
                                            iconPackageIndex, iconResourceIndex, iconIndex,
                                            titleIndex);
                    
                                    switch (container) {
                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                        //添加数据 sBgWorkspaceItems.add(info);
break; default: //如果item的属性是folder,添加到folder,创建forder FolderInfo folderInfo = findOrMakeFolder(sBgFolders, container); folderInfo.add(info); break; } sBgItemsIdMap.put(info.id, info); } else { } break;                 //item类型为文件夹,添加 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: id = c.getLong(idIndex); FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
                   //.........
sBgItemsIdMap.put(folderInfo.id, folderInfo); sBgFolders.put(folderInfo.id, folderInfo); break;                  //Widget添加 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: // Read all Launcher-specific widget details int appWidgetId = c.getInt(appWidgetIdIndex); id = c.getLong(idIndex);                    //........... sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); sBgAppWidgets.add(appWidgetInfo); break; } } catch (Exception e) { Log.w(TAG, "Desktop items loading interrupted:", e); } } } finally { c.close(); } } }


workspace的数据加载总的来说也是按照元素属性来区分加载,分为App快捷方式、Widget、Folder元素。


这几个元素分别加载到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo组成的映射。其他


元素分别加载到3个不同的容器里面,用于后面绑定数据用。这里只给出了loadWorkspace的流程代码,详细代码,


需要看源码,还有很多细节。不过刚开始分析Launcher,我的原则是先把握整体流程和知道改动代码,需要在哪里查找。




6、workspace绑定数据


Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、


finishBindingItems()的调用。下面针对

bindWorkspace做个简单的流程分析。

 private void bindWorkspace() {

       //通知Launcher开始绑定数据 mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { //绑定数据到launcher,Launcher回调,清空相关容器 OWL callbacks.startBinding(); } } }); //添加元素到workspace,主要是添加APP快捷方式 N = workspaceItems.size(); for (int i=0; i<N; i+=ITEMS_CHUNK) { final int start = i; final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindItems(workspaceItems, start, start+chunkSize); } } }); } //文件夹绑定 final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders); mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindFolders(folders); } } }); //分两次加载widget ,当前界面和其他界面,增强用户体验OWL
     //其他页面widget会在后台线程再次加载 final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); N = sAppWidgets.size(); // once for the current screen for (int i=0; i<N; i++) { final LauncherAppWidgetInfo widget = sAppWidgets.get(i); if (widget.screen == currentScreen) { mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }); } } //加载其他看不见的屏幕widget for (int i=0; i<N; i++) { final LauncherAppWidgetInfo widget = sAppWidgets.get(i); if (widget.screen != currentScreen) { mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }); } } //加载完成,通知Launcher,已经完成数据加载 mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.finishBindingItems(); } } }); }


上面就是Launcher的workspace绑定数据的过程,跟加载数据过程很相似,也是区分3中类型的元素进行加载。


下面我们总结一下,workspace的加载和绑定数据的过程。我们现在回头看,可以发现,其实workspace里面就是


存放了3中数据ItemInfo、FolderInfo、LauncherAppWidgetInfo。分别对应我们的APP快捷方式、文件夹、Widget


数据。其中FolderInfo、LauncherAppWidgetInfo都是继承了ItemInfo。数据加载过程,就是从Launcher的数据库


读取数据然后按元素属性分别放到3个ArrayList里面。绑定数据过程就是把3个ArrayList的队列关联到Launcher界面里面。


7、ALL APP数据加载绑定

      private void loadAllAppsByBatch() {//只有这两个标记才需要显示在所有程序列表 OWL
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

            int N = Integer.MAX_VALUE;

            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
                if (i == 0) {
                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    //过滤需要显示的app
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities took "
                                + (SystemClock.uptimeMillis()-qiaTime) + "ms");
                    }
                    if (apps == null) {
                        return;
                    }
                    N = apps.size();
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities got " + N + " apps");
                    }
                    if (N == 0) {
                        // There are no apps?!?
                        return;
                    }
                    //mBatchSize==0表示一次性加载所有的应用  
                    if (mBatchSize == 0) {
                        batchSize = N;
                    } else {
                        batchSize = mBatchSize;
                    }
                }

                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();
         //绑定加载所有的APP数据
                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
                            if (first) {
                                //一次性加载所以app,返回数据到launcher
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });
}


AllAPP的数据加载和绑定跟workspace的差不多,也是先加载数据然后绑定数据,通知Launcher。

加载数据的时候





PackageManager获取所有已经安装的APK包信息,然后过滤只包含需要显示在所有应用列表的应用,需要包含





ACTION_MAIN和


CATEGORY_LAUNCHER两个属性。这个我们在编写应用程序的时候都应该知道。




AllAPP加载跟workspace不同的地方是加载的同时,完成数据绑定的操作,也就是说第一次加载AllAPP页面的数据,



会同时绑定数据到Launcher。第二次需要加载的时候,只会把数据直接绑定到Launcher,而不会重新搜索加载数据。



Launcher启动加载和绑定数据就是这样完成。绑定完数据,Launcher就可以运行。




系列文章:





Android Launcher分析和修改1——Launcher默认界面配置(default_workspace)





Android Launcher分析和修改2——Icon修改、界面布局调整、壁纸设置




Android Launcher分析和修改3——Launcher启动和初始化




Edited by mythou



原创博文,转载请标明出处:


http://www.cnblogs.com/mythou/p/3165259.html



<script type=”text/javascript”>
</script><script type=”text/javascript” src=”http://pagead2.googlesyndication.com/pagead/show_ads.js”></script>