CSipSimple短信发送机制探究

CSipSimple是运行在android设备上的一个开源的sip协议应用程序,介绍就不多说了网上一堆,有兴趣的童鞋可以研究下,项目地址为:http://code.google.com/p/csipsimple/ 

本文将就其中的短信发送机制进行大致分析。

项目中,短信发送利用了AIDL方法来实现。aidl是 Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它来定义进程间的通信接口,完成IPC(Inter-Process Communication,进程间通信)。

  • 创建.aidl文件

ISipService.aidl内容如下:
 1 /**
 2  * Copyright (C) 2010-2012 Regis Montoya ( aka r3gis - www.r3gis.fr)
 3  * This file is part of CSipSimple.
 4  *
 5  *  CSipSimple is free software: you can redistribute it and/or modify
 6  *  it under the terms of the GNU General Public License as published by
 7  *  the Free Software Foundation, either version 3 of the License, or
 8  *  (at your option) any later version.
 9  *  If you own a pjsip commercial license you can also redistribute it
10  *  and/or modify it under the terms of the GNU Lesser General Public License
11  *  as an android library.
12  *
13  *  CSipSimple is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
20  * 
21  *  This file and this file only is also released under Apache license as an API file
22  */
23 package com.csipsimple.api;
24 import com.csipsimple.api.SipProfileState;
25 import com.csipsimple.api.SipCallSession;
26 import com.csipsimple.api.MediaState;
27 
28 interface ISipService{
29    ……
30    // SMS
31    void sendMessage(String msg, String toNumber, long accountId);
32    ……
33 }

ISipService.aidl中定义了包含sendMessage方法的接口ISipService。

  • 自动编译生成java文件

eclipse中的ADT插件会自动在aidl文件中声明的包名目录下生成java文件,如下图所示:

ISipService.java
 1 /*
 2  * This file is auto-generated.  DO NOT MODIFY.
 3  * Original file: C:\\Users\\wayne\\workspace\\SipHome\\src\\com\\ csipsimple\\api \\ISipService.aidl
 4  */
 5 package com.csipsimple.api;
 6 public interface ISipService extends android.os.IInterface
 7 {
 8 ……
 9 // SMS
10 
11 public void sendMessage(java.lang.String msg, java.lang.String toNumber, long accountId) throws android.os.RemoteException;
12 }

接下来就是实现ISipService.aidl中定义的接口,提供接口的实例供客户端调用。这里以调用关系为主线,依次进行分析。

  • IPC实现

首先在项目中,发送短信调用方法

