会话发起协议概览

检测 eSIM 卡和 SIM 卡

检测卡

装有 SIM 卡和 eSIM 卡的 Android 设备会在电话 API 中使用以下 ID,包括 [`TelephonyManager`](/reference/android/telephony/TelephonyManager) 和 [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager): * 订阅 ID:移动订阅的唯一 ID。 * 逻辑插槽索引或 ID:引用逻辑 SIM 卡插槽的唯一索引。 逻辑插槽 ID 从 0 开始,并且根据设备上支持的活跃插槽数量而增加。例如,双 SIM 卡设备通常具有插槽 0 和插槽 1。如果设备具有多个实体插槽,但仅支持一个活动插槽,则其逻辑插槽 ID 仅为 0。* 实体插槽索引或 ID:引用实体 SIM 卡插槽的唯一索引。 实体插槽 ID 从 0 开始,并且根据设备上的实体插槽数量而增加。这与设备具有的逻辑插槽数量不同,后者对应的是设备能够使用的活动插槽数量。例如,在双 SIM 卡模式和单 SIM 卡模式之间切换的设备可能始终具有两个实体插槽,但在单 SIM 卡模式下,它只有一个逻辑插槽。* 卡 ID:用于标识 UiccCard 的唯一 ID。 {9}在包含两个逻辑槽位和三个实体槽位的情况下,ID 的使用方式示意图](/images/guide/topics/connectivity/tel-ids.png) 在上图中: * 设备有两个逻辑槽位。 * 实体插槽 0 中有一个具有有效配置文件的实体 UICC 卡。 * 实体插槽 2 中是一个具有有效配置文件的 eUICC。 * 实体插槽 1 目前没有使用。 {9}在具有三个逻辑槽位和两个物理槽位的情况下,ID 的使用方式示意图](/images/guide/topics/connectivity/tel-ids-2.png) 在上图中: * 设备有三个逻辑槽位。 * 实体插槽 0 中有一个具有有效配置文件的实体 UICC 卡。 * 实体插槽 1 中是一个具有两个已下载配置文件的 eUICC,两个配置文件都使用 MEP(多个已启用的配置文件)处于活动状态。

会话发起协议概览

Android 提供了一个支持会话发起协议 (SIP) 的 API。 这样,您就可以将基于 SIP 的互联网电话功能添加到您的应用。Android 包含完整的 SIP 协议堆栈和集成的通话管理服务,可让应用轻松设置去电和来电语音通话,而无需直接管理会话、传输级通信或录音或播放。

以下是可能使用 SIP API 的应用类型示例:

  • 视频会议
  • 即时通讯

要求和限制

以下是开发 SIP 应用的要求:

  • 您必须拥有搭载 Android 2.3 或更高版本的移动设备。
  • SIP 通过无线数据连接运行,因此您的设备必须具备数据连接(使用移动数据服务或 Wi-Fi)。这意味着您无法在 AVD 上进行测试,只能在实体设备上进行测试。如需了解详情,请参阅测试 SIP 应用
  • 应用通信会话中的每个参与者都必须有一个 SIP 帐号。有很多不同的 SIP 提供商提供 SIP 账号。

注意android.net.sip 库不支持视频通话。如果您想使用 SIP 堆栈(例如 android.net.sip)实现 VoIP 通话,请从众多现代开源替代方案中选择一种,作为任何 VoIP 通话实现的基础。或者,您也可以实现 ConnectionService API,以将这些调用紧密集成到设备的拨号器应用中。

SIP API 类和接口

下面总结了 Android SIP API 中包含的一个类和一个接口 (SipRegistrationListener):

类/接口 说明
SipAudioCall 处理 SIP 互联网语音通话。
SipAudioCall.Listener 监听与 SIP 通话相关的事件,例如正在接听电话(“响铃”)或拨出电话(“正在通话”)。
SipErrorCode 定义在 SIP 操作期间返回的错误代码。
SipManager 为 SIP 任务(例如发起 SIP 连接)提供 API,并提供对相关 SIP 服务的访问权限。
SipProfile 定义 SIP 配置文件,包括 SIP 帐号、网域和服务器信息。
SipProfile.Builder 用于创建 SipProfile 的辅助类。
SipSession 表示与 SIP 对话框关联的 SIP 会话或不在对话框内的独立事务。
SipSession.Listener 监听与 SIP 会话相关的事件,例如注册会话时(“注册时”)或拨出电话时(“通话中”)。
SipSession.State 定义 SIP 会话状态,例如“正在注册”、“去电”和“正在通话”。
SipRegistrationListener 作为 SIP 注册事件监听器的接口。

