引言

在之前的文章,"Launcher3之新安装应用加载过程分析"一文,已经跟大家分析了新应用安装的过程,这篇文章再跟大家分享下它的姊妹篇,launcher3中应用卸载的过程。

应用卸载过程分析

1、launcher中卸载的发起

对于用户来说,Android手机中,应用的卸载入口大概就两个,一个在系统设置,一个是在launcher中通过拖动图标触发。这里从代码层面,来看下launcher中是怎么触发这个交互的。

来看张交互效果:

从上图可以猜测出,要完成卸载,需要把当前的拖动的应用图标,放到卸载按钮上去,代码中怎么实现的呢?
launcher中定义了一个接口类DropTarget,用于抽象图标可以落下的各种对象,这里的卸载的按钮就是其中一个对象。卸载按钮的实现类是SecondaryDropTarget,它其实是一个View,看下类的继承关系:

public interface DropTargetpublic abstract class ButtonDropTarget extends TextView implements DropTargetpublic class SecondaryDropTarget extends ButtonDropTarget

DropTarget定义了一系列接口,这里就不展开讲了,后续在图标拖动系列文章再详细分析。这里只涉及其中一个接口方法onDrop:

public interface DropTarget {......void onDrop(DragObject dragObject, DragOptions options);......
}

在将图标拖动到卸载按钮,并松开落下后,该方法会回调。我们看看SecondaryDropTarget中onDrop的实现:

public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {@Overridepublic void onDrop(DragObject d, DragOptions options) {// Defer onCompleted.dragSource = new DeferredOnComplete(d.dragSource, getContext());super.onDrop(d, options); //调用父类ButtonDropTarget的实现}
}public abstract class ButtonDropTarget extends TextViewimplements DropTarget, DragController.DragListener, OnClickListener {public abstract void completeDrop(DragObject d);@Overridepublic void onDrop(final DragObject d, final DragOptions options) {final DragLayer dragLayer = mLauncher.getDragLayer();......Runnable onAnimationEndRunnable = () -> {completeDrop(d); //发起卸载mDropTargetBar.onDragEnd();mLauncher.getStateManager().goToState(NORMAL); // 完成卸载后恢复到NORMAL状态};dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,DRAG_VIEW_DROP_DURATION,Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,DragLayer.ANIMATION_END_DISAPPEAR, null);}
}

SecondaryDropTarget中又调用了父类ButtonDropTarget中onDrop方法,然后调用了completeDrop抽象方法,SecondaryDropTarget对其进行了实现。

    @Overridepublic void completeDrop(final DragObject d) {ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);......}protected ComponentName performDropAction(View view, ItemInfo info) {ComponentName cn = getUninstallTarget(info);......try {Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0).setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())).putExtra(Intent.EXTRA_USER, info.user);mLauncher.startActivity(i); //启动卸载Activity,这个Activity在其他模块实现return cn;} catch (URISyntaxException e) {Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);return null;}  }

从上面代码可以看出,launcher中最终通过一个Intent启动了一个Activity,发起卸载操作,实际的卸载功能并不是launcher完成的,只是提供了一个交互的入口。
我们来看下这个卸载Activity效果,其实就是一个弹框效果:

2、卸载完成的回调

用户确认卸载后,系统将进行实际的应用卸载操作,完成后launcher也需要进行相应的处理。上面只是分析了卸载的发起,跳转到卸载界面后,似乎已经跟launcher没有交互了,那launcher是怎么知道卸载已经完成的呢?当然是有回调的喽!
看过我前面文章的朋友,应该还有印象,LauncherApps.Callback定义了一系列接口,卸载接口就是其中之一。

public class LauncherAppsCompatVL extends LauncherAppsCompat {protected final LauncherApps mLauncherApps;protected final Context mContext;@Overridepublic void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {WrappedCallback wrappedCallback = new WrappedCallback(callback);...mLauncherApps.registerCallback(wrappedCallback);//LauncherApps向系统注册监听}//接口实现private static class WrappedCallback extends LauncherApps.Callback {private final LauncherAppsCompat.OnAppsChangedCallbackCompat mCallback;// launcher中实际实现使用的接口public WrappedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {mCallback = callback;}@Overridepublic void onPackageRemoved(String packageName, UserHandle user) {mCallback.onPackageRemoved(packageName, user);// 收到卸载回调}}
}

再看看launcher中onPackageAdded在LauncherModel中的实际实现:

public class LauncherModel extends BroadcastReceiverimplements LauncherAppsCompat.OnAppsChangedCallbackCompat {...@Overridepublic void onPackageRemoved(String packageName, UserHandle user) {onPackagesRemoved(user, packageName);}public void onPackagesRemoved(UserHandle user, String... packages) {int op = PackageUpdatedTask.OP_REMOVE;enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));}...
}

