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);
}
});
}
......
}
}
这里主要做了几件事来处理应用卸载完后的善后处理:
- 删除数据库中的数据:主要涉及workspace、hotset和文件夹中的shortcut和widget的数据
- 删除workspace、hotset中的shortcut和widget相关的View
- 更新AllApps的缓存,并刷新界面
考虑到展开分析文章过长,以上逻辑并没有展开分析,按照这个思路,大家可以进一步分析。
小结
以上就是AOSP launcher3中应用卸载的大体过程,有分析不当的地方,欢迎指正,不甚感激!