void com.csipsimple.api.ISipService.sendMessage(String msg, String toNumber, long accountId)
由此入手,我们结合代码一层层来查看具体的调用。
目录:src\com\csipsimple\ui\messages
MessageFragment.java
 1     // Service connection
 2     private ISipService service;
 3     private ServiceConnection connection = new ServiceConnection() {
 4         @Override
 5         public void onServiceConnected(ComponentName arg0, IBinder arg1) {
 6             service = ISipService.Stub.asInterface (arg1 );
 7         }
 8 
 9         @Override
10         public void onServiceDisconnected(ComponentName arg0 ) {
11             service = null;
12         }
13     };
14     
15     ...
16     
17     private void sendMessage() {
18         if (service != null) {
19             SipProfile acc = accountChooserButton.getSelectedAccount();
20             if (acc != null && acc .id != SipProfile.INVALID_ID) {
21                 try {
22                     String textToSend = bodyInput.getText(). toString();
23                     if(!TextUtils .isEmpty (textToSend )) {
24                      String userName = remoteFrom.substring( remoteFrom.indexOf(":" )+1, remoteFrom.indexOf( "@"));
25                      String suffix_userName = remoteFrom.substring( remoteFrom.indexOf("@" ), remoteFrom.length());
26                      
27                      String [] userNames = new String [5];
28                      userNames = userName .split (";");
29                       for(int i=0; i <userNames .length ; i ++){
30                          userNames [i ] = "sip:" + userNames [i ] + suffix_userName;
31                          
32                             service .sendMessage (textToSend , userNames[i], (int) acc.id);
33                       }
34                         bodyInput .getText ().clear ();
35                     }
36                 } catch (RemoteException e ) {
37                     Log .e (THIS_FILE , "Not able to send message");
38                 }
39             }
40         }
41     }
这里的调用需要先了解Service的机制,稍后下文会给出说明。
service.sendMessage (textToSend , userNames[ i], (int ) acc .id)方法调用了ISipService的方法,找到它的代码如下:
目录:src\com\csipsimple\service
2.服务端
SipService.java
 1 /**
 2  * 继承 Service发布服务
 3  */
 4 public class SipService extends Service {
 5     ...
 6 
 7     // 为服务实现公共接口, Stub类继承了Binder
 8     private final ISipService.Stub binder = new ISipService.Stub() {
 9        ...
10     
11        /**
12         * Send SMS using
13         */
14        @Override
15        public void sendMessage(final String message, final String callee, final long accountId) throws RemoteException {
16            SipService.this .enforceCallingOrSelfPermission (SipManager .PERMISSION_USE_SIP , null);
17            //We have to ensure service is properly started and not just binded
18            SipService.this .startService (new Intent(SipService. this, SipService .class));
19            
20            getExecutor().execute(new SipRunnable() {
21               @Override
22               protected void doRun() throws SameThreadException {
23                   Log .d (THIS_FILE , "will sms " + callee);
24                    if(pjService != null) {
25                      ToCall called = pjService.sendMessage( callee, message, accountId );
26                       if(called!=null ) {
27                          SipMessage msg = new SipMessage(SipMessage. SELF,
28                                 SipUri .getCanonicalSipContact (callee ), SipUri.getCanonicalSipContact(called.getCallee()),
29                                 message , "text/plain", System.currentTimeMillis(),
30                                 SipMessage .MESSAGE_TYPE_QUEUED , called .getCallee ());
31                          msg .setRead (true);
32                          getContentResolver ().insert (SipMessage .MESSAGE_URI , msg.getContentValues());
33                          Log .d (THIS_FILE , "Inserted "+msg. getTo());
34                       }else {
35                          SipService .this. notifyUserOfMessage( getString(R. string.invalid_sip_uri)+ " : "+ callee );
36                       }
37                    }else {
38                       SipService .this. notifyUserOfMessage( getString(R. string.connection_not_valid) );
39                    }
40               }
41            });
42        }
43        
44        ...
45     };
46     
47     ...
48     
49     
50     /**
51      * 返回一个实现了接口的类对象,给客户端接收
52      */
53     @Override
54     public IBinder onBind(Intent intent) {
55 
56        String serviceName = intent.getAction();
57        Log.d(THIS_FILE, "Action is " + serviceName );
58        if (serviceName == null || serviceName.equalsIgnoreCase(SipManager.INTENT_SIP_SERVICE )) {
59            Log.d(THIS_FILE, "Service returned");
60            return binder ;
61        } else if (serviceName. equalsIgnoreCase(SipManager.INTENT_SIP_CONFIGURATION )) {
62            Log.d(THIS_FILE, "Conf returned");
63            return binderConfiguration ;
64        }
65        Log.d(THIS_FILE, "Default service (SipService) returned");
66        return binder;
67     }
68     
69     ...
70 }
上文说过,需要实现ISipService.aidl中定义的接口,来提供接口的实例供客户端调用。要实现自己的接口,就从ISipService.Stub类继承,然后实现相关的方法。
Stub类继承了Binder,因此它的对象就可以被远程的进程调用了。如果Service中有对象继承了Stub类,那么这个对象中的方法就可以在Activity等地方中使用,也就是说此时sendMessage就可以被其他Activity访问调用了。
现在我们通过onBind(Intent intent)方法得到了可供客户端接收的IBinder对象,就可以回头看看刚才MessageFragment.java文件中的调用情况了。
2.客户(调用)端
为方便查看,我们贴一段MessageFragment.java代码
MessageFragment.java
 1     // Service connection
 2     private ISipService service;
 3     private ServiceConnection connection = new ServiceConnection() {
 4         @Override
 5         public void onServiceConnected(ComponentName arg0, IBinder arg1) {
 6             service = ISipService.Stub.asInterface (arg1 );
 7         }
 8 
 9         @Override
10         public void onServiceDisconnected(ComponentName arg0 ) {
11             service = null;
12         }
13     };
14     
15     ...
16     
17     private void sendMessage() {
18            ...
19            service .sendMessage (textToSend , userNames[i], (int) acc.id);
20            ...
21     }
可以看到,在客户端(此处也就是调用远程服务的Activity)实现ServiceConnection,在ServiceConnection.onServiceConnected()方法中会接收到IBinder对象,调用ISipService.Stub.asInterface((IBinder)service)将返回值转换为ISipService类型。
语句service.sendMessage (textToSend , userNames[ i], (int ) acc .id );调用接口中的方法,完成IPC方法。
  • 调用关系

