Android Framework定制卸载确认窗口

文章目录

卸载时弹窗源码梳理

弹窗定位

卸载应用时,会弹窗询问是否要卸载,如下图

要定位此UI代码,可以通过查找提示的文本找到对应的xml及代码

复制代码
find frameworks/base/ -name   "*.xml" | xargs grep "要卸载此应用吗"
frameworks/base/packages/PackageInstaller/res/values-zh-rCN/strings.xml:    <string name="uninstall_application_text" msgid="3816830743706143980">"要卸载此应用吗?"</string>

从上面查找到"要卸载此应用吗"对应的id是uninstall_application_text,再查找这个id

复制代码
find frameworks/base/ -name   "*.java" | xargs grep "uninstall_application_text"
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java:                messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java:                        getString(R.string.uninstall_application_text_user, userInfo.name));
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java:                messageBuilder.append(getString(R.string.uninstall_application_text));
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java:                messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java:                        getString(R.string.uninstall_application_text_user, userInfo.name));
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java:                messageBuilder.append(getString(R.string.uninstall_application_text));

上面找到两种java代码,UninstallAlertFragment与UninstallAlertDialogFragment,第一个是tv电视应用才用到的,显然第二个是要找的java代码。

UninstallAlertDialogFragment

来看下java代码:

frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java

java 复制代码
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // 获取系统的包管理器,用于获取应用相关信息(如应用名称、图标等)
    final PackageManager pm = getActivity().getPackageManager();
    
    // 从宿主Activity(UninstallerActivity)中获取卸载弹窗所需的核心信息对象
    // DialogInfo包含了要卸载的应用信息、用户信息、是否跨用户卸载等关键数据
    final UninstallerActivity.DialogInfo dialogInfo =
            ((UninstallerActivity) getActivity()).getDialogInfo();
    
    // 加载并获取要卸载应用的安全标签(应用名称),loadSafeLabel是安全的标签加载方法,避免空指针
    final CharSequence appLabel = dialogInfo.appInfo.loadSafeLabel(pm);
    
    // 创建AlertDialog构建器,用于构建卸载确认弹窗
    AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
    
    // 构建弹窗的提示消息内容,使用StringBuilder提升字符串拼接效率
    StringBuilder messageBuilder = new StringBuilder();

    // 如果存在Activity信息(说明是从某个Activity触发的卸载)
    if (dialogInfo.activityInfo != null) {
        // 获取该Activity的显示名称
        final CharSequence activityLabel = dialogInfo.activityInfo.loadSafeLabel(pm);
        
        // 如果Activity的名称和应用名称不一致,需要明确告知用户该Activity所属的应用
        if (!activityLabel.equals(appLabel)) {
            // 拼接提示文本:说明该Activity属于要卸载的应用
            messageBuilder.append(
                    getString(R.string.uninstall_activity_text, activityLabel));
            messageBuilder.append(" ").append(appLabel).append(".\n\n");
        }
    }

    // 判断当前要卸载的应用是否是"更新的系统应用"
    // FLAG_UPDATED_SYSTEM_APP:标记该系统应用被用户更新过,卸载只会移除更新部分,恢复到出厂版本
    final boolean isUpdate =
            ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
    
    // 获取用户管理服务,用于处理多用户场景下的卸载逻辑
    UserManager userManager = UserManager.get(getActivity());
    
    // 根据应用类型(更新的系统应用/普通应用)和用户场景拼接不同的提示文本
    if (isUpdate) {
        // 卸载更新的系统应用场景
        if (isSingleUser(userManager)) {
            // 单用户设备:使用单用户版卸载更新提示文本
            messageBuilder.append(getString(R.string.uninstall_update_text));
        } else {
            // 多用户设备:使用多用户版卸载更新提示文本
            messageBuilder.append(getString(R.string.uninstall_update_text_multiuser));
        }
    } else {
        // 卸载普通应用场景
        if (dialogInfo.allUsers && !isSingleUser(userManager)) {
            // 跨所有用户卸载应用:使用全用户卸载提示文本
            messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
        } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
            // 卸载其他用户下的应用:获取目标用户信息,并拼接该用户专属的卸载提示
            UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
            messageBuilder.append(
                    getString(R.string.uninstall_application_text_user, userInfo.name));
        } else {
            // 卸载当前用户的普通应用:使用默认卸载提示文本
            messageBuilder.append(getString(R.string.uninstall_application_text));
        }
    }

    // 设置弹窗标题为要卸载的应用名称
    dialogBuilder.setTitle(appLabel);
    // 设置弹窗确认按钮(OK),并将当前Fragment设为点击事件处理者
    dialogBuilder.setPositiveButton(android.R.string.ok, this);
    // 设置弹窗取消按钮(Cancel),并将当前Fragment设为点击事件处理者
    dialogBuilder.setNegativeButton(android.R.string.cancel, this);

    // 获取要卸载应用的包名(唯一标识)
    String pkg = dialogInfo.appInfo.packageName;

    // 标记是否建议保留应用数据(根据应用是否标记有易失性用户数据)
    boolean suggestToKeepAppData;
    try {
        // 获取应用的包信息(包含应用的各种配置信息)
        PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0);
        // 判断应用是否标记了"有易失性用户数据",这类应用卸载时建议保留数据
        suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
    } catch (PackageManager.NameNotFoundException e) {
        // 捕获包未找到异常(应用可能已被卸载),打印错误日志
        Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e);
        // 异常时默认不建议保留数据
        suggestToKeepAppData = false;
    }

    // 应用数据大小,初始化为0
    long appDataSize = 0;
    // 如果建议保留应用数据,则计算该应用的数据大小
    if (suggestToKeepAppData) {
        // 获取应用数据大小(根据是否跨用户卸载,传入不同的用户参数)
        appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user);
    }

    // 根据应用数据大小决定弹窗的布局样式
    if (appDataSize == 0) {
        // 应用无数据/数据大小为0:直接设置文本消息
        dialogBuilder.setMessage(messageBuilder.toString());
    } else {
        // 应用有数据:加载自定义布局,显示保留数据的勾选框
        
        // 获取布局填充器,用于加载自定义布局文件
        LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class);
        // 加载卸载弹窗的自定义布局(uninstall_content_view.xml)
        ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null);

        // 设置自定义布局中的提示消息文本
        ((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString());
        // 获取"保留数据"的复选框控件
        mKeepData = content.requireViewById(R.id.keepData);
        // 显示保留数据复选框
        mKeepData.setVisibility(View.VISIBLE);
        // 设置复选框文本:提示用户可以保留指定大小的应用数据
        mKeepData.setText(getString(R.string.uninstall_keep_data,
                formatFileSize(getContext(), appDataSize)));

        // 将自定义布局设置为弹窗的内容视图
        dialogBuilder.setView(content);
    }

    // 构建并返回最终的AlertDialog弹窗
    return dialogBuilder.create();
}

