Pro Android学习笔记(一二四):Telephony API(6):SIP Phone(上)

文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处http://blog.csdn.net/flowingflying以及作者@恺风Wei。

Android有android.net.sip和android.net.rtp包。而在sdk\samples\android-19(API版本)\legacy\SipDemo提供一个SIP phone的例子。此外,Android的MediaPlayer在某程度上支持RTSP。

对SIPDemo的详细解释,可以阅读:

  • http://www.shuyangyang.com.cn/jishuliangongfang/qitajishu/2013-08-14/106.html
  • http://gushedaoren.blog.163.com/blog/static/17366340520136282316590/

虽然android.net.sip是在Android2.3就提供,android.net.rtp是在Android3.0提供,但就android.net.sip而言,属于高度封装的SIP呼叫(不是协议级别),在使用过程中可塑性很小,能否和SIP server兼容看运气了,除非SIP Server针对Android SIP来调测过。

权限和功能需求

<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<uses-feature android:name="android.hardware.sip.voip" android:required="true"/>

SIP初始化

//初始化包括两个步骤:1、获得sipManager的对象;2、通过SipProfile来配置我们的sipPhone
public SipManager sipManager = null;
public SipProfile mySipProfile = null;

private void initMySipPhone(String username, String domain, String password){
    // 【1】获得SipManger的对象。需要注意,如果在模拟器,无法获得对象,得到的是null,因此必须在实体机中测试。但是不是所有的实体机都一定能获得有效的SipManager对象,例如在N909上,返回null。
    if(sipManager == null){ 
        sipManager = SipManager.newInstance(this); 
    }
    if(sipManager == null){   
        Toast.makeText(this, "设备不支持SIP!", Toast.LENGTH_LONG).show();
        return;
    }  
         
    /*【2】进行SIP配置,配置了SIP的帐号、sip server的domain和账号密码。我们注意到:
    (1)不能设置STUN,也就是Sip server需要支持SBC,才能解决NAT穿越问题,而手机在WiFi的情况下,都是NAT 
    (2)不支持本地SIP端口的设置,SIPManager将自动获取一个空闲端口作为SIP的端口号,这个问题在下面的REGISTER中将继续讨论 */ 

    SipProfile.Builder builder = new SipProfile.Builder(username, domain); 
    builder.setPassword(password);
 
    //builder.setPort(5060); //这是指SIP Server  
    mySipProfile = builder.build(); 
    debug("mySipProfile = " + mySipProfile.toString());
}

开启

如果要收听来电,可通过PendingIntent在来电是触发一个intent,通过这个intent可以打开某个activity,也可以触发广播接收器。下面是开启的例子:

