墨香带你学Launcher之(四)-应用安装、更新、卸载时的数据加载

上一章墨香带你学Launcher之(三)-绑定屏幕、图标、文件夹和Widget讲了桌面CellLayout、图标、文件夹以及Widget的绑定流程,这个主要是第一次加载时的绑定流程,那么当用户安装新的应用,或者更新应用,或者卸载应用时桌面是如何处理的,今天我们来学习一下这个流程。

我们知道应用的安装和更新都是通过应用市场来启动,而应用的卸载是通过桌面或者系统的app管理来启动的,因此我们将应用的安装和更新一起来讲,而应用的卸载单独来讲。首先我们先看一下应用的安装和更新时桌面的数据加载。

应用安装和更新


当我们通过应用市场安装或者更新应用时,会调用系统的安装界面,并执行安装程序,在应用安装或者更新完成后系统会发出对应的广播,通过对应广播Launcher会执行相应的加载程序。首先我们看一个App管理的兼容库:LauncherAppsCompat,这里面有一个接口和一些抽象方法,我们用的到底主要是这个接口还有两个抽象方法:

接口类:

public interface OnAppsChangedCallbackCompat {
        void onPackageRemoved(String packageName, UserHandleCompat user);
        void onPackageAdded(String packageName, UserHandleCompat user);
        void onPackageChanged(String packageName, UserHandleCompat user);
        void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
        void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
    }

从这个接口中的方法我们可以了解到这个是对App移除、添加、改变、可用和不可用的各种情况的处理。

抽象方法:

 public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
 public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);

这两个方法主要是添加、删除App管理的监听。

我们看到LauncherAppsCompat是一个抽象类,那么我们看看到底哪里实现了这里的方法:

launcher01.png

通过上图我们看到有两个类继承了这个抽象类:LauncherAppsCompatV16和LauncherAppsCompatVL,这两个类其实是针对不同的API版本做了兼容处理,LauncherAppsCompatV16是Android的sdk版本高于16低于21的系统的处理,LauncherAppsCompatVL是sdk版本高于21的系统的处理,那么从21以上到底做了哪些不同处理我们简单看一下,在他们的父类中有一个抽象方法:

 public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName,
            UserHandleCompat user);

这个方法是获取系统中所有应用的方法,我们看看在不同版本是怎么处理的,首先看一下LauncherAppsCompatV16中:

 public List<LauncherActivityInfoCompat> getActivityList(String packageName,
            UserHandleCompat user) {
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        mainIntent.setPackage(packageName);
        List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0);
        List<LauncherActivityInfoCompat> list =
                new ArrayList<LauncherActivityInfoCompat>(infos.size());
        for (ResolveInfo info : infos) {
            list.add(new LauncherActivityInfoCompatV16(mContext, info));
        }
        return list;
    }

这个方法是通过包管理来查询所有的应用,然后根据循环来收集所有的应用信息。

而LauncherAppsCompatVL中:

public List<LauncherActivityInfoCompat> getActivityList(String packageName,
            UserHandleCompat user) {
        List<LauncherActivityInfo> list = mLauncherApps.getActivityList(packageName,
                user.getUser());
        if (list.size() == 0) {
            return Collections.emptyList();
        }
        ArrayList<LauncherActivityInfoCompat> compatList =
                new ArrayList<LauncherActivityInfoCompat>(list.size());
        for (LauncherActivityInfo info : list) {
            compatList.add(new LauncherActivityInfoCompatVL(info));
        }
        return compatList;
    }

这个方法中是通过mLauncherApps直接过去的列表信息,那么我们看看api文档,这个LauncherApp是个什么时候开始有的:

launcher02.png

我们可以看到是Api为21时才开始添加的,我们看一下这个类中主要的方法;

public class LauncherApps {
    
    ...
    
    public static abstract class Callback {
       
        abstract public void onPackageRemoved(String packageName, UserHandle user);
        
        abstract public void onPackageAdded(String packageName, UserHandle user);
        
        abstract public void onPackageChanged(String packageName, UserHandle user);

        abstract public void onPackagesAvailable(String[] packageNames, UserHandle user,
                boolean replacing);

        abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
                boolean replacing);
    }
    
    public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
       ...
    }
    
    private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {

        @Override
        public void onPackageRemoved(UserHandle user, String packageName)
                throws RemoteException {
           ...
        }

        @Override
        public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
           ...
        }

        @Override
        public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
            ...
        }

        @Override
        public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
                throws RemoteException {
           ...
        }

        @Override
        public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
                throws RemoteException {
            ...
           }
        }
    };

    private static class CallbackMessageHandler extends Handler {
        ...

        @Override
        public void handleMessage(Message msg) {
            if (mCallback == null || !(msg.obj instanceof CallbackInfo)) {
                return;
            }
            CallbackInfo info = (CallbackInfo) msg.obj;
            switch (msg.what) {
                case MSG_ADDED:
                    mCallback.onPackageAdded(info.packageName, info.user);
                    break;
                case MSG_REMOVED:
                    mCallback.onPackageRemoved(info.packageName, info.user);
                    break;
                case MSG_CHANGED:
                    mCallback.onPackageChanged(info.packageName, info.user);
                    break;
                case MSG_AVAILABLE:
                    mCallback.onPackagesAvailable(info.packageNames, info.user, info.replacing);
                    break;
                case MSG_UNAVAILABLE:
                    mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
                    break;
            }
        }

        public void postOnPackageAdded(String packageName, UserHandle user) {
           ...
        }

        public void postOnPackageRemoved(String packageName, UserHandle user) {
           ...
        }

        public void postOnPackageChanged(String packageName, UserHandle user) {
           ...
        }

        public void postOnPackagesAvailable(String[] packageNames, UserHandle user,
                boolean replacing) {
            ...        }

        public void postOnPackagesUnavailable(String[] packageNames, UserHandle user,
                boolean replacing) {
            ...        }
    }
}

