21、联系人快捷拨号:使用Intent拨打电话、检查拨号权限、通话记录查询(CallLog)、快速拨号Widget

快捷拨号这个功能,说白了就是让用户少点几下屏幕。我做过好几个联系人相关的项目,每次做到拨号这块,总会有产品经理跑过来说:“能不能让用户一键就拨出去?”嗯,今天我们就来聊聊怎么实现这个“一键拨号”。

21.1 使用Intent拨打电话

Android里拨打电话有两种方式,区别挺大的。我刚开始做的时候也踩过坑,这里给你讲清楚。

方式一:直接拨号(需要权限)

这种方式会直接进入拨号界面,并且号码已经填好。用户只需要点一下拨号键就行。

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(intent);

方式二:自动拨出(需要权限)

这种方式更狠——直接就把电话拨出去了,用户连确认的机会都没有。

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(intent);
⚠️ 注意:ACTION_CALL 需要 android.permission.CALL_PHONE 权限。如果你用 ACTION_DIAL,则不需要额外权限。

我个人习惯用 ACTION_DIAL,为什么呢?因为用户体验更好。你想想看,万一用户不小心碰到了拨号按钮,直接打出去多尴尬。我曾经在一个紧急联系人App里用了 ACTION_CALL,结果测试的时候不小心点到了,直接给产品经理打了个电话……场面一度非常尴尬。

21.2 检查拨号权限

Android 6.0 以后,权限管理变得严格了。你不能直接调用 ACTION_CALL,必须先检查权限。

这里我贴一段我常用的权限检查代码:

private void checkCallPermission() {
    if (ContextCompat.checkSelfPermission(this, 
            Manifest.permission.CALL_PHONE) 
            != PackageManager.PERMISSION_GRANTED) {
        
        // 权限没有被授予,需要申请
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CALL_PHONE},
                REQUEST_CODE_CALL_PHONE);
    } else {
        // 权限已经有了,直接拨号
        makeCall(phoneNumber);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, 
        @NonNull String[] permissions, 
        @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, 
            permissions, grantResults);
    
    if (requestCode == REQUEST_CODE_CALL_PHONE) {
        if (grantResults.length > 0 
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            makeCall(phoneNumber);
        } else {
            // 用户拒绝了权限
            Toast.makeText(this, "需要拨号权限才能拨打电话", 
                    Toast.LENGTH_SHORT).show();
        }
    }
}
💡 小技巧:如果用户拒绝了权限,你可以引导用户去设置里手动开启。用 Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 可以跳转到应用详情页。

21.3 通话记录查询(CallLog)

CallLog 是 Android 系统提供的一个内容提供者,里面存了所有的通话记录。你可以用它来查询最近的通话、未接来电等等。

查询 CallLog 需要 android.permission.READ_CALL_LOG 权限。这个权限在 Android 6.0 以后也是运行时权限,需要动态申请。

来看一个查询最近10条通话记录的示例:

private void queryCallLog() {
    Cursor cursor = getContentResolver().query(
            CallLog.Calls.CONTENT_URI,
            null,
            null,
            null,
            CallLog.Calls.DATE + " DESC"
    );
    
    if (cursor != null) {
        List<CallLogEntry> callLogs = new ArrayList<>();
        int count = 0;
        while (cursor.moveToNext() && count < 10) {
            CallLogEntry entry = new CallLogEntry();
            entry.number = cursor.getString(
                    cursor.getColumnIndex(CallLog.Calls.NUMBER));
            entry.name = cursor.getString(
                    cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));
            entry.type = cursor.getInt(
                    cursor.getColumnIndex(CallLog.Calls.TYPE));
            entry.date = cursor.getLong(
                    cursor.getColumnIndex(CallLog.Calls.DATE));
            entry.duration = cursor.getLong(
                    cursor.getColumnIndex(CallLog.Calls.DURATION));
            callLogs.add(entry);
            count++;
        }
        cursor.close();
    }
}

通话类型有几种:

类型常量 说明
CallLog.Calls.INCOMING_TYPE 1 来电
CallLog.Calls.OUTGOING_TYPE 2 去电
CallLog.Calls.MISSED_TYPE 3 未接来电
CallLog.Calls.VOICEMAIL_TYPE 4 语音信箱
CallLog.Calls.REJECTED_TYPE 5 已拒绝
📌 重点:CallLog 查询返回的数据是按时间倒序排列的。最新的通话记录在最前面。如果你要做分页查询,可以用 LIMIT 参数。

21.4 快速拨号Widget

Widget 是 Android 的一大特色。做一个快速拨号的 Widget,用户可以直接在桌面上点一下就打给某个联系人。

创建 Widget 需要三步:

  1. 定义 Widget 布局 - 在 res/layout 下创建 widget_layout.xml
  2. 创建 Widget Provider - 继承 AppWidgetProvider
  3. 注册到 Manifest - 在 AndroidManifest.xml 中声明

先看布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="@drawable/widget_bg">
    
    <TextView
        android:id="@+id/contact_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="张三"
        android:textSize="14sp"
        android:textColor="#333333"/>
    
    <TextView
        android:id="@+id/contact_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13800138000"
        android:textSize="12sp"
        android:textColor="#666666"/>
    
    <ImageButton
        android:id="@+id/call_button"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/ic_call"
        android:background="@drawable/call_button_bg"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>

然后是 Widget Provider:

public class QuickDialWidget extends AppWidgetProvider {
    
    @Override
    public void onUpdate(Context context, 
            AppWidgetManager appWidgetManager, 
            int[] appWidgetIds) {
        
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }
    
    static void updateAppWidget(Context context,
            AppWidgetManager appWidgetManager,
            int appWidgetId) {
        
        RemoteViews views = new RemoteViews(
                context.getPackageName(), 
                R.layout.widget_layout);
        
        // 设置点击拨号
        Intent callIntent = new Intent(Intent.ACTION_CALL);
        callIntent.setData(Uri.parse("tel:13800138000"));
        PendingIntent pendingIntent = PendingIntent.getActivity(
                context, 0, callIntent, 
                PendingIntent.FLAG_UPDATE_CURRENT | 
                PendingIntent.FLAG_IMMUTABLE);
        
        views.setOnClickPendingIntent(R.id.call_button, pendingIntent);
        
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

别忘了在 Manifest 里注册:

<receiver android:name=".QuickDialWidget">
    <intent-filter>
        <action android:name=
            "android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/widget_info" />
</receiver>
⚠️ 注意:Widget 里的 PendingIntent 在 Android 12 以后必须指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE。否则会崩溃。我之前升级 targetSdk 到 31 的时候就踩了这个坑。

下面我用一张图来总结一下本章的知识结构:

联系人快捷拨号知识体系 Intent拨打电话 ACTION_DIAL vs ACTION_CALL 检查拨号权限 运行时权限申请与处理 通话记录查询 CallLog内容提供者使用 快速拨号Widget 桌面快捷方式实现 核心要点总结 • 拨号推荐使用 ACTION_DIAL,用户体验更好 • 权限申请要处理拒绝情况,引导用户手动开启 • CallLog 查询注意权限和性能,避免在主线程执行 • Widget 开发注意 PendingIntent 的 FLAG 设置

好了,以上就是联系人快捷拨号的完整实现方案。从 Intent 拨号到权限检查,从通话记录查询到桌面 Widget,每一步都有它的坑和技巧。我在实际项目中把这些功能组合起来,做了一个“智能拨号助手”,用户反馈还不错。希望今天的分享对你有帮助。

公众号:蓝海资料掘金营,微信deep3321