You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android来电场景下如何等待屏幕亮起后显示弹窗?

解决来电时等待屏幕亮起再显示弹窗的方案

我来帮你梳理下解决思路,针对你遇到的来电时Activity启动早于屏幕亮起、需要等屏幕亮再弹对话框的问题,这里有两个靠谱的方案,结合你之前尝试的方向做优化:

方案一:双广播联动 + 全局状态管理

这个方案是利用来电广播和屏幕状态广播的联动,通过全局状态来传递待处理的来电信息,精准在屏幕亮起时触发弹窗。

具体步骤:

  1. 全局状态存储:在你的Application类里用LiveData(或者全局变量)保存待处理的来电信息,这样两个广播接收器可以共享状态。
  2. 来电广播处理:收到来电时,先判断屏幕是否已经亮起。如果亮了直接启动弹窗;如果没亮,就把来电信息存入全局状态,等待屏幕亮的广播触发。
  3. 屏幕状态广播处理:收到ACTION_SCREEN_ON(屏幕亮起)的广播时,检查是否有待处理的来电,如果有就启动弹窗,然后清除状态避免重复触发。

代码示例:

自定义Application类(存储全局状态)

class MyApp : Application() {
    // 用LiveData来观察待处理的来电事件
    val pendingCallEvent = MutableLiveData<CallInfo?>()

    // 封装来电信息的实体类
    data class CallInfo(val phoneNumber: String)
}

来电广播接收器

public class CallReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
            String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
                String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
                PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                
                if (powerManager.isInteractive()) {
                    // 屏幕已亮,直接启动弹窗Activity
                    startCallDialogActivity(context, phoneNumber);
                } else {
                    // 屏幕未亮,把来电信息存入全局LiveData
                    MyApp app = (MyApp) context.getApplicationContext();
                    app.pendingCallEvent.postValue(new MyApp.CallInfo(phoneNumber));
                }
            } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
                // 来电结束,清除待处理状态
                MyApp app = (MyApp) context.getApplicationContext();
                app.pendingCallEvent.postValue(null);
            }
        }
    }

    private void startCallDialogActivity(Context context, String phoneNumber) {
        Intent dialogIntent = new Intent(context, CallDialogActivity.class);
        dialogIntent.putExtra("PHONE_NUMBER", phoneNumber);
        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 必须加这个标记从广播启动Activity
        context.startActivity(dialogIntent);
    }
}

屏幕状态广播接收器

public class ScreenStateReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action)) {
            MyApp app = (MyApp) context.getApplicationContext();
            MyApp.CallInfo pendingCall = app.pendingCallEvent.getValue();
            
            if (pendingCall != null) {
                // 有等待处理的来电,启动弹窗
                startCallDialogActivity(context, pendingCall.phoneNumber);
                // 清除状态,避免重复触发
                app.pendingCallEvent.postValue(null);
            }
        }
    }

    private void startCallDialogActivity(Context context, String phoneNumber) {
        Intent dialogIntent = new Intent(context, CallDialogActivity.class);
        dialogIntent.putExtra("PHONE_NUMBER", phoneNumber);
        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(dialogIntent);
    }
}

清单文件中注册广播

<receiver android:name=".CallReceiver">
    <intent-filter>
        <action android:name="android.intent.action.PHONE_STATE" />
    </intent-filter>
</receiver>
<receiver android:name=".ScreenStateReceiver">
    <intent-filter>
        <action android:name="android.intent.action.SCREEN_ON" />
    </intent-filter>
</receiver>

这个方案的优点是精准、高效,完全依赖系统广播触发,没有额外的资源消耗,逻辑也清晰。


方案二:轮询检查屏幕状态(快速实现)

如果你不想新增额外的屏幕广播,可以在来电广播里用Handler定期检查屏幕状态,直到屏幕亮起再启动弹窗。注意要避免阻塞主线程,同时在来电结束时取消轮询。

代码示例:

public class CallReceiver extends BroadcastReceiver {
    private Handler handler = new Handler(Looper.getMainLooper());
    private Runnable checkScreenRunnable;

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
            String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
            PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

            if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
                if (!powerManager.isInteractive()) {
                    // 启动轮询,每隔500ms检查一次屏幕状态
                    checkScreenRunnable = new Runnable() {
                        @Override
                        public void run() {
                            if (powerManager.isInteractive()) {
                                startCallDialogActivity(context, phoneNumber);
                                handler.removeCallbacks(this); // 停止轮询
                            } else {
                                handler.postDelayed(this, 500);
                            }
                        }
                    };
                    handler.postDelayed(checkScreenRunnable, 100); // 延迟100ms开始检查,避免立即执行
                } else {
                    startCallDialogActivity(context, phoneNumber);
                }
            } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
                // 来电结束,取消轮询,避免内存泄漏
                if (checkScreenRunnable != null) {
                    handler.removeCallbacks(checkScreenRunnable);
                }
            }
        }
    }

    private void startCallDialogActivity(Context context, String phoneNumber) {
        Intent dialogIntent = new Intent(context, CallDialogActivity.class);
        dialogIntent.putExtra("PHONE_NUMBER", phoneNumber);
        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(dialogIntent);
    }
}

这个方案的优点是实现简单,不需要额外的广播接收器,但轮询会有轻微的资源消耗,所以设置合理的检查间隔很重要(比如500ms就足够,不需要太频繁)。


关于你之前AsyncTask的问题

你遇到的unhandled exception java.util.concurrent.executionexception是因为调用AsyncTask的get()方法时,必须捕获ExecutionException和InterruptedException。不过AsyncTask已经被官方标记为过时,推荐用Coroutines或者ExecutorService替代,比如:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
    PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    // 循环等待屏幕亮起
    while (!powerManager.isInteractive()) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
            break;
        }
    }
    // 回到主线程启动Activity
    new Handler(Looper.getMainLooper()).post(() -> {
        startCallDialogActivity(context, phoneNumber);
    });
});
// 记得在来电结束时关闭executor:executor.shutdownNow();

不过这个本质还是轮询,和方案二类似,只是用了线程池实现。

内容的提问来源于stack exchange,提问作者PeterB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:43:48