2013年9月12日星期四

网盘中的秘密



之前国内各家大公司之间的网盘大战吵的沸沸扬扬,很多同学都在自己的博客上说今天领取了XXXT容量,1元XXXX,免费升级到XXT之类的,鸡冻你没发现dropbox这种网盘鼻祖却始终提供那么点容量,你们就没有想到过为什么么?我只想说,图样图森破啊~

事实是这样的~

我想要为每个用户提供 1G 的网络存储空间。

如果服务器上有一颗 1000G 的硬盘可以全部为用户提供数据储存,如果每个用户分配 1G 的最大储存空间,那么能非配给多少个用户使用呢?

你一定说是 1000/1=1000 个用户。

但事实上你这么分配了,你会发现每个用户平时根本不会上传 1G 的东西将容量占的漫漫的,有多又少,但平均用户平时只上传 50M 的文件,也就是说,你将 1000G 的硬盘分给 1000个 人使用,但只有效利用了其中的 50M*1000=50G 的空间,剩余 950G 的空间基本都完全浪费了。

那么怎么解决呢?

你可以变通一下,将这 1000G 的空间分配给 20000个 用户使用,每个人的上传上限容量还是 1G,但每人平时还是平均上传 50M 的数据,那么 20000*50M=1000G,这下子就把宝贵的服务器上的存储空间充分利用了。但你又怕这样分配给 20000个 人后,万一某一刻人们突然多上传点数据,那么用户不是就觉察出来你分给人家的 1G 空间是假的了吗?所以可以不分配那么多人,只分配给 19000 人,剩下一些空间做应急之用。

突然发现一下子将可分配的用户数量翻了 19倍啊,了不起。那还有买有办法更加有效的利用一下呢?

如果我有 1000个 以上的服务器,一个服务器上有 1000G 空间,那么我们个服务器上都要留下 50G 的空白空间以备用户突然上传大数据时导致数据塞满的情况,呢么我这 1000个服务器上就空出了 1000台*50G=50000G 的空间被浪费了,所么可惜。所以我们发明了计存储集群,使得一个用户的数据可以被分配在多个服务器上存储,但在用户那看起来只是一个 1G 的连续空间,那么就没必要在每个服务器上预留出应急的空间了,甚至可以充分的将前一个服务器塞满后,在将数据往下一个服务器中塞。这样保证了服务器空间的最大利用,如果某一刻管理员发现用户都在疯狂上传数据(在一个大规模用户群下,这样的概率少之又少)导致我现有提供的空间不够了,没关系,只需要随手加几块硬盘或者服务器就解决了。

好吧,这下子我们的服务器空间利用高多了,可以将一定量的空间分配给最多的用户使用了。但有没有更好的改进方案呢?

管理员有一天发现,即使每个用户平局下来只存储 50M 的东西,但这 50M 也不是一蹴而就的,是随着1-2年的使用慢慢的达到这个数量的,也就是说,一个新的用户刚刚注册我的网络空间时,不会上传东西,或者只上传一点非常小的东西。那么我为每一个用户都初始分配了 50M 的空间,即使将来2年后他们会填满这 50M ,但这期间的这空间就有很多时浪费的啊。所以聪明的工程师说:既然我们可以分布式、集群式存储,一个用户的数据可以分布在多个服务器上,那么我们就假设一开始就给一个新注册的用户提供 0M 的空间,将来他用多少,我就给他提供多少存储空间,这样就彻底的保证硬盘的利用了。但用户的前端还是要显示 1G 的。

工程师的这个点子,使得我在建立网盘初期能用 1台 1000G 的服务器提供了大约 1000000 人来注册和使用,随着注册的人多了,我也有钱了,也可以不断增加服务器以提供他们后期的存贮了。同时因为一部分服务器完了一年多购买,我的购买成本也下来了。

那么…这结束了吗?

若是邮箱提供商的话,这样的利用率够高了。但网盘就不一样了。

聪明的工程师发现:不同于邮箱,大家的内容的附件绝大多数都是自创的和不同的。但网盘上大家上传的东西很多都是重复的。

