sip在sdk中有三个目录:server、net、telephony;
以下是针对net目录下Sip的分析:
net目录包含九个目标文件(SimpleSessionDescription、SipAudioCall、SipErrorCode、SipException、SipManager、SipProfile、SipRegistrationListener、SipSession、SipSessionAdapter)。
以上是针对net目录下Sip的分析,好了我们转到server目录下Sip的分析。server目录下有六个目标文件(SipHelper、SipService、SipSessionGroup、SipSessionListenerProxy、SipWakeLock、SipWakeupTimer)。
以上是针对server目录下Sip的分析,好了我们转到telephony目录下Sip的分析。telephony目录下有六个目标文件(SipCallBase、SipCommandInterface、SipConnectionBase、SipPhone、SipPhoneBase、SipPhoneFactory)。
如下是SipPhone实现相关类图。
SipPhone对象虽然也派生自PhoneBase,但实现机制及实例化过程与其它Phone对象大大不同。
SipPhone对象的实例化通过PhoneFactory的makeSipPhone的接口调用SipPhoneFactory的makePhone进行实例化。
SipPhone对象的实例化调用也不是在PhoneApp对象中进行,而是在默认电话应用的SipBroadcastReceiver对象的onReceive回调函数或者SipCallOptionHandler对象的createSipPhoneIfNeeded函数中被调用,另外不同的是对SipPhone对象的创建个数也没有限制。
虽然实例化过程和CDMAPhone、GSMPhone对象不同,但实例化的SipPhone对象和CDMAPhone、GSMPhone对象一样也被添加到CallManager对象中进行统一管理。
SipPhone实现机制是通过IP通道实现电话功能,而不是通过无线通讯模块,因此没有采用RIL和RIL daemon,因此框架实现层与CDMAPhone、GSMPhone有很大不同,SipPhone框架实现主要是通过SipService系统服务及调用第三方协议栈来完成。
SipService系统服务通过JNI来调用本地nist SIP协议堆栈。
SipPhone对象属于SipService服务的客户端对象,其通过SipManager对象的ISipService接口调用SipService。
每个SipPhone对象都对应一个SipProfile对象(包括SIP帐户,地址连接信息以及服务信息等信息)用来标识通话一方。
SipProfile对象在SipPhoneFactory调用makePhone实例化SipPhone对象前根据网络通话的sipUri构建,并作为makePhone的参数传给SipPhone对象。
每个SipPhone对象和其它具体Phone对象相同也包括三个Call对象(SipCall):ringingCall、foregroundCall、backgroundCall,每个SipCall对象也包含一个Connection类型的ArrayList对象,用来维护每个CALL拥有的通话连接。
不过和其它Phone对象不同SipPhone对象对每个Call对象拥有的connection个数没有限制,SipCall拥有的connection对象对应具体的SipConnection对象。
SipConnection对象包括主动和被动两种类型,主动类型的SipConnection对象在SipCall对象的dial函数调用时创建,被动类型的SipConnection对象在SipCall对象接收到输入CALL时,其initIncomingCall函数调用时创建。
创建的SipConnection对象添加到SipCall对象的Connection类型的ArrayList数组列表中进行管理。
每个SipConnection对象也包括一个SipProfile对象和一个SipAudioCall对象。
被动SipConnection类型的SipProfile对象和SipAudioCall对象从对方获得。
被动类型的SipAudioCall对象和initIncomingCall函数传进来的SipAudioCall对象相同,SipProfile对象通过调用SipAudioCall的getPeerProfile函数获得发起通话的对方的SipProfile。
主动SipConnection类型的SipProfile对象在dial函数中调用SipProfile.Builder对象的build函数根据发起的通话URL构建,用来标识本地通话方,SipAudioCall对象在调用dial函数时通过调用SipManager对象的makeAudioCall函数创建。
SipAudioCall对象中包括一个客户端SipSession对象,管理客户端的每一个会话过程,SipSession对象中有一个ISipSession接口成员,通过该接口调用服务端对应的会话对象SipSessionImpl对象(一个实现ISipSession接口的会话桩对象),共同完成通话一方的会话过程,客户端通过ISipSession接口向服务端的SipSessionImpl对象发起IPC调用。
主动类型的SipAudioCall对象对应的SipSession对象在makeAudioCall函数中调用createSipSession进行实例化,在makeAudioCall函数调用新创建SipAudioCall对象的makeCall函数时,SipSession对象作为makeCall函数的参数传给SipAudioCall对象。
主动类型的SipSession对象中的ISipSession类型的接口成员在createSipSession中通过调用SipService的createSession函数创建,返回服务端SipSessionImpl对象的远程调用对象接口。
SipService的createSession函数创建一个Sip会话对象的整个过程:
1、首先调用createGroup函数实例化一个SipSessionGroupExt对象,SipSessionGroupExt对象派生自SipSessionAdapter,而SipSessionAdapter是一个实现ISipSessionListener接口的桩类;
2、SipSessionGroupExt对象实例化时又调用createSipSessionGroup函数实例化一个SipSessionGroup对象;
3、SipSessionGroup对象实例化时由SipFactory工厂对象实例化底层协议栈类对象SipStack,并有SipStack对象创建一个SipProvider对象和一个SipHelper对象,SipSessionGroup对象本身登记为SipProvider对象的监听对象;
4、然后调用SipSessionGroupExt对象的createSession函数,内部实际调用SipSessionGroup对象的createSession函数,实例化SipSessionImpl对象,并返回实例化后的SipSessionImpl对象引用。
5、SipSessionImpl属于SipSessionGroup类的内部类,客户端发起的通话请求都通过服务端的SipSessionImpl对象封装成EventObject类型的命令发起异步处理请求(调用SipSessionImpl对象的doCommandAsync函数,doCommandAsync函数的参数是一个EventObject类型的对象),最终通过SipSessionGroup对象的SipHelper对象调用SIP协议栈的接口与通话对方交互。
6、每个SipSessionImpl会话对象被放到SipSessionGroup对象的HashMap中进行管理。每个实例化的SipSessionGroupExt对象放到SipService的HashMap中进行管理。
客户端也可以通过SipManager对象的open函数发起一个被动会话请求,过程为:
1、 SipManager对象的open函数调用SipService的open3函数,同时也实例化一个PendingIntent对象通过SipService的open3函数传给服务端的SipSessionGroupExt对象中;
2、 在open3函数中调用createGroup函数实例化SipSessionGroupExt对象,并进一步实例化SipSessionGroup对象;
3、 接着调用SipSessionGroup对象的openToReceiveCalls函数来实例化一个SipSessionCallReceiverImpl对象作为接收会话,SipSessionCallReceiverImpl派生自SipSessionImpl,SipSessionGroupExt对象作为函数参数传进openToReceiveCalls函数,并在SipSessionImpl构造函数时调用内部对象成员mProxy(SipSessionListenerProxy类,一个实现ISipSessionListener接口的桩类)的setListener函数,为内部ISipSessionListener类型的成员赋值。
4、 当接收到底层协议发来的会话请求时,监听底层事件的对象SipSessionGroup的processRequest函数被调用;
5、 接着调用接收会话对象SipSessionCallReceiverImpl的process函数,在process函数中判断接收到的事件请求是Request.INVITE时就调用processNewInviteRequest函数;
6、 在processNewInviteRequest函数中调用createNewSession实例化一个SipSessionImpl对象作为新的会话,新的会话状态为INCOMING_CALL。接着调用接收会话SipSessionCallReceiverImpl对象内部成员mProxy的onRinging函数,在onRinging函数中调用其成员mListener(ISipSessionListener类型)的onRinging函数,实际调用SipSessionGroupExt对象的onRinging函数;
7、 在SipSessionGroupExt对象的onRinging函数中通过SipSessionGroupExt对象内部客户端传进来的PendingIntent对象向客户端发送ACTION_SIP_INCOMING_CALL类型广播消息;
8、 客户端在接收到这个广播调用SipManager的takeAudioCall函数。takeAudioCall函数首先通过SipService接口getPendingSession获得一个服务端会话对象引用;接着实例化一个SipAudioCall对象(SipAudioCall对象对应的SipProfile对象调用服务端会话对象的getLocalProfile函数获得);然后根据服务端会话对象引用实例化一个客户端SipSession对象;并调用SipAudioCall对象的attachCall实现SipSession对象与SipAudioCall对象的绑定;
9、 最后调用现有的SipPhone对象的canTake函数,在SipPhone对象的canTake函数中调用SipPhone对象的ringingCall对象的initIncomingCall函数实例化一个SipConnection对象,完成通话建立过程。
Telephony框架的数据连接模块负责数据连接通道的建立,使电话能够提供数据服务,如上网等,数据连接模块的类图如下图。
DataConnectionTracker是数据连接功能的核心,GsmDataConnectionTracker、CdmaDataConnectionTracker是DataConnectionTracker的两个派生类,实现具体网络的数据连接的建立和管理。
每一个数据连接用一个DataConnection对象表示,DataConnection对应的具体网络的派生类为GsmDataConnection和CdmaDataConnection。DataConnectionTracker中用一个HashMap类型的变量mDataConnections维护每一个数据连接。CDMA同时只能建立一路数据连接,GSM网络则没有限制。
DataConnection对象是一个是一个状态机对象,维护连接的状态,并提供数据连接的LinkProperties和LinkCapabilities等属性。DataConnection对象的状态包括DcDefaultState、DcInactiveState、DcActivatingState、DcActiveState、DcDisconnectingState、DcDisconnectionErrorCreatingConnection六种状态。
对于GsmDataConnectionTracker对象,ApnContext对象提供数据连接的APN上下文,每一个APN类型都对应一个ApnContext对象,ApnContext对象维护对应的APN设置、DataConnectionTracker的状态、对应的DataConnection和DataConnectionAc等。
数据连接通道的建立过程:
1、 数据连接的建立最终都通过GsmDataConnectionTracker和CdmaDataConnectionTracker的trySetupData函数启动数据连接;
2、 trySetupData函数调用setupData函数,设置数据连接参数(如采用的Apn设置参数,连接建立成功响应消息);
3、 然后调用对应的DataConnection对象的bringUp函数发送数据连接消息(EVENT_CONNECT)。
4、 EVENT_CONNECT消息由DataConnection对象的状态机的相应状态对象接收处理,对于开始尚未建立数据连接时,DataConnection对象处于DcInactiveState状态,因此DcInactiveState状态对象接收处理EVENT_CONNECT事件,调用onConnect函数,并转变为DcActivatingState状态。
5、 在具体DataConnection对象的onConnect函数中调用RIL接口setupDataCall函数启动数据连接;
6、 连接建立后RIL层回应EVENT_SETUP_DATA_CONNECTION_DONE请求应答事件,由DataConnection对象的DcActivatingState状态对象接收处理EVENT_SETUP_DATA_CONNECTION_DONE事件,调用onSetupConnectionCompleted函数,并过渡到DcActiveState状态,连接建立成功。
DataConnectionTracker对象采用DataConnectionAc对象(派生自AsyncChannel)与DataConnection对象通讯。
smDataConnectionTracker对象在setupData函数调用createDataConnection函数创建时GsmDataConnection对象和DataConnectionAc对象,并通过DataConnectionAc对象与GsmDataConnection对象建立异步通讯连接。GsmDataConnectionTracker对象对于每类ApnContext都可以建立一个GsmDataConnection对象和一个DataConnectionAc对象。
CdmaDataConnectionTracker对象在实例化时调用createAllDataConnectionList函数创建需要的数据连接对象CdmaDataConnection和DataConnectionAc对象。CdmaDataConnectionTracker对象只支持创建一个CdmaDataConnection对象和DataConnectionAc对象。
整个Telephony事件通知框架包括三层: RIL消息层、框架事件处理层、应用层。类图如下图,主要是请求应答模式和观察者模式的采用。
整个框架层以PhoneBase为中心,向上通过PhoneNotifier接口向应用层发送框架层产生的的事件,应用层通过TelephonyRegistry接口提供对特定事件的监听,由PhoneNotifier接口的默认实现DefaultPhoneNotifier通过TelephonyRegistry对象向应用层发送事件通知。
向下框架层通过CommandsInterface接口注册Unsolicited事件(主动通知事件)及发起AT命令请求。框架层的事件处理对象包括CDMAPhone和GSMPhone两个对象本身及其包含的SMSDispatcher、IccFileHandler、DataConnectionTracker、IccRecords类型的对象以及PhoneBase中的SmsStorageMonitor对象。
这些对象都是Handler对象,都能够向RIL层注册Unsolicited事件及发起AT命令请求。也能够接收和处理RIL层产生的Unsolicited事件及AT命令的响应。
CDMAPhone和GSMPhone对象中的事件处理对象除了CallTracker及ServiceStateTracker对象外,其它都在PhoneBase中由基类实现。
向RIL层注册的Unsolicited事件都登记添加到BaseCommands类中的RegistrantList类型的对象中或者设置为BaseCommands类中的Registrant类型的对象(根据设置函数的参数实例化具体类型的Registrant类型的对象)。
RIL层产生的Unsolicited事件通过在BaseCommands类中登记的RegistrantList对象或设置的Registrant对象向框架层的事件处理对象发送Unsolicited事件。
框架层的事件处理对象向RIL层发送的命令直接发送给RIL对象的相应函数,向RIL层发送的命令中都带有一个Message类型的应答消息,RIL对象的相应函数把接收到的命令携带的参数封装进RILRequest请求中发送给RIL的RILSender对象并marshall后通过LocalSocket发送给rild进程。
RIL的RILReceiver对象通过相同LocalSocket通道收到AT命令的响应unmarshall后调用processSolicited()函数把应答结果封装进命令携带的应答消息中发送回框架层的事件处理对象。
RILSender对象和RILReceiver对象都实现了接口,在独立线程运行。
官方提供的实例中有关sip的就一个(SipDemo),下面是对它的分析:
一、基本概念
1、VOIP基于SIP协议,SDK2.3包含一个SIP协议栈和框架API
2、VOIP位于android.net.sip包中,最重要的为SipManager类,可开发基于SIP的VOIP应用。使用时要包含android.permission.INTERNET和android.permission.USE_SIP权限
3、如果在market中显示仅支持VOIP API幸好的手机的话,发布时需要在androidManifest.xml中加入<uses_feature android:name = "android.software.sip" android:required = "true">和<uses_feature android:name = "android.software.sip.voip">
4、要支持SIP API
(1)仅Android2.3或更高版本平台支持
(2)不是所有设备都提供SIP支持,确保你的APP只安装在支持SIP的装置上
5、根据GOOGLE官方DEMO项目来扩展的概念
二、类及方法描述
1、一个基本的VOIP项目至少需要三个类SIPSettings(对SIP的基本设置身份验证)、WalkieTalkieActivity(登录到SIP设备供应商,注册device去处理来电,拨打电话,在通话过程中用户界面管理)、IncomingCallReceiver(监听传入的SIP电话,然后传递这些SIP电话给WalkieTalkieActivity控制)
2、WalkieTalkieActivity
A、SipManager.newInstance()-->此方法中首先判断context是否支持SIP API,若支持则new SipManager。SipManager构造函数中,实例化了一个ISIPService(运用的公式:
IBinder b =ServiceManager.getService(Context.SIP_SERVICE); //获取系统相应的服务
ISipService service = ISipService.Stub.asInterface(bIBinder);)
上面这两句代码其实是使用了AIDL,就以SipService为例,步骤如下
Service端
1、编写aidl文件:ISipService.aidl,并定义使用的接口(就等同于interface一样)
2、使用makefile生成与之同名的JAVA文件,SipService.java,此类继承extends ISipService.Stub并实现接口定义的方法或者在SipService extends Service,并代码中加入
ISipService.stub sipImpl = new ISipService.stub(){
//实现其接口方法,在SipService.java中是实现了一个名为start()的方法,里面有句是ServiceManager.addService("sip",newSipService(context));表示SipService已经交给ServiceManager统一管理了
}
Client端
一(以SIPService为例)
1、而在需要用到SipService时,也就是我们构造SipManager的时候,就通过ServiceManager.getService(Context.SIP_SERVICE)获得SIP的服务(类型为IBinder)
2、并调用 ISipService.Stub.asInterface(IBinder);去获取一个SipService实例(前提是该Service一定是通过ServiceManager.addService的方式添加进去管理的,这样才能找到此Service)
二(以普通Activity为例)
1、利用Intent intent = new Intent(Activity.this,SipService.class);-->bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);来绑定SERVICE,在serviceConnection的onServiceConnected方法中,使用IService.stub.asIntentface(IBinder);来获取实例。
B、SipManager创建好后,先从SharedPreference中获取username,domain及pwd,如果第一次进来没有设置这些的话则需要先创建账户,这里用EditTextPreference来保存用户信息,好处是当填写信息并返回后,EditTextPreference会自动将值放入SharedPreference中。我们假设username="woody";domain="192.168.12.30";pwd="910913"。
C、这时,我们的SipManager以及用户信息已经设定好了,接下来使用了这句SipProfile.Builder builder = new SipProfile.Builder(username, domain);我们去看看SipProfile.Builder中做了些什么:
SipURI mUri =mAddressFactory.createSipURI(username,serverDomain);
SipProfile mProfile.mDomain=serverDomain; //设置domain
(在mAddressFactory.createSipURl方法中,我选取了一些核心代码)
StringBuffer uriString=new StringBuffer("sip:");
uriString.append(user);
uriString.append("@");
//if host is an IPv6 string we should enclose it in sq brackets
if(host.indexOf(':') !=host.lastIndexOf(':')&&host.trim().charAt(0) !='[')
host='['+host+']';
uriString.append(host);
StringMsgParser smp=new StringMsgParser();
SipUrl sipUri=smp.parseSIPUrl(uriString.toString());
return sipUri;
从以上代码可以看出其实就是在Format SipURL罢了,里面多加了个if host为IPV6的判断(IPv4为为32位,十进制;IPv6为128位,16进制)。urlString最后为"sip:[email protected]",smp.parseSIPUrl()方法中,有关于是如何parse的就不做阐述了,总之最后返回了一个SipUri
D、接下来就是SipProfile sipProfile = SipProfile.Builder.build(); //返回一个SipProfile object
在SipProfile.Builder.build()中,设置了sipProfile的pwd值,删除了之前SipUrl对象里的password(mUri.setUserPassword(null);)、将sipProfile的address属性设置为AddressImpl类型的对象值、调用AddressFactory.createURI返回一个SipUri,并sipProfile.mProxyAddress=sipUri.getHost();
E、创建PendingIntent对象:(Intent与PendingIntent区别在于Intent是及时启动,而PendingIntent是不立刻反应,在特定的情况或通知下才启动,适用于AlertClock等)
Intent i = new Intent();
i.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
F、SipManager.open(sipProfile,PendingIntent,null); //(实际是SIPService在做操作)设置localSIPProfile的callingID-->建立SIP连接(算是注册至SIP Server)-->打开receiveCall
其中建立SIP连接,最后能追溯到是在SipSessionGroup.java的reset()方法中通过是注册服务器实现的,
注册服务器的步骤为:
(1)设置服务器的属性,例如服务器的地址(IP_ADDRESS_PROP)、栈名(javax.sip.STACK_NAME)、发出去的路径(localProfile中的javax.sip.OUTBOUND_PROXY)、线程池的大小(gov.nist.javax.sip.THREAD_POOL_SIZE)等,并且将这些属性加载到服务器中.
(2)通过SipFactory的静态方法取得一个实例,然后通过SipFactory实例sipfactory
(3)创建一个SipStack实例sipstack(这一步获得IP_ADDRESS_PROP,String address = Properties.getProperty("javax.sip.IP_ADDRESS");)
(4)用sipstack创建一个SipProvider实例sipProvider
(5)注册SipListener
G、A~F步骤都是在做准备工作,大致的步骤如下:new SIPService-->new SIPManager-->设定用户信息-->new SIPURI-->new SIPProfile-->new PendingIntent-->set sipProfile callingID-->(if profile.getAutoRegistation)open toReceiveCalls-->register SipService 现在是call someone~呼叫的工作是SipAudioCall类来完成(可用sipManager.makeAudioCall或takeAudioCall来实例化,SipAudioCall.startAudio时需要 RECORD_AUDIO, ACCESS_WIFI_STATE, and WAKE_LOCK permissions,setSpeakerMode() 时需要MODIFY_AUDIO_SETTINGS permission)
【1】当需要呼叫时,使用sipManager.makeAudioCall(String localProfileURI, String peerProfileURI, SipAudioCall.listener,int timeout);来创建一个SipAudioCall,其中timeout以seconds为单位,过了timeout表示打电话超时。需要打给别人时使用makeAudioCall创建,接听电话用takeAudioCall来创建sipAudioCall
【2】SipAudioCall中有一个嵌套的class:SipAudioCall.Listener(此类主要用于监听SIP CALL,when[呼叫电话 or 接听电话])
SipAudioCall.Listener listener = new SipAudioCall.Listener() {
@Override
public void onCallEstablished(SipAudioCall call) { //呼叫建立
call.startAudio(); //启动音频
call.setSpeakerMode(true); //调整为可讲话模式
call.toggleMute(); //触发无声
updateStatus(call);
}
};
SipAudioCall call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);
(以上例子为makeAudioCall)
【3】我们看看makeAudioCall()方法(makeAudioCall requires 2 sipProfile):
SipAudioCall call =new SipAudioCall(mContext, localProfile);
call.setListener(listener); //这两句很简单就是创建一个local的sipAudioCall
SipSession s = createSipSession(localProfile, null); -->mSipService.createSession(localProfile, null);// sipService来创建session,并保存在SipSessionGroupExt中
call.makeCall(peerProfile,s,null); //这句就是呼叫,最后追溯到实际是SipSession.makecall
总结:在发起通话中
首先是创建SipAudioCall.listener,以便监听对话建立和对话结束,然后做相应的操作,然后是SipManager.makeAudioCall(localAdd,llistener,XXXX),在makeAudioCall方法中
A、创建一个sipAudioCall(localProfile)
B、创建SipSession以建立起会话
C、SipSession.makeCall(peerProfile,XXXX); //SipSession呼叫远程profile
【4】关于接电话道理都差不多,takeAudioCall通过之前设置的callingID来查找mSipService.getPendingSession(callId);来获得SipSession。并创建SipAudioCall,然后attachCall就算接受电话了。
三、总结
1、VOIP服务位于android.net.sip包中,关键类为SipManager。需要用到的permission列表,其中后面三个为使用WIFI获取IP地址所需要用到的权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-feature android:name = "android.hardware.sip.voip" android:required = "true"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />