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)。

相关推荐
Android疑难杂症2 小时前
鸿蒙Media Kit媒体服务开发快速指南
android·harmonyos·音视频开发
马 孔 多 在下雨3 小时前
Android动画集大成之宗-MotionLayout基础指南
android
用户413079810613 小时前
Android动画集大成之宗-MotionLayout
android
金鸿客3 小时前
在Compose中使用camerax进行拍照和录视频
android
伟大的大威5 小时前
Android 端离线语音控制设备管理系统:完整技术方案与实践
android·macos·xcode
骑驴看星星a8 小时前
【Three.js--manual script】4.光照
android·开发语言·javascript
TDengine (老段)14 小时前
TDengine 字符串函数 CONCAT_WS 用户手册
android·大数据·数据库·时序数据库·tdengine·涛思数据
会跑的兔子15 小时前
Android 16 Kotlin协程 第一部分
android·开发语言·kotlin
Meteors.16 小时前
安卓进阶——OpenGL ES
android