比如:张三 今天下载了一部《TOKYO HOT》上传上传到了自己的网盘上,李四在三天后也下载了一模一样的《TOKYO HOT》上传到了网络硬盘上,随着用户的增多,你会发现总计有 1000个人 上传了 1000份 一模一样的文件到你宝贵的服务器空间上,所以工程师想出一个办法,既然是一样的文件,我就只存一份不久好啦,然后在用户的前端显示是没人都有一份不久行啦。当某些用户要删除这个文件的时候,我并不真的删除,只需要在前端显示似乎删除了,但后端一直保留着以供其他拥有此文件的用户下载。直到所有使用此文件的用户都删除了这个文件我再真的将其删除吧。

这样子随着存储的数据越来越多,注册的用户越来越多,其上传的重复数据越来越多。你发现这样的检测重复文件存储的效率越来越大。这样算下来似乎每个人上传的不重复的文件只能平均 1M/用户。这下子你可以提供超过 50倍 的用户使用您这有限的空间了。

但伴随这使用,你又发现一个规律:

张三上传的《TOKYO HOT N0124》和李四上传的《TH n124》是同一个文件,只不过文件名不一样,难道我就不能识别出他们是一个文件,然后只将其分别给不同的用户保存成不同的文件名不久行啦?确实可行,但这要利用一些识别文件相同性的算法,例如 MD5 值等。只要两个文件的 MD5 值一样,文件大小一样,我就认为它们是相同的文件,只需要保存一份文件并给不同的用户记作不同的文件名就好了。

有一天你发现,因为每一个文件都需要计算 MD5 值,导致 CPU 负荷很大,而且本来一样的文件非要浪费带宽上传回来才可以检测一致性,能改进一下吗?

聪明的工程师写了个小软件/.小插件,美其名曰“上传控件”,将计算 MD5 的工作利用这个软件交给了上传用户的点老来完成,一旦计算出用户要上传的数据和服务器上已经存储的某个数据是一样的,就干脆不用上传了,直接在用户那里标记上这个文件已经按照 XX 文件名上传成功了。这个过程几乎是瞬间搞定了,并给其起了个高富帅的名字“秒传”!

通过以上这么多步骤,你发现本来你只能给 1000用户 提供网络空间的,这么多改进办法后,在用户端显示 1G 空间不变的情况下,近乎可以为 1000000个用户 提供网络空间了。

这样若是您哪天心情好,对外宣传说:我要将每个用户的存储空间上限提升到 1TB。那么每个用户平均还是只上传 50M 数据,只有极个别极个别的用户上传了突破 1G 原始空间的数据,你会发现所付出的成本近乎是微乎其微的。

辛勤的工程师还在为如何更有效率的利用服务器提供的磁盘空间在不屑努力和挖掘着……

2013年9月8日星期日

Android Sip学习(三)Android Voip实现



发布时间:2013-08-08 17:03:03  来源:束洋洋博客  作者:束洋洋
Android Sip学习(准备知识)SIP 协议完整的呼叫流程
Android Sip学习(一)Android 2.3 APIs SIP-based VoIP
Android Sip学习(二)Android VoIP系统实现原理
Android Sip学习(三)Android Voip实现
Android Sip学习(四)Android自带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(运用的公式:
Java Code复制内容到剪贴板
  1. IBinder b =ServiceManager.getService(Context.SIP_SERVICE);  //获取系统相应的服务  
  2. 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中做了些什么:
 
Java Code复制内容到剪贴板
  1. SipURI mUri =mAddressFactory.createSipURI(username,serverDomain);  
  2. SipProfile mProfile.mDomain=serverDomain; //设置domain  
  3. (在mAddressFactory.createSipURl方法中,我选取了一些核心代码)  
  4. StringBuffer uriString=new StringBuffer("sip:");   
  5. uriString.append(user);   
  6. uriString.append("@");   
  7. //if host is an IPv6 string we should enclose it in sq brackets  
  8. if(host.indexOf(':') !=host.lastIndexOf(':')&&host.trim().charAt(0) !='[')   
  9.    host='['+host+']';   
  10. uriString.append(host);  
  11. StringMsgParser smp=new StringMsgParser();   
  12.   
  13. SipUrl sipUri=smp.parseSIPUrl(uriString.toString());   
  14. return sipUri;  
从以上代码可以看出其实就是在Format SipURL罢了,里面多加了个if host为IPV6的判断(IPv4为为32位,十进制;IPv6为128位,16进制)。urlString最后为"sip:woody@192.168.12.30",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等)
 
