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

如何实现类似WhatsApp锁屏/解锁时全屏来电弹窗(含iOS方案)

Android 端解决方案

核心问题分析

锁屏状态下系统会限制普通窗口的显示,需申请锁屏专属权限并使用符合系统规范的窗口类型/标记才能实现全屏弹窗。

具体实现步骤

  1. 申请必要权限
    在AndroidManifest.xml中声明权限:

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    

    Android 6.0+需动态申请悬浮窗权限:

    if (!Settings.canDrawOverlays(context)) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:" + context.getPackageName()));
        startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION);
    }
    
  2. 使用Full Screen Intent(官方推荐,与WhatsApp逻辑一致)
    这是锁屏下显示全屏来电弹窗的标准方案:

    • 配置来电全屏Activity(AndroidManifest.xml):
      <activity
          android:name=".IncomingCallActivity"
          android:excludeFromRecents="true"
          android:launchMode="singleInstance"
          android:theme="@style/FullScreenCallTheme">
          <intent-filter>
              <action android:name="com.yourpackage.ACTION_INCOMING_CALL" />
              <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
      </activity>
      
    • 在Activity的onCreate中设置锁屏显示标记:
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
              setShowWhenLocked(true);
              setTurnScreenOn(true);
          } else {
              getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                      | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
          }
          setContentView(R.layout.activity_incoming_call);
      }
      
    • 发送带全屏意图的通知:
      Intent callIntent = new Intent(context, IncomingCallActivity.class);
      PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(context, 0,
              callIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
      
      NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CALL_CHANNEL_ID)
              .setSmallIcon(R.drawable.ic_call)
              .setContentTitle("来电")
              .setContentText("张三")
              .setPriority(NotificationCompat.PRIORITY_HIGH)
              .setCategory(NotificationCompat.CATEGORY_CALL)
              .setFullScreenIntent(fullScreenPendingIntent, true);
      
      NotificationManagerCompat.from(context).notify(CALL_NOTIFICATION_ID, builder.build());
      
  3. 悬浮窗方案适配锁屏
    若坚持使用悬浮窗,需额外添加锁屏相关Window标记:

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.MATCH_PARENT,
            WindowManager.LayoutParams.MATCH_PARENT,
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD,
            PixelFormat.TRANSLUCENT);
    

    同时唤醒屏幕:

    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "Call:WakeLock");
    wakeLock.acquire(5000); // 保持唤醒5秒,可按需调整
    

iOS 端实现思路与代码片段

核心依赖:CallKit框架

iOS系统对来电类弹窗有严格管控,CallKit是唯一能在锁屏/后台显示全屏来电界面的官方方案,WhatsApp等应用均采用此框架。

具体实现步骤

  1. 配置项目权限

    • 在Capabilities中开启Push Notifications和Background Modes下的Voice over IP权限。
    • 导入CallKit与AVFoundation框架:
      import CallKit
      import AVFoundation
      
  2. 实现CallKit代理逻辑

    class CallManager: NSObject, CXProviderDelegate {
        private let provider: CXProvider
        
        override init() {
            let config = CXProviderConfiguration(localizedName: "我的通话")
            config.supportsVideo = true
            config.maximumCallGroups = 1
            config.maximumCallsPerCallGroup = 1
            config.iconTemplateImageData = UIImage(named: "call_icon")?.pngData()
            
            provider = CXProvider(configuration: config)
            super.init()
            provider.setDelegate(self, queue: nil)
        }
        
        // 触发来电通知
        func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false) {
            let update = CXCallUpdate()
            update.remoteHandle = CXHandle(type: .generic, value: handle)
            update.hasVideo = hasVideo
            
            provider.reportNewIncomingCall(with: uuid, update: update) { error in
                if let error = error {
                    print("来电报告失败: \(error.localizedDescription)")
                }
            }
        }
        
        // 处理来电接听
        func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
            // 跳转至自定义通话界面
            action.fulfill()
        }
        
        // 处理来电挂断
        func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
            // 执行挂断逻辑
            action.fulfill()
        }
        
        // 激活音频会话
        func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
            do {
                try audioSession.setCategory(.playAndRecord, mode: .voiceChat)
                try audioSession.setActive(true)
            } catch {
                print("音频会话激活失败: \(error.localizedDescription)")
            }
        }
        
        // 停用音频会话
        func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
            do {
                try audioSession.setActive(false)
            } catch {
                print("音频会话停用失败: \(error.localizedDescription)")
            }
        }
    }
    
  3. 触发来电(从推送/后台事件中调用)

    // 全局单例管理通话
    let callManager = CallManager()
    
    // 收到来电推送时触发
    func handleIncomingCall(contactName: String) {
        let callUUID = UUID()
        callManager.reportIncomingCall(uuid: callUUID, handle: contactName)
        // 解锁状态下可跳转至自定义全屏通话界面
    }
    

注意事项

  • 锁屏时系统会显示统一风格的来电界面,解锁后可切换至自定义全屏视图。
  • 需确保功能真实用于语音/视频通话,否则苹果审核会被驳回。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 08:20:01