上一章墨香带你学Launcher之(三)- 绑定屏幕、图标、文件夹和Widget讲了桌面CellLayout、图标、文件夹以及Widget的绑定流程,这个主要是第一次加载时的绑定流程,那么当用户安装新的应用,或者更新应用,或者卸载应用时桌面是如何处理的,今天我们来学习一下这个流程。
我们知道应用的安装和更新都是通过应用市场来启动,而应用的卸载是通过桌面或者系统的app管理来启动的,因此我们将应用的安装和更新一起来讲,而应用的卸载单独来讲。首先我们先看一下应用的安装和更新时桌面的数据加载。
应用安装和更新
当我们通过应用市场安装或者更新应用时,会调用系统的安装界面,并执行安装程序,在应用安装或者更新完成后系统会发出对应的广播,通过对应广播Launcher会执行相应的加载程序。首先我们看一个App管理的兼容库:LauncherAppsCompat,这里面有一个接口和一些抽象方法,我们用的到底主要是这个接口还有两个抽象方法:
接口类:
1 2 3 4 5 6 7
| 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移除、添加、改变、可用和不可用的各种情况的处理。
抽象方法:
1 2
| public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener); public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
|
这两个方法主要是添加、删除App管理的监听。
我们看到LauncherAppsCompat是一个抽象类,那么我们看看到底哪里实现了这里的方法:
通过上图我们看到有两个类继承了这个抽象类:LauncherAppsCompatV16和LauncherAppsCompatVL,这两个类其实是针对不同的API版本做了兼容处理,LauncherAppsCompatV16是Android的sdk版本高于16低于21的系统的处理,LauncherAppsCompatVL是sdk版本高于21的系统的处理,那么从21以上到底做了哪些不同处理我们简单看一下,在他们的父类中有一个抽象方法:
1 2
| public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName, UserHandleCompat user);
|
这个方法是获取系统中所有应用的方法,我们看看在不同版本是怎么处理的,首先看一下LauncherAppsCompatV16中:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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是个什么时候开始有的:
我们可以看到是Api为21时才开始添加的,我们看一下这个类中主要的方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| 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中实现:
1 2 3
| private static class WrappedCallback extends LauncherApps.Callback { ... }
|
这个回调类就是实现app管理的那些方法,那么我们再看一下LauncherAppsCompat16中如何实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @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:
1 2 3
| public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) { ... }
|
这个方法是通过LauncherAppState中调用的:
1
| LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
|
我们看参数是一个mModel,其实告诉我们是在LanuncherModel中实现的,LauncherAppsCompatVL中也是在这里调用的,我们就不在看了,自己看看就知道了。LauncherModel中的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @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这个方法,我们看看哪里实现了这个方法:
在这里有三处实现,其实从名字我们可以看出最后一个是卸载应用的实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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,也就是对应的图标放回原处。
对于系统怎么卸载应用的这里就不讲了,有兴趣的可以去看源码处理。
最后
Github地址:https://github.com/yuchuangu85/Launcher3_mx/tree/launcher3_6.0
Android开发群:192508518
微信公众账号:Code-MX
注:本文原创,转载请注明出处,多谢。