Java Code复制内容到剪贴板
  1. Intent i = new Intent();  
  2. i.setAction("android.SipDemo.INCOMING_CALL");  
  3. PendingIntent pi = PendingIntent.getBroadcast(this0, i, Intent.FILL_IN_DATA);  
F、
SipManager.open(sipProfile,PendingIntent,null); //(实际是SIPService在做操作)设置localSIPProfile的callingID-->建立SIP连接(算是注册至SIP Server)-->打开receiveCall
其中建立SIP连接,最后能追溯到是在SipSessionGroup.java的reset()方法中通过是注册服务器实现的,如下图
QQ截图20130808164903.jpg
注册服务器的步骤为:
(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 接听电话])
 
Java Code复制内容到剪贴板
  1. SipAudioCall.Listener listener = new SipAudioCall.Listener() {  
  2.                   @Override  
  3.                   public void onCallEstablished(SipAudioCall call) { //呼叫建立  
  4.                       call.startAudio(); //启动音频  
  5.                       call.setSpeakerMode(true); //调整为可讲话模式  
  6.                       call.toggleMute(); //触发无声  
  7.                       updateStatus(call);  
  8.                   }  
  9.   
  10.   
  11. };  
  12. SipAudioCall call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);  

(以上例子为makeAudioCall)
【3】我们看看makeAudioCall()方法(makeAudioCall requires 2 sipProfile):
 
Java Code复制内容到剪贴板
  1. SipAudioCall call =new SipAudioCall(mContext, localProfile);  
  2. call.setListener(listener);  //这两句很简单就是创建一个local的sipAudioCall  
  3. SipSession s = createSipSession(localProfile, null); -->mSipService.createSession(localProfile, null);//  sipService来创建session,并保存在SipSessionGroupExt中  
  4. 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地址所需要用到的权限:
XML/HTML Code复制内容到剪贴板
  1. <uses-permission android:name="android.permission.INTERNET"></uses-permission>  
  2. <uses-permission android:name="android.permission.USE_SIP"/>  
  3. <uses-feature android:name = "android.hardware.sip.voip" android:required = "true"/>  
  4. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  5. <uses-permission android:name="android.permission.WAKE_LOCK" />  
  6. <uses-permission android:name="android.permission.RECORD_AUDIO" />  

2、类之间的关系
图片1.jpg
图片2.jpg

另外在这里给大家分享一个查看源码好用的工具SourceInsight3.5序列号+汉化【点我下载

windows下CSipSimple编译【带最新源码】

windows下CSipSimple编译【带最新源码】


发布时间:2013-07-25 16:40:05  来源:束洋洋博客  作者:束洋洋
下载后得到这样的文件:QQ截图20130725163926.jpg
解压目录后得到CSipSimple源码文件图.jpg
eclipse指定目录到这个文件夹,导入记得用这个格式:1111.jpg而非导入Android项目,记得导入的时候要两个一起导入。
导入以后,基本上就可以了,有什么问题,请Clean或者Fix Project。如果还报错,那么请右键项目CSipSimple,选择Android Tools-Add Support Libary……自动下载缺少的安卓包。
2222.jpg

好了之后,大家可以导出APK,安装到模拟设备上看看,给大家演示下
333333.jpg
444444.jpg
有什么问题欢迎在下面留言。
 
 

Android Sip学习(四)Android自带SipDemo详解