卸载完成的回调大概就是这样,具体launcher界面和数据的更新就交给PackageUpdatedTask去处理了。

3、launcher中界面更新

"Launcher3之新安装应用加载过程分析"一文已经涉及过PackageUpdatedTask类的部分代码分析,我们再来看下卸载完成后,里面到底又做了哪些事情?
代码就是最好的文档,依然看代码:

public class PackageUpdatedTask extends BaseModelUpdateTask {@Overridepublic void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {...final String[] packages = mPackages;final int N = packages.length;FlagOp flagOp = FlagOp.NO_OP;...switch (mOp) {case OP_REMOVE: {for (int i = 0; i < N; i++) {iconCache.removeIconsForPkg(packages[i], mUser);// 移除icon缓存}// Fall through}case OP_UNAVAILABLE:for (int i = 0; i < N; i++) {appsList.removePackage(packages[i], mUser);// 更新AllAppsList中的缓存数据app.getWidgetCache().removePackage(packages[i], mUser);}flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);break;}}......final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed);appsList.removed.clear();final LongArrayMap<Boolean> removedShortcuts = new LongArrayMap<>();// Update shortcut infosif (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();......synchronized (dataModel) {for (ItemInfo info : dataModel.itemsIdMap) {if (info instanceof ShortcutInfo && mUser.equals(info.user)) {// 判断是否shortcutShortcutInfo si = (ShortcutInfo) info;boolean infoUpdated = false;boolean shortcutUpdated = false;......ComponentName cn = si.getTargetComponent();if (cn != null && matcher.matches(si, cn)) {// 匹配到被卸载应用的包名......int oldRuntimeFlags = si.runtimeStatusFlags;si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);if (si.runtimeStatusFlags != oldRuntimeFlags) {shortcutUpdated = true;}}if (infoUpdated || shortcutUpdated) {updatedShortcuts.add(si);}......}}}// 1、更新Workspace和文件夹中的shorcut,对于卸载应用来说,似乎没有意义bindUpdatedShortcuts(updatedShortcuts, mUser); ......}final HashSet<String> removedPackages = new HashSet<>();final HashSet<ComponentName> removedComponents = new HashSet<>();if (mOp == OP_REMOVE) {// Mark all packages in the broadcast to be removedCollections.addAll(removedPackages, packages);// No need to update the removedComponents as// removedPackages is a super-set of removedComponents}......if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser).or(ItemInfoMatcher.ofComponents(removedComponents, mUser)).and(ItemInfoMatcher.ofItemIds(removedShortcuts, true));// 2、删除数据库中数据,删除其他跟此包名相关的界面组件,如widgetdeleteAndBindComponentsRemoved(removeMatch);// Remove any queued items from the install queueInstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);}// 3、刷新AllApps界面if (!removedApps.isEmpty()) {// Remove corresponding apps from All-AppsscheduleCallbackTask(new CallbackTask() {@Overridepublic void execute(Callbacks callbacks) {callbacks.bindAppInfosRemoved(removedApps);}});}......}
}

这里主要做了几件事来处理应用卸载完后的善后处理:

  1. 删除数据库中的数据:主要涉及workspace、hotset和文件夹中的shortcut和widget的数据
  2. 删除workspace、hotset中的shortcut和widget相关的View
  3. 更新AllApps的缓存,并刷新界面

考虑到展开分析文章过长,以上逻辑并没有展开分析,按照这个思路,大家可以进一步分析。

小结

以上就是AOSP launcher3中应用卸载的大体过程,有分析不当的地方,欢迎指正,不甚感激!

Launcher3之应用卸载过程分析相关推荐

  1. 类加载过程中几个重点执行顺序整理

    类的加载过程: 1. JVM会先去方法区中找有没有相应类的.class存在.如果有,就直接使用:如果没有,则把相关类的.class加载到方法区 2. 在.class加载到方法区时,会分为两部分加载:先 ...