创建清单

如果您正在开发使用 SIP API 的应用,请记住,只有 Android 2.3(API 级别 9)及更高版本的平台才支持此功能。此外,在搭载 Android 2.3(API 级别 9)或更高版本的设备上,并非所有设备都支持 SIP 支持。

要使用 SIP,请将以下权限添加到应用的清单中:

  • android.permission.USE_SIP
  • android.permission.INTERNET

为了确保您的应用只能安装在支持 SIP 的设备上,请将以下内容添加到应用的清单中:

<uses-sdk android:minSdkVersion="9" />

这表示您的应用需要安装 Android 2.3 或更高版本。如需了解详情,请参阅 API 级别<uses-sdk> 元素的文档。

如需控制如何从不支持 SIP 的设备(例如在 Google Play 上)过滤您的应用,请将以下代码添加到应用的清单中:

<uses-feature android:name="android.software.sip.voip" />

这说明您的应用使用的是 SIP API。声明应包含 android:required 属性,指示您是否希望从不支持 SIP 的设备中滤除应用。您可能还需要其他 <uses-feature> 声明,具体取决于您的实现。如需了解详情,请参阅 <uses-feature> 元素的文档。

如果应用可用于接听电话,您还必须在应用清单中定义接收器(BroadcastReceiver 子类):

<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />

以下代码摘录自 SipDemo 清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.android.sip">
  ...
     <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
  ...
  <uses-sdk android:minSdkVersion="9" />
  <uses-permission android:name="android.permission.USE_SIP" />
  <uses-permission android:name="android.permission.INTERNET" />
  ...
  <uses-feature android:name="android.software.sip.voip" android:required="true" />
  <uses-feature android:name="android.hardware.wifi" android:required="true" />
  <uses-feature android:name="android.hardware.microphone" android:required="true" />
</manifest>

创建 SipManager

要使用 SIP API,您的应用必须创建 SipManager 对象。在您的应用中,SipManager 负责处理以下事项:

  • 发起 SIP 会话。
  • 发起和接听电话。
  • 向 SIP 提供商注册和取消注册。
  • 验证会话连接。

按照如下所示实例化一个新的 SipManager

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

Java

public SipManager sipManager = null;
...
if (sipManager == null) {
    sipManager = SipManager.newInstance(this);
}

向 SIP 服务器注册

典型的 Android SIP 应用涉及一个或多个用户,每个用户都有一个 SIP 帐号。在 Android SIP 应用中,每个 SIP 帐号由一个 SipProfile 对象表示。

SipProfile 定义 SIP 配置文件,包括 SIP 帐号、网域和服务器信息。与运行应用的设备上 SIP 帐号关联的配置文件称为“本地配置文件”。会话连接到的配置文件称为“对等配置文件”。当您的 SIP 应用使用本地 SipProfile 登录 SIP 服务器时,实际上就是将设备注册为针对您的 SIP 地址的 SIP 通话发送到的位置。

本部分介绍了如何创建 SipProfile、向 SIP 服务器注册它以及跟踪注册事件。

按照如下所示创建一个 SipProfile 对象:

Kotlin

private var sipProfile: SipProfile? = null
...

val builder = SipProfile.Builder(username, domain)
        .setPassword(password)
sipProfile = builder.build()

Java

public SipProfile sipProfile = null;
...

SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
sipProfile = builder.build();

以下代码片段会打开用于拨打电话和/或接听常规 SIP 通话的本地配置文件。调用方可以通过 mSipManager.makeAudioCall 进行后续通话。此摘录还设置了 android.SipDemo.INCOMING_CALL 操作,当设备收到来电时,intent 过滤器将使用该操作(请参阅设置 intent 过滤器以接听电话)。以下是注册步骤:

Kotlin

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open(sipProfile, pendingIntent, null)

Java

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

最后,此代码在 SipManager 上设置了 SipRegistrationListener。这会跟踪 SipProfile 是否已成功向您的 SIP 服务提供商注册:

