当前位置: 首页 > news >正文

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

相关文章:

  • 基于 Java 的实现前端组装查询语句,后端直接执行查询方案,涵盖前端和后端的设计思路
  • 如何搭建spark yarn 模式的集群集群
  • java 和 C#操作数据库对比
  • Web基础和HTTP协议
  • kvm学习小结
  • 计算机视觉——通过 OWL-ViT 实现开放词汇对象检测
  • Java垃圾收集器与内存分配策略深度解析
  • php数据库连接
  • Linux常见基础命令
  • Leetcode - 双周赛155
  • 超级好用的​​参数化3D CAD 建模​​图形库 (CadQuery库介绍)
  • 数字孪生的浪潮:从虚拟镜像到现实世界的 IT 变革
  • Rust 学习笔记:编程练习(一)
  • 计算机基础—(九道题)
  • 24体育NBA足球直播M28模板体育赛事直播源码
  • Rmarkdown输出为pdf的方法与问题解决
  • 从代码学习机器学习 - UMAP降维算法 scikit-learn版
  • Android 消息队列之MQTT的使用(二):会话+消息过期机制,设备远程控制,批量控制实现
  • JavaScript高级进阶(四)
  • Crusader Kings III 王国风云 3(十字军之王 3) [DLC 解锁] [Steam] [Windows SteamOS macOS]
  • 幸福航空取消“五一”前航班,财务人员透露“没钱飞了”
  • 澎湃思想周报丨数字时代的育儿;凛冬已至好莱坞
  • 加拿大温哥华一车辆冲撞人群,造成多人伤亡
  • 快捷公寓单间不足5平方米?公寓方:预订平台图片只是参考,已退房款
  • 来论|这无非就是一只“纸老虎”:评特朗普政府“关税战”
  • 财政部:前3月国有企业利润总额10907.4亿元,同比增1.7%