3.1 Fragment生命周期
前面的主界面介绍到了,当ViewPage切换到发送短信这个Tab的时候,会进入到ConversationListFragment这个Fragment中。说起Fragment,就必须了解Fragment与Activity在启动界面的时候是如何调用相应的方法的。其实,Fragment和Activity的生命周期类似,这是因为Fragment寄宿在Activity中运行的,因此宿主activity的生命周期直接影响到Fragment的生命周期,比如activity生命周期的回调函数调用时,所有在其中的fragment的相同的回调函数会同时被调用。如图
图15 Acitivty和Fragment的生命周期
从图15可看出,onAttach()在Fragment和activity被关联时被调用。OnCreate()当创建fragment时系统调用这个方法,在此实现中,需要初始化fragment重要的组件,这些在暂停或停止时保留,在这里恢复。OnCreateView()当fragment第一次绘制UI界面时,系统调用此方法。为fragment绘制UI,必须从此方法返回View对象,这个是fragment布局的基础,当不需要提供UI时,返回null。onActivityCreated()当activity的onCreate()方法或者fragment的onCreateView()返回时被调用。onCreateView(LayoutInflater, ViewGroup, Bundle)方法返回之后、之前被保存的View对象的状态被恢复之前,系统会立即调用onViewCreated方法。
下面具体讲解从SIP_Home到切换到ConverstaionListFragment这个Tab并显示内容以及具体的一些操作时如何进行的。如图16:
图16 主界面到信息收发这个Tab
在Sip_Home中,响应ViewPage滑动的事件处理方法是onPageScrollStateChanged()方法,一旦用户进行滑动,则进入onPageScrollStateChange()该方法,并判断响应的状态,这里进入空闲状态的处理语句,并调用sendFragmentVisibilityChange方法。而sendFragmentVisibilityChange的内容如图17:
/*
* 在activity创建之后,若activity没destroy,只是退出页面,当重新进入时,调用了onResume()方法
* ,接着调用根据上次停留的tab,进入sendFragmentVisibilityChange函数。显示某个fragment。
*/
private void sendFragmentVisibilityChange(int position, boolean visibility) {
try {
final Fragment fragment = getFragmentAt(position);
if (fragment instanceof ViewPagerVisibilityListener) {
System.out.println("IN SIP_HOME(sendFragmentVisibilityChange) before go to ConversationListFragment");
((ViewPagerVisibilityListener) fragment).onVisibilityChanged(visibility);
}
}catch(IllegalStateException e) {
Log.e(THIS_FILE, "Fragment not anymore managed");
}
}
图17 sendFragmentVisibilityChange方法
从图17可看出,根据position就可定位具体是那个fragment,这里讨论的是短信收发的fragment,所以进入的是ConversationsListFragment这个fragment的onVisibilityChanged方法,从而也就有了图16(右)的界面。
其实当我们从左图16切换到右图16的时候,实际上出现了一个界面,只不过因为速度很快,我们无法察觉,这个可以从Logcat调试观察到,这样有助于我们进一步了解Fragment的生命周期。ConversationsListFragment所调用的函数顺序如下图17:
图18 ConversationListFragment所调用的函数顺序
从图18可看出,在还没出现图16(右)的界面之前,已经调用了onCreateView()及接下来其他函数,然后回到SipHome调用了onAttachFragment()方法,最后因为进行了viewPage切换,进入到ConversationsListFragment的onVisibilityChanged方法。进入该fragment之后点击Button按钮,程序响应了addClickButtonListener事件,从而进入了MessageFragment,MessageFragment实例的初始化是在MessageAcitivty进行的,如下语句:MessageFragmentdetailFragment = new MessageFragment()。初始化MessageFragment顺序如图19所示:
图19MessageFragment方法顺序调用
由此我们进一步印证了Fragment的调用顺序如下:onAttach()-> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreated()-> onResume()。而在onViewCreated()方法中,我们打开了一个了一个PickSipUri的acitivty,其界面如图20:
图20 PickSipUri Activity
当添加完相应的Sip URI之后,回到MessageFragment的onActivityResult方法,因为设置的resultCode为PICKUP_SIP_URI,从PickupSipUri这个activity所携带的extract数据包括被叫的ID和URI,于是就有了如图21的红色矩形显示的结果:
图21 添加被叫SIPURI之后的MessageFragment
3.2 Andriod的进程间通信
接着在MessageFragment中,当编辑完短信信息之后,点击发送便开始进入发送短息之旅。而这个短信的发送涉及到进程间通信问题(IPC),所以在没讲解PJSUA是如何操作短信发送之前,先说明Andriod的进程间通信是如何实现的(IPC)。
我们知道,在linux中,进程间的通信有很多种,而在Andriod中,使用IPC机制实现进程间的通信。Andriod利用不同的组件(Activity,service)来表示进程间的通信,组件中通信的核心机制是Intent,通过Intent可以开启一个Activity或Service。通过消息机制实现的进程间通信,其有个弊端就是,若Activity与Service之间的交往不是简单的Activity开启Service操作,而是要随时发送一些控制请求,那么就必须要保证Activity在Service的运行过程随时可以接收到Service,而要实现这个要求,Andriod的IPC机制提供这样的解决手段,其只适应于Activity和Service之间的通信。类似于远程调用,像C/S模式的访问。而其使用也相对简单,通过定义AIDL接口文件来定义一个IPC接口,而Service端实现IPC接口以及Client端调用IPC接口的本地代理即完成IPC机制。
Andriod的AIDL模型如下:
Client端——>Proxy——>parcel数据包——> stub——>Server端。其中Proxy是运行在客户端的进程,STUB是运行在服务端的进程。当通过AIDL访问服务端时,客户端阻塞于Proxy,当服务端处理完之后,便通知Proxy返回。至于Parcel数据包,这里多啰嗦几句,在java中,我们使用Serializable来保存对象信息,而Andriod中即可使用Serializable又可使用parcel完成对象传递。而parcel相对Serializable,在Andriod系统中更稳定也多被使用。
进程间通信的例子,在CSipSimple间只进行了activity对Service的信息传递,而没有从Service到Activity间的通信。下面先举个和CSipSimple无关的简单例子,然后附带说下CSipSimple的IPC通信。
1. 准备两个AIDL文件,一个为forActivity.aidl,一个为forService.aidl。
其中forActivity.aidl如下:
Interface forforActivity{
void performAction();
}
forService.aidl如下:
interface forService{
void registerTestcall(forActivity cb);
void involkCallback();
}
2. 把上面两个AIDL生成.java文件
3. 编写一个mAIDLActivity.java文件;
i. 包括提供forActivity接口的内嵌类:
private forActivitymCallback = new forActivity.stub(){
//实现performAction方法
void performAction(){
System.out.println(“This’sforActivcity’s performAction()”);
}
};
ii. 建立与Service的连接:
forService mservice;
privateServiceConnection mConnection = new ServiceConnection(){
@Override
public voidonServiceConnected(ComponentName arg0, IBinder arg1) {
mService = forService.Stub.asInterface(arg1);
//接着在mAIDLAcitivty中调用forService的方法,完成注册
mService.registerTestCall(forActivity cb);
}
@Override
public voidonServiceDisconnected(ComponentName arg0) {
service = null;
}
};
iii. 一般在OnCreate中开启Intent,绑定service,这一步和基本的消息机制一样
getApplicationContext().bindService(newIntent(Context,mAIDLService.class),mConnection, Context.BIND_AUTO_CREATE);
iv. 用上面得到的mService调用forService里的involkCallBack函数,也就是说下面这个调用可以是在mAIDLAcivity中某个事件被触发之后调用该方法。
mService.involkCallback();
4. 一个mAIDLService.java文件:
包括提供forService接口的内嵌类:
privatefinal forService.stub mBinder = new forService.Stub(){
void registerTestCall(forActivitycb){
callback = cb;
}
void involkCallBack(){
callback.performAction();
}
};
5 测试结果,可以在mAIDLAcivity中加个Button按钮相应,然后在相应函数中调用mService. involkCallBack();最终从activity中到Service,而在Service中又回到Acivity,并最终调用了performAction(),从而完成输出结果。
解释完关于进程间的通信之后,接下来分析CSipSimple的进程间通信。在CSipSimple中,其实现不和上面的例子完全一样实现Activity和Service间的相互通信,而是在Acivity中调用了Service的方法,而Service并未回传信息到Service,而是通过ContentProvider数据的改变引起广播消息的发送,进而通过广播接收器进行相应的UI更新的。
在CSipSimple中,有多个AIDL文件,其中最主要的有ISipService.aidl文件,同时SipService完成了ISipService接口的相应方法。从MessageFragment中,即3.1节提到的,但点击发送按钮之后,将会到达这步,如图22所示:
private void sendMessage() {
if (service != null) {
SipProfile acc = accountChooserButton.getSelectedAccount(); //要发送的ID号,即和谁通信
if (acc != null && acc.id != SipProfile.INVALID_ID) {
try {
String textToSend = bodyInput.getText().toString();
if(!TextUtils.isEmpty(textToSend)) {
service.sendMessage(textToSend, remoteFrom, (int) acc.id); //客户端发送消息,经过服务器
bodyInput.getText().clear();
}
} catch (RemoteException e) {
Log.e(THIS_FILE, "Not able to send message");
}
}
}
}
图22 发送短息
上面的为在acitivty调用了service的方法,故进入SipService。如图23所示:
图23SipService的sendMessage方法
从上面的红色矩形圈中可看出,程序真正调用的是PjSipService的sendMessage()方法。其如图24所示。而蓝色部分就是编译在activity根据数据的变化进行相应的操作而设计的。这样不需要Service回传信息给Acitivty,而是通过ContentProvider实现。
/**
* Send sms/message using SIP server
*/
public ToCall sendMessage(String callee, String message, long accountId)
throws SameThreadException {
if (!created) {
return null;
}
ToCall toCall = sanitizeSipUri(callee, accountId);
if (toCall != null) {
pj_str_t uri = pjsua.pj_str_copy(toCall.getCallee());
pj_str_t text = pjsua.pj_str_copy(message);
/*
* Log.d(THIS_FILE, "get for outgoing"); int finalAccountId =
* accountId; if (accountId == -1) { finalAccountId =
* pjsua.acc_find_for_outgoing(uri); }
*/
// Nothing to do with this values
byte[] userData = new byte[1];
int status = pjsua.im_send(toCall.getPjsipAccountId(), uri, null, text, null, userData);
return (status == pjsuaConstants.PJ_SUCCESS) ? toCall : null;
}
return toCall;
}
图24 PjSipService的sendMessage方法
从图24中可看出,调用了Pjsua的im_send函数。如图25所示。而其最终调用而来JNI库的API im_send方法。
public synchronized static int im_send(int acc_id, pj_str_t to, pj_str_t mime_type, pj_str_t content, pjsua_msg_data msg_data, byte[] user_data) {
return pjsuaJNI.im_send(acc_id, pj_str_t.getCPtr(to), to, pj_str_t.getCPtr(mime_type), mime_type, pj_str_t.getCPtr(content), content, pjsua_msg_data.getCPtr(msg_data), msg_data, user_data);
}
图25 Pjsua的im_send方法
至此,CSipSimple的短息发送已完成。而我们仍然会有个疑问就是为什么得进行进程间通信。其实这里的理解是,因为涉及到SIP通信,所以凡是进行这样的网络通信的,我们一般不在Acitivty中进行,那么怎么办呢?而andriod的IPC机制既能达到这种要求,再者,要完成SIP通信后的信息共享,在andriod中使用进程间通信即可实现。另外,因为是IP电话,即希望能时时监控是否有电话进来等,那么如果是一般的消息通信机制,则不能达到这种效果。而进程间通信可以实现这种要求,因为IPC一旦开启且没关闭之前会处于监听状态。