Kotlin

sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener {

    override fun onRegistering(localProfileUri: String) {
        updateStatus("Registering with SIP Server...")
    }

    override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
        updateStatus("Ready")
    }

    override fun onRegistrationFailed(
            localProfileUri: String,
            errorCode: Int,
            errorMessage: String
    ) {
        updateStatus("Registration failed. Please check settings.")
    }
})

Java

sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() {

    public void onRegistering(String localProfileUri) {
        updateStatus("Registering with SIP Server...");
    }

    public void onRegistrationDone(String localProfileUri, long expiryTime) {
        updateStatus("Ready");
    }

    public void onRegistrationFailed(String localProfileUri, int errorCode,
        String errorMessage) {
        updateStatus("Registration failed.  Please check settings.");
    }
}

当您的应用使用完配置文件后,应将其关闭以将关联的对象释放到内存中,并从服务器取消注册设备。例如:

Kotlin

fun closeLocalProfile() {
    try {
        sipManager?.close(sipProfile?.uriString)
    } catch (ee: Exception) {
        Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee)
    }
}

Java

public void closeLocalProfile() {
    if (sipManager == null) {
       return;
    }
    try {
       if (sipProfile != null) {
          sipManager.close(sipProfile.getUriString());
       }
     } catch (Exception ee) {
       Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
     }
}

进行语音通话

要进行语音通话,您必须具备以下条件:

  • 正在拨打电话的 SipProfile(“本地配置文件”),以及用于接听电话的有效 SIP 地址(“对等配置文件”)。
  • 一个 SipManager 对象。

要进行语音通话,您应设置 SipAudioCall.Listener。客户端与 SIP 堆栈的大部分交互都是通过监听器进行的。在此代码段中,您可以看到 SipAudioCall.Listener 在建立调用后是如何进行的设置:

Kotlin

var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {

    override fun onCallEstablished(call: SipAudioCall) {
        call.apply {
            startAudio()
            setSpeakerMode(true)
            toggleMute()
        }
    }

    override fun onCallEnded(call: SipAudioCall) {
        // Do something.
    }
}

Java

SipAudioCall.Listener listener = new SipAudioCall.Listener() {

   @Override
   public void onCallEstablished(SipAudioCall call) {
      call.startAudio();
      call.setSpeakerMode(true);
      call.toggleMute();
         ...
   }

   @Override

   public void onCallEnded(SipAudioCall call) {
      // Do something.
   }
};

设置 SipAudioCall.Listener 后,您就可以进行调用了。SipManager 方法 makeAudioCall 接受以下参数:

  • 本地 SIP 配置文件(来电者)。
  • 对等 SIP 配置文件(被呼叫的用户)。
  • 一个 SipAudioCall.Listener,用于监听来自 SipAudioCall 的通话事件。这可以是 null,但如上所示,监听器用于在建立调用后进行设置。
  • 超时值,以秒为单位。

例如:

Kotlin

val call: SipAudioCall? = sipManager?.makeAudioCall(
        sipProfile?.uriString,
        sipAddress,
        listener,
        30
)

Java

call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);

接听电话

为了接听电话,SIP 应用必须包含 BroadcastReceiver 的子类,能够响应表明有来电的 intent。因此,您必须在应用中执行以下操作:

  • AndroidManifest.xml 中,声明 <receiver>。在 SipDemo 中,此为 <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
  • 实现接收器,它是 BroadcastReceiver 的子类。在 SipDemo 中,此为 IncomingCallReceiver
  • 使用一个待处理 intent 初始化本地配置文件 (SipProfile),该 intent 会在有人调用本地资料时触发您的接收器。
  • 设置一个 intent 过滤器,按表示来电的操作进行过滤。在 SipDemo 中,此操作为 android.SipDemo.INCOMING_CALL

创建 BroadcastReceiver 的子类

要接听电话,您的 SIP 应用必须创建 BroadcastReceiver 的子类。Android 系统会处理 SIP 来电,并在收到来电时广播“来电” intent(由应用定义)。以下是 SipDemo 示例中的 BroadcastReceiver 子类代码。

