Launcher3之应用卸载过程分析

引言

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

应用卸载过程分析

1、launcher中卸载的发起

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

来看张交互效果:
在这里插入图片描述

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

public interface DropTarget

public abstract class ButtonDropTarget extends TextView implements DropTarget

public class SecondaryDropTarget extends ButtonDropTarget

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

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

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

public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
    @Override
    public void onDrop(DragObject d, DragOptions options) {
        // Defer onComplete
        d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
        super.onDrop(d, options); //调用父类ButtonDropTarget的实现
    }
}

public abstract class ButtonDropTarget extends TextView
        implements DropTarget, DragController.DragListener, OnClickListener {
		
    public abstract void completeDrop(DragObject d);
		
    @Override
    public 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对其进行了实现。

    @Override
    public 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;

    @Override
    public 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;
        }

        @Override
        public void onPackageRemoved(String packageName, UserHandle user) {
            mCallback.onPackageRemoved(packageName, user);// 收到卸载回调
        }
	}
}

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

public class LauncherModel extends BroadcastReceiver
        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
	...
	
    @Override
    public 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 {
    @Override
    public 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 infos
        if (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)) {// 判断是否shortcut
                        ShortcutInfo 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 removed
            Collections.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、删除数据库中数据,删除其他跟此包名相关的界面组件,如widget
            deleteAndBindComponentsRemoved(removeMatch);

            // Remove any queued items from the install queue
            InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
        }

		// 3、刷新AllApps界面
        if (!removedApps.isEmpty()) {
            // Remove corresponding apps from All-Apps
            scheduleCallbackTask(new CallbackTask() {
                @Override
                public void execute(Callbacks callbacks) {
                    callbacks.bindAppInfosRemoved(removedApps);
                }
            });
        }
		......
	}
}

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

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

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

小结

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值