找到这个卸载确认窗口是由onCreateDialog函数创建的AlertDialog对象。

  1. 核心功能:该方法的核心是构建 Android 应用卸载确认弹窗,根据应用类型(系统更新应用 / 普通应用)、用户场景(单用户 / 多用户 / 其他用户)和应用数据情况,动态生成不同的弹窗内容和布局。
  2. 关键逻辑分支:
  • 区分「更新的系统应用」和「普通应用」,展示不同的卸载提示文本;
  • 区分「单用户」「多用户」「其他用户」场景,适配不同的卸载文案;
  • 检测应用是否有易失性用户数据,如有则显示「保留数据」复选框并展示数据大小。

UninstallerActivity

UninstallAlertDialogFragment是由UninstallerActivity调用的

  • UninstallerActivity 是宿主 / 发起者:负责创建并弹出 UninstallAlertDialogFragment 这个弹窗碎片;

  • UninstallAlertDialogFragment 是依赖方 / 执行者:依赖 UninstallerActivity 提供的卸载相关数据(DialogInfo),并将用户的操作(确认 / 取消卸载)回调给 UninstallerActivity。

    frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java

showConfirmationDialog函数会创建UninstallAlertDialogFragment

java 复制代码
    @Override
    public void onCreate(Bundle icicle) {
        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
        // 其它代码省略......
        showConfirmationDialog(); // 这一行调用的弹窗
    }   

二者的关系:

  1. UninstallerActivity 主动创建并显示 UninstallAlertDialogFragment
  2. UninstallAlertDialogFragment 依赖 UninstallerActivity 获取数据
  3. UninstallAlertDialogFragment 将用户操作回调给 UninstallerActivity

实现卸载时不弹窗

修改UninstallerActivity代码,把showConfirmationDialog注释掉并进行卸载

java 复制代码
        //showConfirmationDialog(); // 注释掉这行
        // 添加以下两行
        startUninstallProgress(false);
        finish();

Android相关专栏

Android Framework专栏

Android专栏

作者:帅得不敢出门

相关推荐
Android-Flutter2 小时前
Compose 什么叫重组?怎么样触发重组?
android·compose
硫酸锌012 小时前
安卓手机连接Windows电脑后通过DHCP服务为手机分配IP地址
android·windows·计算机网络
骑着蜗牛撵大象3272 小时前
设备监控数据存与查:Spring Boot 接入 TimescaleDB 超表的完整落地
android·spring boot·后端·springboot·时序数据库·es·设备监控
云贝贝贝3 小时前
MySQL 线上报 Lock wait timeout 别慌:6 招定位锁等待与死锁
android
mmsx3 小时前
Android 签名校验为什么能拦住二次打包:指纹与完整性校验
android·签名
jason.zeng@15022074 小时前
使用vscode+flutter开发android程序(入门)
android·vscode·flutter
事圆则缓4 小时前
Kotlin学习导航:从知识树走到项目与面试
android·开发语言·kotlin
天空之城--4 小时前
近一周Android开发动态:AI编码、Compose与自适应实践
android