android框架层弹出对话框的分析

在android中,如果想对第三方应用的某些操作弹出窗口提醒用户,由用户决策是否允许进行后续的操作,就需要在框架层进行代码设计和实现。本文以打开WIFI时的弹窗提醒为示例进行分析。

1,在打开WIFI时,代码逻辑会走到frameworks下的 WifiServiceImpl.java 中的

public synchronized boolean setWifiEnabled(String packageName, boolean enable) {...} 函数。 可以在这个函数中当enable为true时进行弹窗显示。

scss 复制代码
        if (enable) {
            int uid = Binder.getCallingUid(); // 获取调用方的 UID
            // 判断 UID 是否属于「第三方应用进程」
            if (uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID) {
                return checkUserConfirmation(packageName, uid);
            }
        }

在上面的代码中,Binder 是 Android 跨进程通信(IPC)的核心机制,所有系统服务(如 WifiManager)的跨进程调用都通过 Binder 实现。

getCallingUid():获取「发起当前 Binder 调用的进程的 UID」(UID = User ID,是 Android 中进程 / 应用的唯一身份标识)。

关键意义:系统服务通过这个 UID 识别「谁在调用我」,从而判断是否允许其执行敏感操作(如启用 WiFi)。

注意:仅在 Binder 线程(系统服务的 IPC 处理线程)中有效,普通线程调用会返回 Process.myUid()(当前进程自身的 UID)。

2,具体的函数如下:

java 复制代码
// 初始化计数器=1(等待1个事件:权限结果返回),初始化计数器count必须 ≥ 0,代表需要等待的 "任务数量"。
// count=1意味着:只需等待 1 个事件完成(权限申请结果返回),阻塞线程就会被唤醒。
private CountDownLatch mPermissionLatch = new CountDownLatch(1);
    private boolean mUserGranted = false;
    private BroadcastReceiver mPermissionReceiver;
    private void registerPermissionReceiver() {
        mPermissionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if ("com.android.systemui.action.WIFI_ENABLE_PERMISSION".equals(intent.getAction())) {
                    mUserGranted = intent.getBooleanExtra("granted", false);
                    // 计数器减1(事件完成)
                    mPermissionLatch.countDown();
                }
            }
        };
        IntentFilter filter = new IntentFilter("com.android.systemui.action.WIFI_ENABLE_PERMISSION");
        mContext.registerReceiver(mPermissionReceiver, filter);
    }

    private void unregisterPermissionReceiver() {
        if (mPermissionReceiver != null) {
            try {
                mContext.unregisterReceiver(mPermissionReceiver);
                mPermissionReceiver = null;
            } catch (IllegalArgumentException e) {
                ...
            }
        }
    }
    // synchronized同步方法,避免多线程同时发起权限校验(防止重复弹窗、状态错乱)。
    private synchronized boolean checkUserConfirmation(String pkgName, int uid) {
        // 通过 AppOps(Android 应用操作权限管控框架)检查应用是否已被授予 "修改 Wifi 状态" 的权限。
        int appOpsMode = mAppOps.noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, pkgName);
        if (appOpsMode == AppOpsManager.MODE_ALLOWED) {
             return true;
        }
        try {
            registerPermissionReceiver();
            mUserGranted = false;
            //重新创建 CountDownLatch 实例(计数器 = 1):因 CountDownLatch 计数器不可重置,每次请求需新建实例,确保阻塞逻辑有效。
            //目的:让当前线程阻塞,等待用户在弹窗中做出决策(计数器减为 0 后唤醒)。
            mPermissionLatch = new CountDownLatch(1);
            Intent dialogIntent = new Intent("com.android.systemui.action.REQUEST_WIFI_ENABLE_PERMISSION");
            dialogIntent.setComponent(new ComponentName("com.android.systemui", "com.android.systemui.wifi.WifiEnableActivity"));
            dialogIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName);
            dialogIntent.putExtra(Intent.EXTRA_UID, uid);
            // 当前方法运行在系统服务进程(如 wifi 进程),而非 Activity 栈中,启动 Activity 必须指定 NEW_TASK 标记,否则会抛出 ActivityNotFoundException。
            dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            int originalCallingUid = Binder.getCallingUid();
            // 切换为系统身份启动弹框。避免权限检验失败。
            // clearCallingIdentity():将当前 Binder 调用的身份重置为 "系统进程"(Process.SYSTEM_UID),返回原始身份的令牌(originalIdentity)。
            // 目的:避免启动系统弹窗时触发权限校验失败。系统弹窗的类文件是系统级组件,第三方应用 UID 直接启动会被权限拦截,切换为系统身份后即可正常启动。
            long originalIdentity = Binder.clearCallingIdentity();
            try {
                // 以 "系统用户" 身份启动弹窗(UserHandle.getUserHandleForUid(Process.SYSTEM_UID)),确保弹窗拥有系统级权限,正常显示。
                mContext.startActivityAsUser(dialogIntent, UserHandle.getUserHandleForUid(Process.SYSTEM_UID));
            } catch (Exception e) {
            ...
            }  finally {
                // 必须恢复原始 Binder 身份(restoreCallingIdentity(originalIdentity)),否则会导致后续 IPC 调用身份错乱(比如其他操作误以系统身份执行,引发安全风险)。
                Binder.restoreCallingIdentity(originalIdentity);
            }
            // 当前线程(系统 Wifi 服务线程)阻塞,最多等待 30 秒。
            // 唤醒条件:用户点击弹窗的 "允许" 或 "拒绝",广播接收器mPermissionResultReceiver 收到结果,调用 mPermissionLatch.countDown()(计数器从 1→0),await() 返回 true。15 秒内未收到结果(用户未操作),await() 返回 false(超时)。
            boolean waitSuccess = mPermissionLatch.await(15, TimeUnit.SECONDS);
            if (!waitSuccess) {
                return false;
            }

            int result = mAppOps.noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, pkgName);
            if (mUserGranted) {
                long ident = Binder.clearCallingIdentity();
                try {
                    // mSettingsStore:Wifi 状态存储管理类,handleWifiToggled(true) 用于更新 Wifi 启用状态(写入系统设置,同步到硬件和其他组件)。
                    if (mSettingsStore.handleWifiToggled(true)) {
                        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
                            mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
                        }
                        mWifiMetrics.incrementNumWifiToggles(false, true);
                        mActiveModeWarden.wifiToggled();
                        return true;
                    }
                    return false;
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            } else {
                return false;
            }
        } catch (Exception e) {
            return false;
        } finally {
            mUserGranted = false;
            unregisterPermissionReceiver();
        }
    }

上面的代码中,CountDownLatch 是 Java java.util.concurrent 包下的 同步工具类,核心作用是:让 1 个或多个线程等待,直到其他线程完成指定数量的任务后,再继续执行。

CountDownLatch 的核心特性如下:

计数器不可重置:一旦 countDown() 使计数器减到 0,后续再调用 countDown() 不会有任何效果,await() 也会立即返回。代码中若需 重复申请权限,需重新创建 CountDownLatch 实例(不能复用已减到 0 的对象)。

阻塞线程的风险:若广播接收器未收到结果(如广播未注册、countDown() 未被调用),await() 会一直阻塞当前线程(可能导致 ANR)。

解决方案:必须使用带超时的 await(long, TimeUnit),避免无限阻塞。

用 CountDownLatch 将异步流程转换为同步阻塞,方便后续逻辑按 "申请 → 等待结果 → 处理" 的顺序执行; 其中 CountDownLatch 的核心价值是 解决 "线程等待多个任务完成" 的同步问题,在 Android 权限申请、网络请求回调、多线程任务协同等场景中广泛应用。

3,具体的弹窗页面如下:

