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