  2. java加载顺序_类加载过程中几个重点执行顺序整理

    正文前先来一波福利推荐: 福利一: 百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家. 福利二: 毕业答辩以及工 ...

  3. Launcher3应用的安装与卸载

    本文我们来看下Launcher3应用的安装与卸载的处理流程.应用的安装与卸载是通过PackageManagerService来完成的,在成功安装或者卸载之后它是怎么通知Launcher3更新的呢?关于 ...

  4. Launcher3 安装App加载显示过程分析,androidstudio开发app实例

    "Initializing LauncherAppState in the absence of LauncherProvider"); } Log.v(Launcher.TAG, ...

  5. Android Launcher3 去掉应用列表后,在系统桌面拖动应用图标卸载应用

    隐藏掉应用列表后,发现在Launcher的WorkSpace中不管怎样都市无法卸载应用的,只能删除图标.研究发现在DeleteDropTarget这个类中可以控制拖拽应用图标卸载应用. 1.修改代码, ...

  6. Android应用程序签名过程和解析过程分析

    在正式解释Android应用程序签名过程之前,作为铺垫,还得先讲讲最基本的一些概念. 非对称加密算法 非对称加密算法需要两个密钥:公开密钥(简称公钥)和私有密钥(简称私钥).公钥与私钥是一对,如果用公 ...

  7. python虚拟环境安装包_Python虚拟环境的创建和包下载过程分析

    为什么使用虚拟环境 因为直接在真实环境进行安装python的包会造成环境之间的污染,因此需要创建虚拟环境,原则上每一个项目都需要有一个独属于自己的虚拟环境 Python项目的每次运行都需要启动环境,如 ...

  8. android+残留软件包名,关于使用pm hide伪卸载系统软件的一些包名

    本帖最后由 北慕城南 于 2017-6-19 09:15 编辑 最新的卫士已经从云端屏蔽了终端的入口,如果你发现你的终端入口没了,只能通过断网->清空360卫士数据来找回 不想使用的系统软件能停 ...

  9. Android10/11 原生Launcher3深度定制开发

    一.引言 关于Android10和11系统Launcher3的定制有很多,根据项目的需求会进行各种定制开发, 于是就需要研究Launcher3的源码.本文主要从Android 11的Launcher3 ...

最新文章

  1. C++容器适配器之priority_queue
  2. python 2 类与对象
  3. 向量表示 运动抛物线_初学讲义之高中物理(四)常见运动类型
  4. 网络连接的net命令
  5. 关于meta http-equiv=Content-Type content=text/html:charset=UTF-8
  6. [转载] python获取set中某些元素_取集合中元素_Python Set集合
  7. 蓝桥杯dfs搜索专题
  8. Java程序员不得不会的124道面试题(含答案)
  9. 一款老飞飞_魅力飞飞脚本研究增加攻击与暴击几率方式探讨源码(附带易语言源码)
  10. SAP FICO全解析之-货币换算比率
  11. 洛谷 P1562 智破连环阵 题解(搜索)
  12. C++ primer 第三章 字符串、向量和数组 练习题
  13. 哪个求职App最容易找到好工作?2019在线求职招聘市场研究报告
  14. 黑苹果详细安装教程-基于OpenCore官网指导-UPUPMO(macOS Monterey)
  15. 基于51单片机的关于7SEG-MPX8-CA,7SEG-MPX6-CA,7SEG-MPX4-CA的使用
  16. Linux_cJSON--数据封装与解析
  17. 好的目标管理,让职场人更轻松
  18. 使用计算机开机按啥建,学生计算器第一次使用应该如何开机
  19. 爬取正方教务管理系统获取学生信息
  20. 无源定位入门(一)TDOA(3)CRLB

热门文章

  1. element的el-table列标题添加自定义图标
  2. 管理潜能测试软件,快速判断管理者潜力   人才测评工具来帮忙
  3. 分布式存储系统设计 反熵 (Anti-Entropy)
  4. apache添加php语言模块,在apache中添加php处理模块-Go语言中文社区
  5. Android简单适配9.0~12.0
  6. Linux文件系统从磁盘读页面
  7. 【计算机组成原理】CPU如何区分指令和数据
  8. 如何计算一只股票收盘价比开盘价上涨多少?并筛选出上升上涨超过5%的股票
  9. 数据库读写分离(二)
  10. 我的2007-written in BIT