在这个类中,谷歌工程师直接给我们封装了查找所有应用信息的方法,以及App包管理的方法,也就是Callback这个类中的抽象方法,和我们前面讲到的兼容库中的接口是一样的,只是21之前是自己来写,21之后是系统帮我们做了处理,我们直接实现就好了。

LauncherAppsCompatVL中实现:

private static class WrappedCallback extends LauncherApps.Callback {
        ...
}

这个回调类就是实现app管理的那些方法,那么我们再看一下LauncherAppsCompat16中如何实现的:

@Thunk class PackageMonitor extends BroadcastReceiver {
        public void onReceive(Context context, Intent intent) {
            ...

            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
                    || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                ...
                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                    ...
                        callback.onPackageChanged(packageName, user);
                    ...
                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                    ...
                            callback.onPackageRemoved(packageName, user);
                     ...
                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                   ...
                            callback.onPackageChanged(packageName, user);
                     ...
                }
            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
               ...
                    callback.onPackagesAvailable(packages, user, replacing);
                ...
            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
                ...
                    callback.onPackagesUnavailable(packages, user, replacing);
                ...
            }
        }
    }

此时我们可以看到是通过接受广播然后回调不同的方法来实现的。那么回调的实现又是在哪里呢,上面我们看到都是调用的callback中的方法,只要找到哪里初始化了callback即可,我们发现是通过下面方法传进来的这个callback:

public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
       ...
    }

这个方法是通过LauncherAppState中调用的:

LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

我们看参数是一个mModel,其实告诉我们是在LanuncherModel中实现的,LauncherAppsCompatVL中也是在这里调用的,我们就不在看了,自己看看就知道了。LauncherModel中的代码如下:

@Override
    public void onPackageChanged(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_UPDATE;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }

    @Override
    public void onPackageRemoved(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_REMOVE;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }

    @Override
    public void onPackageAdded(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_ADD;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[]{packageName},
                user));
    }
    ...

通过上面代码我们可知最后都是调用PackageUpdatedTask这个任务执行的,只是传入的参数不同,这个任务中代码很多也不贴了,我简单介绍下任务执行过程,首先区分收到广播是安装、更新、移除还是不可用,然后对不同的操作执行不同的处理,对于添加的调用addAppsToAllApps这个方法进行处理,最后调用callbacks.bindAppsAdded方法进行绑定,更新的调用callbacks.bindAppsUpdated这个方法进行更新操作,代码很简单,自己看一下就好了,对于卸载的,更新图标缓存,将其移除掉,在这些操作的同时,要还行数据的数据更新操作,如果有小部件的,也要对小部件做相应的操作处理。

应用的卸载


原生桌面的卸载应用是将图标拖拽到卸载框进行卸载的,关于拖拽的操作流程我们后续会详细讲解,这里我们直接看拖拽到相应位置的处理,在完成拖拽时会调用completeDrop这个方法,我们看看哪里实现了这个方法:

launcher03.png

在这里有三处实现,其实从名字我们可以看出最后一个是卸载应用的实现,

void completeDrop(final DragObject d) {
        ...
        if (startUninstallActivity(mLauncher, d.dragInfo)) {

            final Runnable checkIfUninstallWasSuccess = new Runnable() {
                @Override
                public void run() {
                    String packageName = componentInfo.first.getPackageName();
                    boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
                            getContext(), packageName, user);
                    sendUninstallResult(d.dragSource, uninstallSuccessful);
                }
            };
            mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
        } else {
            sendUninstallResult(d.dragSource, false);
        }
    }

首先判断startUninstallActivity,这个方法中调用startApplicationUninstallActivity方法,如果是系统应用返回false,如果不是启动卸载界面并且返回true,启动卸载界面是通过Intent.ACTION_DELETE这个action启动的,如果能够卸载,执行完卸载返回到桌面时,或者取消返回到桌面时,检测是否卸载成功,然后调用sendUninstallResult方法,在这个方法中调用onUninstallActivityReturned回调函数,这个回调函数是在Folder或者workspace中实现的,其实代码这两个地方都一样,最后都执行onDropCompleted方法,如果移除成功,则调用removeWorkspaceItem方法,这个方法主要是从CellLayout中删除对应的图标,如果没有成功则刷新UI,也就是对应的图标放回原处。

对于系统怎么卸载应用的这里就不讲了,有兴趣的可以去看源码处理。

最后


本文原创,转载请注明出处。

公众账号:Code-MX

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 上一章墨香带你学Launcher之(七)- 小部件的加载、添加以及大小调节介绍了小部件的加载以及添加过程,基于我的...
    翰墨飘香阅读 3,776评论 5 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 蕙质凝幽露,纤柔化梦姝。 风尘无觅处,沁骨黛眉舒。
    紫风铃_阅读 445评论 6 5
  • 没有感觉了,不想结婚了。自己独身一人也挺好的。不知道是不是因为吃药,反正是没有一点需要男人的感觉。孤单寂寞可以干点...
    星月1号阅读 218评论 0 0