Kotlin

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
class IncomingCallReceiver : BroadcastReceiver() {

    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    override fun onReceive(context: Context, intent: Intent) {
        val wtActivity = context as WalkieTalkieActivity

        var incomingCall: SipAudioCall? = null
        try {
            incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener)
            incomingCall?.apply {
                answerCall(30)
                startAudio()
                setSpeakerMode(true)
                if (isMuted) {
                    toggleMute()
                }
                wtActivity.call = this
                wtActivity.updateStatus(this)
            }
        } catch (e: Exception) {
            incomingCall?.close()
        }
    }

    private val listener = object : SipAudioCall.Listener() {

        override fun onRinging(call: SipAudioCall, caller: SipProfile) {
            try {
                call.answerCall(30)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

Java

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
public class IncomingCallReceiver extends BroadcastReceiver {
    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        SipAudioCall incomingCall = null;
        try {
            SipAudioCall.Listener listener = new SipAudioCall.Listener() {
                @Override
                public void onRinging(SipAudioCall call, SipProfile caller) {
                    try {
                        call.answerCall(30);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
            incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener);
            incomingCall.answerCall(30);
            incomingCall.startAudio();
            incomingCall.setSpeakerMode(true);
            if(incomingCall.isMuted()) {
                incomingCall.toggleMute();
            }
            wtActivity.call = incomingCall;
            wtActivity.updateStatus(incomingCall);
        } catch (Exception e) {
            if (incomingCall != null) {
                incomingCall.close();
            }
        }
    }
}

设置 Intent 过滤器以接听电话

当 SIP 服务收到新来电时,它会发出一个包含应用提供的操作字符串的 intent。在 SipDemo 中,此操作字符串为 android.SipDemo.INCOMING_CALL

以下代码摘录自 SipDemo,展示了如何基于操作字符串 android.SipDemo.INCOMING_CALL 使用待处理 intent 创建 SipProfile 对象。PendingIntent 对象会在 SipProfile 收到来电时执行广播:

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

var sipProfile: SipProfile? = null
...

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open (sipProfile, pendingIntent, null)

Java

public SipManager sipManager = null;
public SipProfile sipProfile = null;
...

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

intent 过滤器会拦截广播,然后该过滤器将触发接收器 (IncomingCallReceiver)。您可以在应用的清单文件中指定 intent 过滤器,也可以在代码中操作,如 SipDemo 示例应用的 ActivityonCreate() 方法所示:

Kotlin

class WalkieTalkieActivity : Activity(), View.OnTouchListener {
    ...
    lateinit var callReceiver: IncomingCallReceiver
    ...

    override fun onCreate(savedInstanceState: Bundle) {
        val filter = IntentFilter().apply {
            addAction("android.SipDemo.INCOMING_CALL")
        }
        callReceiver = IncomingCallReceiver()
        this.registerReceiver(callReceiver, filter)
        ...
    }
    ...
}

Java

public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
...
    public IncomingCallReceiver callReceiver;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {

       IntentFilter filter = new IntentFilter();
       filter.addAction("android.SipDemo.INCOMING_CALL");
       callReceiver = new IncomingCallReceiver();
       this.registerReceiver(callReceiver, filter);
       ...
    }
    ...
}

测试 SIP 应用

要测试 SIP 应用,您需要具备以下条件:

  • 搭载 Android 2.3 或更高版本的移动设备。SIP 通过无线方式运行,因此您必须在实际设备上进行测试。在 AVD 上进行测试无效。
  • SIP 账号。有很多不同的 SIP 提供商提供 SIP 账号。
  • 如果您要拨打电话,它还必须是有效的 SIP 账号。

要测试 SIP 应用,请按以下步骤操作:

  1. 将设备连接到无线网络(依次点击设置 > 无线和网络 > Wi-Fi > Wi-Fi 设置)。
  2. 面向测试设置您的移动设备,详见在设备上进行开发
  3. 按照在设备上进行开发中所述,在移动设备上运行您的应用。
  4. 如果您使用的是 Android Studio,则可以打开事件日志控制台 (View > Tool Windows > Event Log) 来查看应用日志输出。
  5. 确保您的应用已配置为在运行时自动启动 Logcat:
    1. 依次选择 Run > Edit Configurations
    2. 选择 Run/Debug Configurations 窗口中的 Miscellaneous 标签页。
    3. Logcat 下,选择 Show logcat automated,然后选择 OK