Google 致力于为黑人社区推动种族平等。查看具体举措

会话发起协议概览

Android 提供支持会话发起协议 (SIP) 的 API。借助该 API,您可以将基于 SIP 的互联网电话功能添加到您的应用中。Android 包含完整的 SIP 协议栈和集成的呼叫管理服务,可让应用轻松设置呼出和呼入语音通话,而无需直接管理会话、传输级通信或音频录制/播放。

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

  • 进行视频会议
  • 即时通讯

要求和限制

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

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

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.hardware.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.hardware.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。这会跟踪是否已成功向您的 SIP 服务提供商注册 SipProfile

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
  • 初始化本地配置文件 (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_CALLSipProfile 对象创建一个待定 Intent。当 SipProfile 接到来电时,PendingIntent 对象会执行广播。

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. 在设备上连接到无线网络(设置 > 无线和网络 > WLAN > WLAN 设置)。
  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 automatically,然后选择 OK。