CSimple程序之短信发送

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一旦开启且没关闭之前会处于监听状态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值