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.javauninstall
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 开发工具查看源码,提高代码阅读效率