Intent i = new Intent(); 
i.setAction("cn.wei.flowingflying.mysipphone.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA); 
sipManager.open(mySipProfile, pi, null);  //第三个参数为监听器,可以在REGISTER时在设定

如果我们不需要来电接听,可以简单如下:

sipManager.open(mySipProfile);

在实体机测试中,Nubia Z5S(百度云ROM)无法正确开启,报NullProinterExpection的异常,而在华为P6(Andriod 4.4.2)中则正常。我们不清楚OEM对android.net.sip的支持情况,不是每款手机都会支持,因此其使用受到制约,如果要实现SIP phone,采用其他方式恐怕更为合适。

REGISTER

sip客户端需要向SIP server报告其地址,以便SIP Server寻址。SIP Server有很多名称,早前叫软交换,后来叫IMS,其实没什么太大的区别。有自动和手动两种注册方式,我们先看看手动方式。

public void register(){          
    //人工注册的方式。设置的Expired时间为300秒。人工注册是只人工触发一个REGISTER的sesssion的执行,不会自动进行定期REGISTER,需要人工处理。第三个参数是register状态的回调函数对象,本例采用内部类的方式。SIP消息的发送(包括重复)和接收是在后台线程中进行的,经检查为Binder_1,所以我们有理由怀疑是使用了JNI。例子中,希望在UI中显示状态,但是后台线程是不能直接操纵UI的,因此debug中需要特别处理。 
    sipManager.register(mySipProfile, 300, new SipRegistrationListener()
       @Override
        public void onRegistrationFailed(String localProfileUri, int errorCode,
                String errorMessage) { 
            debug("Register Fail : code=" + errorCode + "," + errorMessage);
        }
       
        @Override
        public void onRegistrationDone(String localProfileUri, long expiryTime) { 
            long secs = expiryTime - System.currentTimeMillis(); 
            debug("Register Done : uri=" + localProfileUri + ",expired-time=" + secs);
            try{  //人工注册过程请特别注意,即便是收到了REGISTER的200OK的恢复,即注册成功,但无法检查注册状态isRegistered(),因为不属于自动处理,相关的状态没有记录。由于相关的状态没有记录,导致不能定期自动注册,需要根据注册到期时间expiryTime,在此之前再次人工进行注册。
                debug("check register : " + sipManager.isRegistered(mySipProfile.getUriString()));
            }catch(Exception e){                    
            }
        }
       
        @Override
        public void onRegistering(String localProfileUri) { 
            Log.i("WEI",Thread.currentThread().getName());
            debug("" + localProfileUri  + " is registering");
        }
    });


private void debug(final String info){ 
    this.runOnUiThread(new Runnable() { //可以参阅Android学习笔记(三一):线程:Message和Runnable中的例子5           
         @Override 
          public void run() {  
              String str = getCurTimeStr() + info; 
               Log.i("SIP",str); 
               tv.setText(str + "\n" + tv.getText()); 
          } 
    }); 
}

在程序结束之前,我们需要进行close,否则在程序再次开启open(),会报告createSipSession()的错误。我们另外需要进行的向SIP server进行unregister。有些SIP server不支持其他位置进行unregister,由于每次SipManager分配的本地SIP协议端口不同,如果不能在退出之前及时注销,那下次开启将不能进行unregister以及register,会回复403 Forbidden的错误。上面的代码,我们的注册有效时间设置为5分钟,但是SIP的缺省值是3600(1小时)。即便是5分钟,在调测程序时会让人抓狂的。程序退出,通常在onDestroy()中进行,我们加入下面的代码:

if(sipManager == null)
    return;
try{
    if(mySipProfile != null){
        //在人工注册,是无法获知是否注册成功,直接执行unregister即可。如果Sip Server支持异地unregister,可以在开启时,先unregister,然后register,但是合理的,我们仍应进行unregister。
        sipManager.unregister(mySipProfile, null); 
        Thread.sleep(2000); //unregister的完成需要时间,而且是在线程进行的,主线程会马上执行下面一句,即sipManager.close(),未完成unregister动作,就直接关闭掉sipManager,或者App就已经destory了,这里延迟2秒,运行允许线程完成unregister的处理
        sipManager.close(mySipProfile.getUriString());

    }
}catch(Exception e){
    Log.i("SIP","Close sipManager error:" + e.toString());
    e.printStackTrace();
}

让我们看看有关的交互信息,在sip server的Log如下:

----------------注册-----------------
MAIN> 14-09-11 15:36:45.293 [NET]: Received Message from 192.168.1.11:56794 len(431)
MAIN> REGISTER sip:192.168.1.10 SIP/2.0
MAIN> Call-ID: [email protected]
MAIN> CSeq: 6960 REGISTER
MAIN> From: <sip:[email protected]>;tag=1882649854
MAIN> To: <sip:[email protected]>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bKfbb820f0c65898834736fc1198523599363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: <sip:[email protected]:56794;transport=udp>
MAIN> Expires: 3600
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:45.304 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 401 Unauthorized
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bKfbb820f0c65898834736fc1198523599363735;rport
MAIN> From: <sip:[email protected]>;tag=1882649854
MAIN> To: <sip:[email protected]>;tag=908211099445681538
MAIN> Call-ID: [email protected]
MAIN> Cseq: 6960 REGISTER
MAIN> WWW-Authenticate: Digest realm="0.0.0.0",nonce="c756e447c91410421005304ed82e4646e",opaque="",stale=FALSE,algorithm=MD5
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:45.502 [NET]: Received Message from 192.168.1.11:56794 len(633)
MAIN> REGISTER sip:192.168.1.10:5060 SIP/2.0
MAIN> Call-ID: [email protected]
MAIN> CSeq: 6961 REGISTER
MAIN> From: <sip:[email protected]>;tag=1882649854
MAIN> To: <sip:[email protected]>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK5fc05d8ea72d360c6c15b86db1969a38363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: <sip:[email protected]:56794;transport=udp>
MAIN> Expires: 3600
MAIN> Authorization: Digest username="78000001",realm="0.0.0.0",nonce="c756e447c91410421005304ed82e4646e",uri="sip:192.168.1.10:5060",response="ce2a66e08ca377b0ebbc5783f1cd9515",algorithm=MD5,opaque=""
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:45.509 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 200 OK
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK5fc05d8ea72d360c6c15b86db1969a38363735;rport
MAIN> From: <sip:[email protected]>;tag=1882649854
MAIN> To: <sip:[email protected]>;tag=908211099445681538
MAIN> Contact: <sip:[email protected]:56794>;expires=3600
MAIN> Call-ID: [email protected]
MAIN> Cseq: 6961 REGISTER
MAIN> Content-Length: 0
MAIN>
MAIN>


----------------注销-----------------
MAIN> 14-09-11 15:36:46.650 [NET]: Received Message from 192.168.1.11:56794 len(380)
MAIN> REGISTER sip:192.168.1.10 SIP/2.0
MAIN> Call-ID: [email protected]
MAIN> CSeq: 456 REGISTER
MAIN> From: <sip:[email protected]>;tag=947417185
MAIN> To: <sip:[email protected]>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK2910eed7fc8666adf4d77f5eed54e6ef363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: *
MAIN> Expires: 0
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:46.662 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 401 Unauthorized
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK2910eed7fc8666adf4d77f5eed54e6ef363735;rport
MAIN> From: <sip:[email protected]>;tag=947417185
MAIN> To: <sip:[email protected]>;tag=444461521727471416
MAIN> Call-ID: [email protected]
MAIN> Cseq: 456 REGISTER
MAIN> WWW-Authenticate: Digest realm="0.0.0.0",nonce="91a98e159914104210066590de27c726a",opaque="",stale=FALSE,algorithm=MD5
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:46.806 [NET]: Received Message from 192.168.1.11:56794 len(582)
MAIN> REGISTER sip:192.168.1.10:5060 SIP/2.0
MAIN> Call-ID: [email protected]
MAIN> CSeq: 457 REGISTER
MAIN> From: <sip:[email protected]>;tag=947417185
MAIN> To: <sip:[email protected]>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK3dfab8e5a8763ac0efccd9b912ac89cc363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: *
MAIN> Expires: 0
MAIN> Authorization: Digest username="78000001",realm="0.0.0.0",nonce="91a98e159914104210066590de27c726a",uri="sip:192.168.1.10:5060",response="8b4b33d3bdccd20d6ffca2dcbe4b9d79",algorithm=MD5,opaque=""
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:46.813 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 200 OK
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK3dfab8e5a8763ac0efccd9b912ac89cc363735;rport
MAIN> From: <sip:[email protected]>;tag=947417185
MAIN> To: <sip:[email protected]>;tag=444461521727471416
MAIN> Call-ID: [email protected]
MAIN> Cseq: 457 REGISTER
MAIN> Content-Length: 0
MAIN>
MAIN>

自动注册的代码如下:

sipManager.setRegistrationListener(mySipProfile.getUriString(), new SipRegistrationListener() {
    public void onRegistering(String localProfileUri) {
        debug("Registering with SIP Server...");
    }
    public void onRegistrationDone(String localProfileUri, long expiryTime) { 
        debug("Ready...");
    }
    public void onRegistrationFailed(String localProfileUri, int errorCode, 
        String errorMessage) { 
        debug("Registration failed.  Please check settings. code=" + errorCode + ":" + errorMessage); 
     }
});

自动注册中,采用缺省的注册有效时间3600秒。在一开始会发送UNREGISTER消息,注销后,发送REGISTER消息。如果采用无监听在sipManager.close()的时候,没发现进行unregister。在注册成功后,会发送OPTIONS消息来检测sip server是否有效存在。如果OPTIONS消息没有得到回应,则会重新出发register流程。

如果我们采用了sipManager.open(mySipProfile);来开启,会报告注册失败,code=-10;no data connection,没有发送register消息,很是奇怪,但这是高度封装的包,很多内部处理我们并不清楚。

相关链接:我的Android开发相关文章

你可能感兴趣的:(Pro Android学习笔记(一二四):Telephony API(6):SIP Phone(上))