发布时间:2013-08-14 12:35:38  来源:束洋洋博客  作者:束洋洋
前几篇从Android的Voip官网支持功能到Android Voip系统实现原理到VOIP的实现,相信大家已经对Voip有了大概的了解了吧?什么,还不了解,没关系,本节给大家看下Android的SDK里自带的SipDemo,这就算实践了吧,接下来会慢慢学习怎么自己做基于SIP协议栈的软电话,希望可以做到像CsipSimple那样。
Android Sip学习(准备知识)SIP 协议完整的呼叫流程
Android Sip学习(一)Android 2.3 APIs SIP-based VoIP
Android Sip学习(二)Android VoIP系统实现原理
Android Sip学习(三)Android Voip实现
Android Sip学习(四)Android自带SipDemo详解
会话发起协议
Android提供了一个支持会话发起协议(SIP)的API,这可以让你添加基于SIP的网络电话功能到你的应用程序。Android包括一个完整的SIP协议栈和集成的呼叫管理服务,让应用轻松无需管理会话和传输层的沟通就可设置传出和传入的语音通话,或直接音频记录或播放。
以下类型的应用程序可能使用SIP API:
视频会议。
即时消息。

条件和限制
以下是开发一个SIP应用程序的条件:
你必须有一个运行Android2.3或者更高版本的移动设备。
SIP是通过无线数据连接来运行的,所以你的设备必须有一个数据连接(通过移动数据服务或者Wi-Fi)。这意味着你不能在模拟器(AVD)上进行测试,只能在一个物理设备上测试。每一个参与者在应用程序的通信会话过程中必须有一个SIP账户。有很多不同的SIP服务提供商提供SIP账户。
SIP API类和接口
以下是Android SIP API中包含的一些类和一个接口(SipRegistrationListener)的概述:
类/接口描述
 SipAudioCall 通过SIP处理网络音频电话
 SipAudioCall.Listener 关于SIP电话的事件监 听器,比如接受到一个电话(on ringing)或者呼出一个电话(on calling)的时候
 SipErrorCode 定义在SIP活动中返回的错误代码
 SipManager 为SIP任务提供APIs,比如初始化一个SIP连接。提供相关SIP服务的访问。
 SipProfile 定义了SIP的相关属性,包含SIP账户、域名和服务器信息
 SipProfile.Builder 创建SipProfile的帮助类
 SipSession 代表一个SIP会话,跟SIP对话框或者一个没有对话框的独立事务相关联
 SipSession.Listener 关于SIP会话的事件监 听器,比如注册一个会话(on registering)或者呼出一个电话(on calling)的时候
 SipSession.State 定义SIP会话的声明,比如“注册”、“呼出电话”、“打入电话”
 SipRegistrationListener 一个关于SIP注册事件监 听器的接口

 创建Manifest文件
如果你开发一个用到SIP API的应用程序,记住它需要Android2.3(API9)或者更高版本的平台的支持。所以在你的设备上要运行Android2.3(API9)或者更高的版本,并不是所有的设备都提供SIP的支持。
为了使用SIP,需要添加以下权限到你的manifest文件:
android.permission.USE_SIP
android.permission.INTERNET

