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

如何在Angular 14中使用Capacitor获取手机通话记录?

Angular 14 + Capacitor 获取通话记录实现方案

由于Capacitor官方无通话记录相关插件,且原Cordova插件存在稳定性问题,最优方案是自定义Capacitor插件,针对Android/iOS平台特性分别实现原生逻辑,再暴露API给Angular调用。以下是具体实现步骤:

核心思路

  • Android:通过系统ContentResolver读取历史通话记录,需申请对应权限
  • iOS:受苹果隐私政策限制,无法获取历史通话记录,仅能通过CallKit监听实时通话事件(来电/去电)

一、自定义Capacitor插件

先通过Capacitor CLI生成插件基础框架:

npm init @capacitor/plugin call-log-plugin

1. Android端实现(Kotlin)

步骤1:添加权限声明

在android/src/main/AndroidManifest.xml中添加权限:

<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 可选,用于获取实时通话状态 -->

步骤2:实现通话记录读取逻辑

在插件的CallLogPlugin.kt中编写核心代码:

@CapacitorPlugin(name = "CallLog")
class CallLogPlugin : Plugin() {

    @PluginMethod()
    fun getCallLogs(call: PluginCall) {
        // 检查权限
        if (!hasPermission(Manifest.permission.READ_CALL_LOG)) {
            requestPermission(Manifest.permission.READ_CALL_LOG, call)
            return
        }

        val callLogs = mutableListOf<HashMap<String, Any>>()
        val contentResolver = context.contentResolver
        val projection = arrayOf(
            CallLog.Calls.NUMBER,
            CallLog.Calls.TYPE,
            CallLog.Calls.DATE,
            CallLog.Calls.DURATION,
            CallLog.Calls.NAME
        )

        // 查询通话记录(按时间倒序)
        contentResolver.query(
            CallLog.Calls.CONTENT_URI,
            projection,
            null,
            null,
            "${CallLog.Calls.DATE} DESC"
        )?.use { cursor ->
            while (cursor.moveToNext()) {
                val log = HashMap<String, Any>().apply {
                    put("number", cursor.getString(cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER)))
                    put("type", cursor.getInt(cursor.getColumnIndexOrThrow(CallLog.Calls.TYPE)))
                    put("date", cursor.getLong(cursor.getColumnIndexOrThrow(CallLog.Calls.DATE)))
                    put("duration", cursor.getLong(cursor.getColumnIndexOrThrow(CallLog.Calls.DURATION)))
                    put("name", cursor.getString(cursor.getColumnIndexOrThrow(CallLog.Calls.NAME)) ?: "未知")
                }
                callLogs.add(log)
            }
        }

        call.resolve(JSObject().apply {
            put("callLogs", callLogs)
        })
    }

    // 权限申请回调
    override fun handleRequestPermissionResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        val call = bridge.getSavedCall(requestCode) ?: return
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            getCallLogs(call)
        } else {
            call.reject("权限被拒绝,无法获取通话记录")
        }
    }
}

2. iOS端实现(Swift)

iOS不允许读取历史通话记录,仅实现实时通话监听:

步骤1:添加权限描述

在ios/Plugin/Info.plist中添加权限说明:

<key>NSCallKitUsageDescription</key>
<string>需要获取通话信息以识别来电者身份</string>

步骤2:实现CallKit监听逻辑

在CallLogPlugin.swift中编写监听代码:

import Capacitor
import CallKit

@objc(CallLogPlugin)
public class CallLogPlugin: CAPPlugin, CXCallObserverDelegate {
    private let callObserver = CXCallObserver()

    override public func load() {
        callObserver.setDelegate(self, queue: nil)
    }

    @objc func startCallListening(_ call: CAPPluginCall) {
        call.resolve(["status": "已启动实时通话监听"])
    }

    public func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
        // 监听已接通的通话
        if call.hasEnded == false && call.hasConnected == true {
            if let handle = call.handle as? CXHandle, let phoneNumber = handle.value as? String {
                // 发送实时通话事件到Angular端
                notifyListeners("callReceived", data: [
                    "number": phoneNumber,
                    "isIncoming": !call.isOutgoing
                ])
            }
        }
    }
}

二、Angular端集成与调用

1. 定义插件TypeScript接口

在Angular项目中创建call-log-plugin.ts:

import { registerPlugin, PluginListenerHandle } from '@capacitor/core';

export interface CallLog {
  number: string;
  type: number;
  date: number;
  duration: number;
  name: string;
}

export interface CallLogPlugin {
  getCallLogs(): Promise<{ callLogs: CallLog[] }>;
  startCallListening(): Promise<{ status: string }>;
  addListener(eventName: 'callReceived', listenerFunc: (data: { number: string; isIncoming: boolean }) => void): Promise<PluginListenerHandle> & PluginListenerHandle;
}

const CallLog = registerPlugin<CallLogPlugin>('CallLog');
export default CallLog;

2. 在组件中调用

import { Component, OnInit } from '@angular/core';
import CallLog from './call-log-plugin';
import { Capacitor } from '@capacitor/core';

@Component({
  selector: 'app-caller-identify',
  templateUrl: './caller-identify.component.html'
})
export class CallerIdentifyComponent implements OnInit {
  callLogs: CallLog[] = [];

  async ngOnInit() {
    // 监听实时来电(仅iOS有效)
    CallLog.addListener('callReceived', (data) => {
      console.log('实时来电号码:', data.number);
      this.matchCallerInfo(data.number);
    });

    // Android端获取历史通话记录
    if (Capacitor.getPlatform() === 'android') {
      try {
        const result = await CallLog.getCallLogs();
        this.callLogs = result.callLogs;
        this.callLogs.forEach(log => this.matchCallerInfo(log.number));
      } catch (err) {
        console.error('获取通话记录失败:', err);
      }
    }

    // iOS启动实时监听
    if (Capacitor.getPlatform() === 'ios') {
      await CallLog.startCallListening();
    }
  }

  private async matchCallerInfo(phoneNumber: string) {
    // 调用自有数据库接口,传入号码获取来电者信息
    // const callerInfo = await this.apiService.getCallerDetail(phoneNumber);
    // console.log('来电者信息:', callerInfo);
  }
}

三、关键注意事项

  • Android:Android 11+对通话记录权限限制更严格,需用户手动授予,且应用需符合Google Play隐私政策
  • iOS:苹果禁止读取历史通话记录,若需实现来电弹窗识别,需结合CallKit与后台推送服务(后台运行权限受限)
  • 测试:必须在真机上测试,模拟器无法模拟真实通话场景与权限流程
  • 隐私合规:需在应用隐私政策中明确说明通话数据的用途,符合各平台隐私要求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:33:26