zoukankan      html  css  js  c++  java
  • Android Launcher分析和修改4——初始化加载数据

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

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

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

    //Edited by mythou
    //http://www.cnblogs.com/mythou/
    //加载启动数据 if (!mRestoring)
    { mModel.startLoader(
    this, true); }

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

     

    1、Callbacks接口

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

    
    
    //Edited by mythou
    //http://www.cnblogs.com/mythou/
     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主要是启动了一个线程,用于加载数据。


    //Edited by mythou
    //http://www.cnblogs.com/mythou/
    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()方法


    //Edited by mythou
    //http://www.cnblogs.com/mythou/
    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()方法。
    下面分别对这两个方法进行分析。

    //Edited by mythou
    //http://www.cnblogs.com/mythou/
    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做个简单的流程分析。


    //Edited by mythou
    //http://www.cnblogs.com/mythou/

    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数据加载绑定


    //Edited by mythou
    //http://www.cnblogs.com/mythou/

    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 

  • 相关阅读:
    ADF中遍历VO中的行数据(Iterator)
    程序中实现两个DataTable的Left Join效果(修改了,网上第二个DataTable为空,所处的异常)
    ArcGIS api for javascript——鼠标悬停时显示信息窗口
    ArcGIS api for javascript——查询,然后单击显示信息窗口
    ArcGIS api for javascript——查询,立刻打开信息窗口
    ArcGIS api for javascript——显示多个查询结果
    ArcGIS api for javascript——用图表显示查询结果
    ArcGIS api for javascript——查询没有地图的数据
    ArcGIS api for javascript——用第二个服务的范围设置地图范围
    ArcGIS api for javascript——显示地图属性
  • 原文地址:https://www.cnblogs.com/mythou/p/3165259.html
Copyright © 2011-2022 走看看