ini 复制代码
// AlertActivity 是 Android 系统提供的弹窗基类,内置 AlertController 用于快速构建标准弹窗(标题、内容、按钮、图标等),无需自定义布局。
public class WifiEnableActivity extends AlertActivity
        implements DialogInterface.OnClickListener {
    private static final String TAG = "WifiEnableActivity";

    private String mPkgName;
    private int mUid;
    private AppOpsManager mAppOps;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAppOps = getSystemService(AppOpsManager.class);
        Intent intent = getIntent(); // 获取启动当前Activity的Intent(携带请求参数)
        
        mPkgName = intent.getStringExtra("package");
        if (mPkgName == null) {
            mPkgName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
        }
        
        mUid = intent.getIntExtra("uid", -1);
        if (mUid == -1) {
            mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
        }

        if (mPkgName == null || mUid == -1) {
            sendResult(false);
            finish();
            return;
        }

        PackageManager packageManager = getPackageManager();
        ApplicationInfo aInfo;
        try {
            // 根据包名和 UID 获取应用的 ApplicationInfo(包含应用名称、图标、版本等信息),UserHandle 确保跨用户场景下能正确查询。
            aInfo = packageManager.getApplicationInfoAsUser(mPkgName, 0, UserHandle.getUserHandleForUid(mUid));
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Package not found: " + mPkgName, e);
            sendResult(false);
            finish();
            return;
        }

        String appName = aInfo.loadLabel(packageManager).toString();
        final AlertController.AlertParams ap = mAlertParams;
        ap.mTitle = getString(R.string.wifi_enable_title);
        ap.mMessage = getString(R.string.wifi_enable_msg, appName);
        ap.mPositiveButtonText = getString(android.R.string.ok);
        ap.mNegativeButtonText = getString(android.R.string.cancel);
        ap.mPositiveButtonListener = this;
        ap.mNegativeButtonListener = this;
        try {
            Drawable appIcon = packageManager.getApplicationIcon(aInfo);
            ap.mIcon = appIcon;
        } catch (Exception e) {
            ap.mIconId = android.R.drawable.ic_menu_info_details;
        }
        ap.mForceInverseBackground = true;
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        // AlertActivity 的核心方法,根据 mAlertParams 构建弹窗并显示在屏幕上。
        setupAlert();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        boolean allow = (which == DialogInterface.BUTTON_POSITIVE);
        mAppOps.setMode(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
        sendResult(allow);
        finish();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void sendResult(boolean granted) {
        try {
            Intent resultIntent = new Intent("com.android.systemui.action.WIFI_ENABLE_PERMISSION");
            resultIntent.putExtra("granted", granted);
            resultIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
            resultIntent.putExtra(Intent.EXTRA_UID, mUid);
            // 向请求应用所属的用户发送广播(跨用户场景下,确保广播能被正确接收,避免用户隔离导致的通信失败)。
            sendBroadcastAsUser(resultIntent, UserHandle.getUserHandleForUid(mUid));
        } catch (Exception e) {
            Log.e(TAG, "Failed to send user choice result", e);
        }
    }

}

//在SystemUI的AndroidManifest.xml中对该类声明时要加上下面的代码
android:export=true

上面的代码,完整闭环如下:

WifiServiceImpl 发起权限请求 → 启动 WifiEnableActivity(传递包名、UID)。 WifiEnableActivity 显示弹窗 → 用户点击 "确认"/"取消"。

WifiEnableActivity 调用 sendResult(allow) → 发送广播(Action:WIFI_ENABLE_PERMISSION)。

WifiServiceImpl 的 mPermissionResultReceiver 接收广播 → 解析 granted 参数,设置 mUserGranted → 调用 mPermissionLatch.countDown()。

WifiServiceImpl 中阻塞的线程被唤醒 → 根据 mUserGranted 执行后续逻辑(允许 / 拒绝启用 Wifi)。

相关推荐
传奇开心果编程20 分钟前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓5 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1236 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)6 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白1 天前
自回归和上下文
android