接下来继续回到sendMessage方法的调用关系分析,希望整理得不至太过混乱。
回到刚才的服务端实现,在继承Service发布服务的代码中,调用了pjService.sendMessage( callee, message, accountId )方法。
先看看这部分代码:
目录:src\com\csipsimple\pjsip
PjSipService.java
 1     /**
 2      * Send sms/message using SIP server
 3      */
 4     public ToCall sendMessage(String callee, String message , long accountId )
 5             throws SameThreadException {
 6         if (!created) {
 7             return null;
 8         }
 9 
10         ToCall toCall = sanitizeSipUri (callee , accountId );
11         if (toCall != null) {
12 
13             pj_str_t uri = pjsua.pj_str_copy( toCall.getCallee());
14             pj_str_t text = pjsua.pj_str_copy (message );
15             /*
16              * Log.d(THIS_FILE, "get for outgoing"); int finalAccountId =
17              * accountId; if (accountId == -1) { finalAccountId =
18              * pjsua.acc_find_for_outgoing(uri); }
19              */
20             // Nothing to do with this values
21             byte[] userData = new byte[ 1];
22 
23             int status = pjsua.im_send( toCall.getPjsipAccountId(), uri, null, text, null, userData);
24             return (status == pjsuaConstants .PJ_SUCCESS ) ? toCall : null ;
25         }
26         return toCall;
27     }

 

由红色部分的语句,我们找到pjsua类。
目录:src\org\pjsip\pjsua
pjsua.java
 1 /* ----------------------------------------------------------------------------
 2  * This file was automatically generated by SWIG (http://www.swig.org).
 3  * Version 2.0.4
 4  *
 5  * Do not make changes to this file unless you know what you are doing--modify
 6  * the SWIG interface file instead.
 7  * ----------------------------------------------------------------------------- */
 8 
 9 package org.pjsip.pjsua;
10 
11 public class pjsua implements pjsuaConstants {
12     
13     ...
14     
15     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 ) {
16     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 );
17     }
18     
19     ...
20 }
这时候大家可以发现,代码调用来到了org.pjsip.pjsua包部分。这部分代码在项目中是没有的,它是通过编译,同库文件libpjsipjni.so一起生成的。
CSipSimple编译后生成libs和pjsua两个目录,关于编译的方法网上资料很多了就不赘述了。
继续看调用,找到pjsuaJNI文件。
目录:src\org\pjsip\pjsua
pjsuaJNI.java
 1 /* ----------------------------------------------------------------------------
 2  * This file was automatically generated by SWIG (http://www.swig.org).
 3  * Version 2.0.4
 4  *
 5  * Do not make changes to this file unless you know what you are doing--modify
 6  * the SWIG interface file instead.
 7  * ----------------------------------------------------------------------------- */
 8 
 9 package org.pjsip.pjsua;
10 
11 public class pjsuaJNI {
12 
13     ...
14     
15     public final static native int im_send(int jarg1, long jarg2, pj_str_t jarg2_, long jarg3, pj_str_t jarg3_ , long jarg4, pj_str_t jarg4_, long jarg5, pjsua_msg_data jarg5_ , byte[] jarg6);
16     
17     ...
18     
19 }

 我们看到了native方法im_send(),它调用的是封装在库libpjsipjni.so中的函数pjsua_im_send,进一步可以在jni目录下找到C代码。

目录:jni\pjsip\sources\pjsip\src\pjsua-lib
pjsua_im.c
 1 /*
 2  * Send instant messaging outside dialog, using the specified account for
 3  * route set and authentication.
 4  */
 5 PJ_DEF( pj_status_t) pjsua_im_send( pjsua_acc_id acc_id ,
 6                 const pj_str_t *to ,
 7                 const pj_str_t *mime_type ,
 8                 const pj_str_t *content ,
 9                 const pjsua_msg_data *msg_data ,
10                 void *user_data )
11 {
12    ……
13 }

 

到这里,庞大的C实现就不贴出了,感兴趣的可以找到相应目录进行进一步的学习。
通过这篇文章,我们了解到CSipSimple通过aidl方法实现进程间通信,从而实现sendMessage服务在Activity中调用。整个调用过程大家还可以进一步探讨,希望能共同学习。
posted @ 2012-07-10 10:30  精神邋遢的民工  阅读(5273)  评论(4编辑  收藏  举报