为了确保你的应用程序能够安装到支持SIP的设备上,你需要添加以下内容到你应用程序的manifest文件里:
<uses-sdk android:minSdkVersion="9" />.这个设置表明你的应用程序需要Android2.3或者更高版本的平台。详情请参考API Levels和<uses-sdk>元素相关的文档。
为了控制你的应用程序被那些不支持SIP的设备过滤掉(比如:在Google Play),你需要添加以下内容到你应用程序的manifest文件里:
<uses-feature android:name="android.hardware.sip.voip" />. 这个设置声明了你的应用程序用到了SIP API。这个声明还应该包含一个android:required 属性来表明你是否想让你的应用程序被那些不提供SIP支持的设备过滤掉。其他<uses-feature>声明你也可能需要,具体取决于你的实现,详情请参考<uses- feature> 元素相关的文档。
如果你的应用程序设计用来接受呼叫,那么你还必须在应用程序的manifest文件里定义一个接收器(BroadcastReceiver 的子类):
<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver"/>
以下是从SipDemo项目manifest文件中摘录的内容:
XML/HTML Code复制内容到剪贴板
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.   
  3. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  4.           package="com.example.android.sip">  
  5. ……省略此部分
  6.   
  7.         <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver"/>  
  8.     </application>  
  9.     <uses-sdk android:minSdkVersion="9" />  
  10.     <uses-permission android:name="android.permission.USE_SIP" />  
  11.     <uses-permission android:name="android.permission.INTERNET" />  
  12.     <uses-permission android:name="android.permission.VIBRATE" />  
  13.     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  14.     <uses-permission android:name="android.permission.WAKE_LOCK" />  
  15.     <uses-permission android:name="android.permission.RECORD_AUDIO" />  
  16.   
  17.     <uses-feature android:name="android.hardware.sip.voip" android:required="true" />  
  18.     <uses-feature android:name="android.hardware.wifi" android:required="true" />  
  19.     <uses-feature android:name="android.hardware.microphone" android:required="true" />  
  20. </manifest>  

创建一个SipManager对象
要想使用SIP API,你的应用程序需要创建一个SipManager对象,这个SipManager对象在你的应用程序里负责以下内容:
发起SIP会话
发起和接受呼叫
在SIP provider里进行注册和注销
验证会话的连通性

你可以像下面一样实例化一个新的SipManager对象:
Java Code复制内容到剪贴板
  1. public SipManager mSipManager = null;  
  2. ...  
  3. if(mSipManager == null) {  
  4.     mSipManager = SipManager.newInstance(this);  
  5. }  

在SIP服务器上进行注册
一个典型的Android SIP应用中包含一个或多个用户,他们中的每个人都有一个SIP账户。在Android SIP应用中,每一个SIP账户代表一个SipProfile对象。
一个SipProfile对象定义了一个SIP的概要文件,包括SIP账户、域名和服务器信息。跟正在这个设备上运行应用的SIP账户相关联的概要文件被称之为本地配置文件。与会话相连接的概要文件被称之为对应配置文件。当你的SIP应用通过本地SipProfile登录到SIP服务器的时候,这就有效的注册当前设备为基站来发送SIP呼叫到你想呼叫的SIP地址。
本节展示了如何创建一个SipProfile,以及如何把刚创建的SipProfile注册到SIP服务器上,并且跟踪注册事件。 你可以像以下一样创建一个SipProfile对象:
Java Code复制内容到剪贴板
  1. public SipProfile mSipProfile = null;  
  2. ...  
  3.   
  4. SipProfile.Builder builder = new SipProfile.Builder(username, domain);  
  5. builder.setPassword(password);  
  6. mSipProfile = builder.build();  

接下来的代码摘录本地配置文件,用于呼出电话和/或接收通用的SIP电话。呼叫器可以通过mSipManager.makeAudioCall来呼出后续电话。这段摘录同样设置了一个android.SipDemo.INCOMING_CALL行动,这个行动会被一个intent过滤器来使用,当前设备接收到一个呼叫。以下是注册步骤:
Java Code复制内容到剪贴板
  1. Intent intent = new Intent();  
  2.  intent.setAction("android.SipDemo.INCOMING_CALL");  
  3.  PendingIntent pendingIntent = PendingIntent.getBroadcast(this0, intent, Intent.FILL_IN_DATA);  
  4.  mSipManager.open(mSipProfile, pendingIntent, null);  

