MTK Android12-13 App卸载加锁

实现:App 卸载时候需要加一层拦截锁,客户输入密码后才能正常卸载


参考资料:

android 卸载应用流程
android 应用卸载流程分析
Android PackageManagerService总结(五) APK卸载流程
MTK Android12 安装app添加密码锁限制

实现方案

涉及到修改文件


/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

修改方案

在 PackageManagerService 类中的 deletePackageVersioned 方法中进行加锁拦截。 具体修改如下:对应的导入类包 自己导入即可。

     @Override
     public 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 ACTIVITY
  ACTIVITY com.mediatek.camera/.CameraLauncher 4dfd640 pid=(not running)
  ACTIVITY com.android.launcher3/.uioverrides.QuickstepLauncher f787db9 pid=1523
  ACTIVITY 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 {

    @Override
    public Intent getIntent() {
        Intent modIntent = new Intent(super.getIntent());
        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppInfoDashboardFragment.class.getName());
        return modIntent;
    }

    @Override
    protected boolean isValidFragment(String fragmentName) {
        return AppInfoDashboardFragment.class.getName().equals(fragmentName);
    }
}

AppInfoDashboardFragment

在createPreferenceControllers 创建控制器中有这样一段代码,如下:

@Override
    protected 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 activity
        Uri 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

分析源码就是一个弹框,点击事件如下:

    @Override
    public 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 里面我们看到了这样一段卸载内容,如下:

@Override
    protected 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 方法了,去看看:

    @Override
    public 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 permission
            final 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 user
            final 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 方法

   @Override
    public 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().applicationContext
            val pkgManager = context.packageManager
            val c = pkgManager.javaClass
            val p2 = Class.forName("android.content.pm.IPackageDeleteObserver")
            //不能用Int.javaClass,必须用Int::class.javaPrimitiveType
            val 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)
    @UnsupportedAppUsage
    public abstract void deletePackage(@NonNull String packageName,
            @Nullable IPackageDeleteObserver observer, @DeleteFlags int flags);

我们发现反射调用的是PackageManager类的抽象方法 deletePackage, 并且是一个 @hide 标志的隐藏方法。

再分析下 context.packageManager,方法:
getPackageManager()函数的实现在ContextImpl.java , 那么真实的PackageManager 的 子类实现就是在这个ContextImpl 中定义的。

ContextImpl  getPackageManager 方法

   @Override
    public 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
    @UnsupportedAppUsage
    public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
        deletePackageAsUser(packageName, observer, flags, getUserId());
    }

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


  @UnsupportedAppUsage
    protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) {
        mContext = context;
        mPM = pm;
    }

这里不再详细追寻 PM 逻辑了,一看就是最终调用到 PackageManagerService 类的deletePackageAsUser 方法了。
PackageManagerService deletePackageAsUser 方法

    @Override
    public 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 开发工具查看源码,提高代码阅读效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值