MTK Android12-13 App卸载加锁
实现:App 卸载时候需要加一层拦截锁,客户输入密码后才能正常卸载
文章目录
- 参考资料:
- 实现方案
- 涉及到修改文件
- 修改方案
- 实现效果
- 源码分析- 卸载方式
- 一) 设置界面进行卸载
- InstalledAppDetails
- AppInfoDashboardFragment
- AppButtonsPreferenceController
- handleDialogClick
- uninstallPkg
- 卸载程序 PackageInstaller - UninstallerActivity
- showConfirmationDialog
- 卸载程序 PackageInstaller UninstallAlertDialogFragment
- startUninstallProgress
- UninstallUninstalling
- Framework层- PackageInstallerService
- uninstall
- 二) 命令 adb uninstall 卸载方式
- 三) deletePackage 反射实现卸载app
- 卸载方案小结
- 实现方案避坑
- 总结
参考资料:
android 卸载应用流程
android 应用卸载流程分析
Android PackageManagerService总结(五) APK卸载流程
MTK Android12 安装app添加密码锁限制
实现方案
涉及到修改文件
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
修改方案
在 PackageManagerService 类中的 deletePackageVersioned 方法中进行加锁拦截。 具体修改如下:对应的导入类包 自己导入即可。
@Overridepublic void deletePackageVersioned(VersionedPackage versionedPackage,final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
- deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
+ // deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
+
+ String tName= Thread.currentThread().getName();
+ Slog.w(TAG, "deletePackageVersioned tName:"+tName);
+
+
+ Handler handler = new Handler(Looper.getMainLooper());
+
+
+ final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
+ mLayoutParams.width = 1000;
+ mLayoutParams.height =500;
+ mLayoutParams.dimAmount =0.5f;
+ mLayoutParams.format = PixelFormat.TRANSLUCENT;
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+ WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+
+ final LinearLayout parentLayout = new LinearLayout(mContext);
+ parentLayout.setOrientation(LinearLayout.VERTICAL);
+ parentLayout.setBackgroundColor(Color.WHITE);
+ LinearLayout.LayoutParams layoutParams
+ = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+ parentLayout.setLayoutParams(layoutParams);
+
+ TextView titleText = new TextView(mContext);
+ LinearLayout.LayoutParams contentParams
+ = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ titleText.setLayoutParams(contentParams);
+ titleText.setText("check password");
+ titleText.setTextColor(Color.BLACK);
+ titleText.setTypeface(Typeface.create(titleText.getTypeface(), Typeface.NORMAL), Typeface.BOLD);
+ titleText.setPadding(10, 10, 0, 0);
+ parentLayout.addView(titleText);
+
+ EditText passEdtTxt = new EditText(mContext);
+ passEdtTxt.setLayoutParams(contentParams);
+ passEdtTxt.setHint("Please input password");
+ passEdtTxt.setTextSize(14);
+ passEdtTxt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ passEdtTxt.setTextColor(Color.BLACK);
+
+ parentLayout.addView(passEdtTxt);
+ RelativeLayout reLayout = new RelativeLayout(mContext);
+ RelativeLayout.LayoutParams rightReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ rightReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ rightReal.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
+ rightReal.setMargins(0,10,15,0);
+
+ Button confirmBtn = new Button(mContext);
+ LinearLayout.LayoutParams btnParams
+ = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ confirmBtn.setLayoutParams(btnParams);
+ confirmBtn.setText("ok");
+ confirmBtn.setTextColor(Color.parseColor("#BEBEBE"));
+ confirmBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String password = passEdtTxt.getText().toString();
+ if ("123456".equals(password)) {
+ if (parentLayout!=null){
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.removeViewImmediate(parentLayout);
+ }
+ });
+ }
+ deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
+ } else {
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(mContext,"PassWorld is Error !",Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+ });
+
+ reLayout.addView(confirmBtn, rightReal);
+ RelativeLayout.LayoutParams leftReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ leftReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ leftReal.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
+ leftReal.setMargins(15,10,0,0);
+ Button cancelBtn = new Button(mContext);
+ LinearLayout.LayoutParams cancelbtnParams
+ = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ cancelBtn.setLayoutParams(cancelbtnParams);
+ cancelBtn.setText("cancel");
+ cancelBtn.setTextColor(Color.parseColor("#BEBEBE"));
+
+ cancelBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (parentLayout != null) {
+ Slog.w(TAG, "cancelBtn ");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.removeViewImmediate(parentLayout);
+ }
+ });
+ }
+ }
+ });
+ reLayout.addView(cancelBtn, leftReal);
+ parentLayout.addView(reLayout);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mWindowManager.addView(parentLayout, mLayoutParams);
+ } catch (WindowManager.BadTokenException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+}
实现效果
实际测试,接下来的源码分析当中的三种方案:系统常规进入设置卸载app、adb uninstall +package 命令卸载app、静默卸载app 都可以达到想要的效果,即卸载app 会弹框输入框。输入正确后才会走接下来的卸载app 流程,达到了想要的效果。
源码分析- 卸载方式
卸载有以下几种方式
- 去设置界面 进行卸载
- 命令行 adb uninstall pkg(包名)
- 具备系统签名的应用通过反射 deletePackage 实现卸载App 功能
一) 设置界面进行卸载
InstalledAppDetails
定位界面如下:定位到卸载入口 InstalledAppDetails
DisPlay:/ $ dumpsys activity top | grep ACTIVITYACTIVITY com.mediatek.camera/.CameraLauncher 4dfd640 pid=(not running)ACTIVITY com.android.launcher3/.uioverrides.QuickstepLauncher f787db9 pid=1523ACTIVITY com.android.settings/.applications.InstalledAppDetails b8801c2 pid=3597
看Manifest.xml 定义,它的实际 Activity 是 InstalledAppDetailsTop
<!-- Keep compatibility with old shortcuts. --><activity-alias android:name=".applications.InstalledAppDetails"android:label="@string/application_info_label"android:exported="true"android:targetActivity=".applications.InstalledAppDetailsTop"><intent-filter android:priority="1"><action android:name="android.settings.APPLICATION_DETAILS_SETTINGS" /><action android:name="android.intent.action.AUTO_REVOKE_PERMISSIONS" /><category android:name="android.intent.category.DEFAULT" /><data android:scheme="package" /></intent-filter></activity-alias>
源码如下;很简单的一个Activity,实际UI逻辑都在AppInfoDashboardFragment 中
public class InstalledAppDetailsTop extends SettingsActivity {@Overridepublic Intent getIntent() {Intent modIntent = new Intent(super.getIntent());modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppInfoDashboardFragment.class.getName());return modIntent;}@Overrideprotected boolean isValidFragment(String fragmentName) {return AppInfoDashboardFragment.class.getName().equals(fragmentName);}
}
AppInfoDashboardFragment
在createPreferenceControllers 创建控制器中有这样一段代码,如下:
@Overrideprotected List<AbstractPreferenceController> createPreferenceControllers(Context context) {retrieveAppEntry();..............................// The following are controllers for preferences that don't need to refresh the preference// state when app state changes.mInstantAppButtonPreferenceController =new InstantAppButtonsPreferenceController(context, this, packageName, lifecycle);controllers.add(mInstantAppButtonPreferenceController);mAppButtonsPreferenceController = new AppButtonsPreferenceController((SettingsActivity) getActivity(), this, lifecycle, packageName, mState,REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);............... controllers.add(mAppButtonsPreferenceController);controllers.add(new AppBatteryPreferenceController(context, this, packageName, getUid(), lifecycle));controllers.add(new AppMemoryPreferenceController(context, this, lifecycle));controllers.add(new DefaultHomeShortcutPreferenceController(context, packageName));controllers.add(new DefaultBrowserShortcutPreferenceController(context, packageName));controllers.add(new DefaultPhoneShortcutPreferenceController(context, packageName));controllers.add(new DefaultEmergencyShortcutPreferenceController(context, packageName));controllers.add(new DefaultSmsShortcutPreferenceController(context, packageName));return controllers;}
所以控制器大概就是 AppButtonsPreferenceController
AppButtonsPreferenceController
handleDialogClick
看源码
public void handleDialogClick(int id) {switch (id) {case ButtonActionDialogFragment.DialogType.DISABLE:mMetricsFeatureProvider.action(mActivity,SettingsEnums.ACTION_SETTINGS_DISABLE_APP);AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName,PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));break;case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE:mMetricsFeatureProvider.action(mActivity,SettingsEnums.ACTION_SETTINGS_DISABLE_APP);uninstallPkg(mAppEntry.info.packageName, false, true);break;case ButtonActionDialogFragment.DialogType.FORCE_STOP:forceStopPackage(mAppEntry.info.packageName);break;}}
这里 uninstallPkg forceStopPackage 对应的不就是界面上 卸载、停止 app 对应的操作嘛
卸载点击后弹出的界面如下
uninstallPkg
看源码
void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {stopListeningToPackageRemove();// Create new intent to launch Uninstaller activityUri packageUri = Uri.parse("package:" + packageName);Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);mMetricsFeatureProvider.action(mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP);mFragment.startActivityForResult(uninstallIntent, mRequestUninstall);mDisableAfterUninstall = andDisable;}
这里发送了一个广播 ACTION_UNINSTALL_PACKAGE
/*** Activity Action: Launch application uninstaller.* <p>* Input: The data must be a package: URI whose scheme specific part is* the package name of the current installed package to be uninstalled.* You can optionally supply {@link #EXTRA_RETURN_RESULT}.* <p>* Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the uninstall* succeeded.* <p>* Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}* since {@link Build.VERSION_CODES#P}.** @deprecated Use {@link android.content.pm.PackageInstaller#uninstall(String, IntentSender)}* instead*/@Deprecated@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
看方法注释,就是拉起 uninstaller app,进行卸载操作。 那就需要分析 PackageInstaller 应用
卸载程序 PackageInstaller - UninstallerActivity
看Manifest 配置
<activity android:name=".UninstallerActivity"android:configChanges="orientation|keyboardHidden|screenSize"android:theme="@style/Theme.AlertDialogActivity.NoActionBar"android:excludeFromRecents="true"android:noHistory="true"android:exported="true"><intent-filter android:priority="1"><action android:name="android.intent.action.DELETE" /><action android:name="android.intent.action.UNINSTALL_PACKAGE" /><category android:name="android.intent.category.DEFAULT" /><data android:scheme="package" /></intent-filter></activity>
类注释如下:
/** This activity presents UI to uninstall an application. Usually launched with intent* Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute* com.android.packageinstaller.PackageName set to the application package name*/
public class UninstallerActivity extends Activity {
展示卸载的UI
showConfirmationDialog
最终会调用一个Fragment 来显示 卸载界面展示和业务
private void showConfirmationDialog() {if (isTv()) {showContentFragment(new UninstallAlertFragment(), 0, 0);} else {showDialogFragment(new UninstallAlertDialogFragment(), 0, 0);}}
这里举例一个来说明 UninstallAlertDialogFragment
卸载程序 PackageInstaller UninstallAlertDialogFragment
分析源码就是一个弹框,点击事件如下:
@Overridepublic void onClick(DialogInterface dialog, int which) {if (which == Dialog.BUTTON_POSITIVE) {((UninstallerActivity) getActivity()).startUninstallProgress(mKeepData != null && mKeepData.isChecked());} else {((UninstallerActivity) getActivity()).dispatchAborted();}}
反向调用到了Activity 里面的 startUninstallProgress 方法。
startUninstallProgress
直接看源码
public void startUninstallProgress(boolean keepData) {boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());if (isTv()) {Intent newIntent = new Intent(Intent.ACTION_VIEW);newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);if (returnResult) {newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);}newIntent.setClass(this, UninstallAppProgress.class);startActivity(newIntent);} else if (returnResult || mDialogInfo.callback != null || getCallingActivity() != null) {Intent newIntent = new Intent(this, UninstallUninstalling.class);newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);if (returnResult) {newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);}if (returnResult || getCallingActivity() != null) {newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);}startActivity(newIntent);} else {..........}}
这里又是跳转界面,传递参数 Intent newIntent = new Intent(this, UninstallUninstalling.class);
UninstallUninstalling
先看类注释:就是展示一个卸载的弹框
/*** Start an uninstallation, show a dialog while uninstalling and return result to the caller.*/
public class UninstallUninstalling extends Activity implements
在onCreate 里面我们看到了这样一段卸载内容,如下:
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setFinishOnTouchOutside(false);......................try {if (savedInstanceState == null) {.....................Intent broadcastIntent = new Intent(BROADCAST_ACTION);broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);broadcastIntent.setPackage(getPackageName());PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId,broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT| PendingIntent.FLAG_MUTABLE);int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;try {ActivityThread.getPackageManager().getPackageInstaller().uninstall(new VersionedPackage(mAppInfo.packageName,PackageManager.VERSION_CODE_HIGHEST),getPackageName(), flags, pendingIntent.getIntentSender(),user.getIdentifier());} catch (RemoteException e) {e.rethrowFromSystemServer();}} else {mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);UninstallEventReceiver.addObserver(this, mUninstallId, this);}} catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) {Log.e(LOG_TAG, "Fails to start uninstall", e);onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,null);}}
用到了卸载的核心方法:
ActivityThread.getPackageManager().getPackageInstaller().uninstall(new VersionedPackage(mAppInfo.packageName,PackageManager.VERSION_CODE_HIGHEST),getPackageName(), flags, pendingIntent.getIntentSender(),user.getIdentifier());
ActivityThread.getPackageManager().getPackageInstaller() 又是什么呢?
getPackageManager 指向的应该是 PackageInstaller
根据经验 就要去ApplicationPackageManager 里面找 这个getPackageInstaller 方法了,去看看:
@Overridepublic PackageInstaller getPackageInstaller() {synchronized (mLock) {if (mInstaller == null) {try {mInstaller = new PackageInstaller(mPM.getPackageInstaller(),mContext.getPackageName(), mContext.getAttributionTag(), getUserId());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return mInstaller;}}
mPM.getPackageInstaller() 获取的 IPackageInstaller实例的封装,而 IPackageInstaller 在系统服务端的具体实现是 PackageInstallerService。
Framework层- PackageInstallerService
路径:/frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
uninstall
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,IntentSender statusReceiver, int userId) {final int callingUid = Binder.getCallingUid();mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {mAppOps.checkPackage(callingUid, callerPackageName);}// Check whether the caller is device owner or affiliated profile owner, in which case we do// it silently.DevicePolicyManagerInternal dpmi =LocalServices.getService(DevicePolicyManagerInternal.class);final boolean canSilentlyInstallPackage =dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid);final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,statusReceiver, versionedPackage.getPackageName(),canSilentlyInstallPackage, userId);if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)== PackageManager.PERMISSION_GRANTED) {// Sweet, call straight through!mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);} else if (canSilentlyInstallPackage) {// Allow the device owner and affiliated profile owner to silently delete packages// Need to clear the calling identity to get DELETE_PACKAGES permissionfinal long ident = Binder.clearCallingIdentity();try {mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);} finally {Binder.restoreCallingIdentity(ident);}DevicePolicyEventLogger.createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE).setAdmin(callerPackageName).write();} else {ApplicationInfo appInfo = mPm.getApplicationInfo(callerPackageName, 0, userId);if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,null);}// Take a short detour to confirm with userfinal Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());adapter.onUserActionRequired(intent);}}
最终调用的是PackageManagerService deletePackageVersioned 方法
@Overridepublic void deletePackageVersioned(VersionedPackage versionedPackage,final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);}
二) 命令 adb uninstall 卸载方式
这里暂不讲解,实际客需过程中,客户使用工程中不会涉及到这个需求的。如果硬是需要这个功能,防止 adb 卸载,一般都会直接屏蔽掉adb 功能。 实则保护的就不仅仅是卸载这个业务了。
三) deletePackage 反射实现卸载app
关联到的源码类:
/frameworks/base/core/java/android/content/pm/PackageManager.java
/frameworks/base/core/java/android/app/ContextImpl.java
/frameworks/base/core/java/android/app/ApplicationPackageManager.java
如下常用的反射代码 应用端实现:
private fun deleteByProxy(packageName: String) {try {val context = ContextProvider.get().applicationContextval pkgManager = context.packageManagerval c = pkgManager.javaClassval p2 = Class.forName("android.content.pm.IPackageDeleteObserver")//不能用Int.javaClass,必须用Int::class.javaPrimitiveTypeval method =c.getMethod("deletePackage", *arrayOf(String::class.java, p2, Int::class.javaPrimitiveType))method.invoke(pkgManager, packageName, null, 0)} catch (e: Exception) {e.printStackTrace()}}
反射通过调用 PackageManager 类的 deletePackage 方法:如下源码
类声明如下:
/*** Class for retrieving various kinds of information related to the application* packages that are currently installed on the device.** You can find this class through {@link Context#getPackageManager}.** <p class="note"><strong>Note: </strong>If your app targets Android 11 (API level 30) or* higher, the methods in this class each return a filtered list of apps. Learn more about how to* <a href="/training/basics/intents/package-visibility">manage package visibility</a>.* </p>*/
public abstract class PackageManager {
deletePackage 方法如下:
/*** Attempts to delete a package. Since this may take a little while, the* result will be posted back to the given observer. A deletion will fail if* the calling context lacks the* {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the* named package cannot be found, or if the named package is a system* package.** @param packageName The name of the package to delete* @param observer An observer callback to get notified when the package* deletion is complete.* {@link android.content.pm.IPackageDeleteObserver#packageDeleted}* will be called when that happens. observer may be null to* indicate that no callback is desired.* @hide*/@SuppressWarnings("HiddenAbstractMethod")@RequiresPermission(Manifest.permission.DELETE_PACKAGES)@UnsupportedAppUsagepublic abstract void deletePackage(@NonNull String packageName,@Nullable IPackageDeleteObserver observer, @DeleteFlags int flags);
我们发现反射调用的是PackageManager类的抽象方法 deletePackage, 并且是一个 @hide 标志的隐藏方法。
再分析下 context.packageManager,方法:
getPackageManager()函数的实现在ContextImpl.java , 那么真实的PackageManager 的 子类实现就是在这个ContextImpl 中定义的。
ContextImpl getPackageManager 方法@Overridepublic PackageManager getPackageManager() {if (mPackageManager != null) {return mPackageManager;}final IPackageManager pm = ActivityThread.getPackageManager();if (pm != null) {// Doesn't matter if we make more than one instance.return (mPackageManager = new ApplicationPackageManager(this, pm));}return null;}
ApplicationPackageManager
看类定义:
/** @hide */
public class ApplicationPackageManager extends PackageManager {private static final String TAG = "ApplicationPackageManager"; 果然是 PackageManager 类的子类,实现 上面 deletePackage 方法@Override@UnsupportedAppUsagepublic void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {deletePackageAsUser(packageName, observer, flags, getUserId());}@Overridepublic void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,int flags, int userId) {try {mPM.deletePackageAsUser(packageName, VERSION_CODE_HIGHEST,observer, userId, flags);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}@UnsupportedAppUsageprotected ApplicationPackageManager(ContextImpl context, IPackageManager pm) {mContext = context;mPM = pm;}
这里不再详细追寻 PM 逻辑了,一看就是最终调用到 PackageManagerService 类的deletePackageAsUser 方法了。
PackageManagerService deletePackageAsUser 方法
@Overridepublic void deletePackageAsUser(String packageName, int versionCode,IPackageDeleteObserver observer, int userId, int flags) {Slog.w(TAG, "deletePackageAsUser packageName:"+packageName);deletePackageVersioned(new VersionedPackage(packageName, versionCode),new LegacyPackageDeleteObserver(observer).getBinder(), userId, flags);}
卸载方案小结
上面源码分析,无论是通过反射静默卸载还是设置界面进行卸载 最终调用到PMS的 deletePackageVersioned方法,我们一步一步分析了整个流程。需要处理的就是在 deletePackageVersioned 方法中实现拦截的方法。
实现方案避坑
- 无论何种方案,卸载回调拦截的方法deletePackageVersioned 是 Binder 机制的回调方法,并不是UI线程,所以处理 UI 时候需要回到主线程
- 卸载和安装存在一些公共的区域,比如安装更新app时候也有卸载过程,记得验证是否OK,不要冲突导致安装更新app不成功
- 上面分析了卸载流程走到了 卸载器 PackageInstaller 中 UninstallUninstalling 卸载并监听进度显示UI,那么拦截后 取消过程就需要自行处理下,不然会有个安装弹框一直显示,可以用广播或者系统自带回调实现。 如下这个弹框
总结
- 多分析源码,看流程看业务
- 卸载加锁和安装加锁,在 demo 中的 UI一样的,可以参考下:MTK Android12 安装app添加密码锁限制
- PMS 本身功能比较多,代码量大,多打日志看流程。 用 IDE 开发工具查看源码,提高代码阅读效率