最后这段代码在SipManager上设置了一个SipRegistrationListener 监 听器,这个监 听器会跟踪SipProfile是否成功的注册到你的SIP服务提供者。
Java Code复制内容到剪贴板
  1. mSipManager.setRegistrationListener(mSipProfile.getUriString(), new SipRegistrationListener() {  
  2.   
  3. public void onRegistering(String localProfileUri) {  
  4.     updateStatus("Registering with SIP Server...");  
  5. }  
  6.   
  7. public void onRegistrationDone(String localProfileUri, long expiryTime) {  
  8.     updateStatus("Ready");  
  9. }  
  10.   
  11. public void onRegistrationFailed(String localProfileUri, int errorCode,  
  12.     String errorMessage) {  
  13.     updateStatus("Registration failed.  Please check settings.");  
  14. }  

当你的应用程序使用完一个profile的时候,你应该关闭它来释放相关联的对象到内存中以及从服务器上注销当前设备。例如:
Java Code复制内容到剪贴板
  1. public void closeLocalProfile() {  
  2.     if (mSipManager == null) {  
  3.       return;  
  4.     }  
  5.     try {  
  6.        if (mSipProfile != null) {  
  7.           mSipManager.close(mSipProfile.getUriString());  
  8.        }  
  9.      } catch (Exception ee) {  
  10.        Log.d("WalkieTalkieActivity/onDestroy""Failed to close local profile.", ee);  
  11.      }  
  12. }  

拨打一个语音电话
要想拨打一个语音电话,你需要准备如下条件:
一个发起呼叫电话的SipProfile对象(本地配置文件)和一个用来接收呼叫的有效的SIP地址(对应配置文件);
一个SipManager对象。

要想拨打一个语音电话,你应该建立一个SipAudioCall.Listener监 听器。大部分客户与SIP堆栈的交互都是通过监 听器来发生的。在这一小段你将会看到SipAudioCall.Listener监 听器是如何在呼叫制定之后建立事务的:
Java Code复制内容到剪贴板
  1. SipAudioCall.Listener listener = new SipAudioCall.Listener() {  
  2.   
  3.    @Override  
  4.    public void onCallEstablished(SipAudioCall call) {  
  5.       call.startAudio();  
  6.       call.setSpeakerMode(true);  
  7.       call.toggleMute();  
  8.          ...  
  9.    }  
  10.   
  11.    @Override  
  12.    public void onCallEnded(SipAudioCall call) {  
  13.       // Do something.  
  14.    }  
  15. };  

一旦你创建了这个SipAudioCall.Listener监 听器,你就可以拨打电话了,SipManager对象里的makeAudioCall方法接受以下参数:
一个本地SIP配置文件(呼叫方)
一个相对应的SIP配置文件(被呼叫方)
一个用来监听从SipAudioCall发出的呼叫事件的SipAudioCall.Listener,这个参数可以为null,但是如上所说,一旦呼叫电话制定,这个监 听器将被用来创建事务
(超时的值,以秒为单位)

例如:
Java Code复制内容到剪贴板
  1. call = mSipManager.makeAudioCall(mSipProfile.getUriString(), sipAddress, listener, 30);  

接收呼叫
为了接收呼叫,SIP应用程序必须包含一个BroadcastReceiver的子类,这个子类得有能力响应一个表明有来电的intent。因此你需要在你的应用程序里做如下事情:
在AndroidManifest.xml文件中声明一个<receiver>元素。在SipDemo项目中,<receiver>元素是这样的<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver"/>
实现BroadcastReceiver的子类,在SipDemo中,这个子类是IncomingCallReceiver
通过挂起一个intent来初始化本地配置文件(SipProfile),当有人呼叫你的时候,这个挂起的intent会调用你的接收器。
创建一个intent过滤器,这个过滤器通过标志着来电的行动来进行过滤。在SipDemo中,这个action是android.SipDemo.INCOMING_CALL

实现BroadcastReceiver的子类
为了接收呼叫,你的SIP应用必须实现BroadcastReceiver的子类。当Android系统接收到一个呼叫的时候,他会处理这个SIP呼叫,然后广播一个来电intent(这个intent由系统来定义),以下是SipDemo中实现BroadcastReceiver子类的代码。(如果想查看完整的例子,你可以下载官网的SDK,里面自带例子从android-10就开始有这个功能,下面是我的电脑截图)
QQ截图20130814145342.jpg
Java Code复制内容到剪贴板
  1. /*** Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity. 
  2.  */  
  3. public class IncomingCallReceiver extends BroadcastReceiver {  
  4.     /** 
  5.      * Processes the incoming call, answers it, and hands it over to the 
  6.      * WalkieTalkieActivity. 
  7.      * @param context The context under which the receiver is running. 
  8.      * @param intent The intent being received. 
  9.      */   
  10.     @Override  
  11.     public void onReceive(Context context, Intent intent) {  
  12.         SipAudioCall incomingCall = null;  
  13.         try {  
  14.             SipAudioCall.Listener listener = new SipAudioCall.Listener() {  
  15.                 @Override  
  16.                 public void onRinging(SipAudioCall call, SipProfile caller) {  
  17.                     try {  
  18.                         call.answerCall(30);  
  19.                     } catch (Exception e) {  
  20.                         e.printStackTrace();  
  21.                     }  
  22.                 }  
  23.             };  
  24.             WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;  
  25.             incomingCall = wtActivity.mSipManager.takeAudioCall(intent, listener);  
  26.             incomingCall.answerCall(30);  
  27.             incomingCall.startAudio();  
  28.             incomingCall.setSpeakerMode(true);  
  29.             if(incomingCall.isMuted()) {  
  30.                 incomingCall.toggleMute();  
  31.             }  
  32.             wtActivity.call = incomingCall;  
  33.             wtActivity.updateStatus(incomingCall);  
  34.         } catch (Exception e) {  
  35.             if (incomingCall != null) {  
  36.                 incomingCall.close();  
  37.             }  
  38.         }  
  39.     }  
  40. }  

创建一个用来接收呼叫的intent过滤器
当SIP服务接收到一个新的呼叫的时候,他会发送一个intent,这个intent会附带一个由应用程序提供的action。在SipDemo项目中,这个action是android.SipDemo.INCOMING_CALL。
以下从SipDemo中摘录的代码展示了如何通过挂起一个基于android.SipDemo.INCOMING_CALL action的intent来创建SipProfile对象的。PendingIntent对象将执行一个广播当SipProfile接收到一个呼叫的时候:
Java Code复制内容到剪贴板
  1. public SipManager mSipManager = null;  
  2. public SipProfile mSipProfile = null;  
  3. ...  
  4.   
  5. Intent intent = new Intent();   
  6. intent.setAction("android.SipDemo.INCOMING_CALL");   
  7. PendingIntent pendingIntent = PendingIntent.getBroadcast(this0, intent, Intent.FILL_IN_DATA);   
  8. mSipManager.open(mSipProfile, pendingIntent, null);  

上面被执行的广播如果被intent过滤器拦截的话,这个intent过滤器将会启动声明过的Receiver(IncomingCallReceiver)。你可以在你的应用程序里的manifest文件中指定一个intent过滤器,或者通过代码来指定一个intent过滤器,就像SipDemo项目中Activity中的onCreate()方法一样:
Java Code复制内容到剪贴板
  1. public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {  
  2. ...  
  3.     public IncomingCallReceiver callReceiver;  
  4.     ...  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.   
  9.        IntentFilter filter = new IntentFilter();  
  10.        filter.addAction("android.SipDemo.INCOMING_CALL");  
  11.        callReceiver = new IncomingCallReceiver();  
  12.        this.registerReceiver(callReceiver, filter);  
  13.        ...  
  14.     }  
  15.     ...  
  16. }  

测试SIP应用程序
要测试SIP应用程序的话,你需要以下条件:
一个运行Android2.3或者更高版本的移动设备。SIP通过无线来运行,所以你必须在一个真正的设备上测试,在AVD上是测试是行不通的
一个SIP账户,有很多不同的提供SIP账户的SIP服务提供商。
如果你要打电话,这个电话必须是有效的SIP账户。
测试一个SIP应用程序的步骤:
让你的设备连接到无线(设置>无线&网络>Wi-Fi>Wi-Fi设置)
设置你的移动设备进行测试,就像在Developing on a Device里描述的一样
在你的移动设备上运行程序,就像在Developing on a Device里描述的一样
如果你正在使用Eclipse,你可以在Eclipse中查看应用程序的日志输出(Window > Show View > Other > Android > LogCat)。