在封装sdk的过程中,sdk是如何与app进行通信的呢?
总所周知,进程间通信,android的四大组件都是可以做到的,如果大家对其中原理有不明白的可以参照http://blog.youkuaiyun.com/toyuexinshangwan/article/details/8640709,这个博客说明得比较清楚。
下面我们主要学习的是,sdk利用aidl服务实现与app之间相互通信。
以下就举我做项目用到的一个demo作为例子,说说aidl服务的用法及使用中会遇到的问题。
demo实现的功能很简单,因为我做的是一个IM的sdk包,所以需要在IM的连接状态发生变化时,去通过app即时展示出来。
那么我们的aidl的服务可以这样设计。
创建一个IXmppConnectCallBack用来给予app实现sdk的连接状态监听。
创建一个IRemoteServiceInterface用来向sdk注册IXmppConnectCallBack的回调方法。
(可能大家对这两个接口关系不太明白,开始我也是;可以认为sdk是一个法院,IRemoteServiceInterface是我们的代表律师,IXmppConnectCallBack就是申诉人;就是申诉人委托律师去法院执行一些工作,个人愚见,哈哈)
// IRemoteServiceInterface.aidl package com.guiyi.hsim; // Declare any non-default types here with import statements import com.guiyi.hsim.IXmppConnectCallBack; interface IRemoteServiceInterface { /** * Often you want to allow a service to call back to its clients. * This shows how to do so, by registering a callback interface with * the service. */ void registerCallback(IXmppConnectCallBack cb); /** * Remove a previously registered callback interface. */ void unregisterCallback(IXmppConnectCallBack cb); }
// IXmppConnectCallBack.aidl package com.guiyi.hsim; // Declare any non-default types here with import statements interface IXmppConnectCallBack { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void connectStateChanged(int value); }
注意点1:添加aidl的时候,如果需要引入别的类,需要手动Import,如上IRemoteServiceInterface 的
import com.guiyi.hsim.IXmppConnectCallBack;
定义两个接口之后,我们需要实现一个service类来运作。
package com.dannytree.mylibrary; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; import java.util.Timer; import java.util.TimerTask; /** * Created by kangsq on 2016/7/20. */ public class RemoteService extends Service { /** * This is a list of callbacks that have been registered with the * service. Note that this is package scoped (instead of private) so * that it can be accessed more efficiently from inner classes. */ private final static int CONNECITON_STATE_CHANGE = 101; static final RemoteCallbackList<IXmppConnectCallBack> mCallbacks = new RemoteCallbackList<IXmppConnectCallBack>(); private int mValue = 0; //private static final int REPORT_MSG = 1; public int FullState = 0; private int tempint = 1; @Override public void onCreate() { //mHandler.sendEmptyMessage(REPORT_MSG); timer.schedule(task, 10000, 10000); // 10s后执行task,经过10s再次执行 } Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { connectionStateChange(tempint++); } }; public void connectionStateChange(int state) { /*final int N = mCallbacks.beginBroadcast(); Log.d("RemoteService","before handler mCallbacks count....----.=="+N); */ FullState = state; Log.d("RemoteService", "gettting FullState...." + state); Message msg = new Message(); msg.arg1 = state; msg.what = CONNECITON_STATE_CHANGE; mHandler.sendMessageDelayed(msg, 1000); } @Override public void onDestroy() { // Tell the user we stopped. //Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); // Unregister all callbacks. mCallbacks.kill(); Log.d("RemoteService","mCallbacks ..kill .."); // Remove the next pending message to increment the counter, stopping // the increment loop. //mHandler.removeMessages(REPORT_MSG); } @Override public IBinder onBind(Intent intent) { Log.d("RemoteService","IRemoteServiceInterface onbind...."); return mServiceBinder; // Select the interface to return. If your service only implements // a single interface, you can just return it here without checking // the Intent. /*if (IRemoteServiceInterface.class.getName().equals(intent.getAction())) { Log.d("RemoteService","IRemoteServiceInterface onbind...."); return mServiceBinder; } Log.d("RemoteService","nothing onbind...."); return null;*/ } /** * The IRemoteInterface is defined through IDL */ private final IRemoteServiceInterface.Stub mServiceBinder = new IRemoteServiceInterface.Stub() { public void registerCallback(IXmppConnectCallBack cb) { if (cb != null) { boolean aa = mCallbacks.register(cb); Log.d("RemoteService", "mCallbacks ..register successed.?" + aa); // int bb = mCallbacks.beginBroadcast(); // Log.d("RemoteService", "mCallbacks ..count ?" + bb); } } public void unregisterCallback(IXmppConnectCallBack cb) { if (cb != null) mCallbacks.unregister(cb); } }; @Override public void onTaskRemoved(Intent rootIntent) { Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show(); } /** * Our Handler used to execute operations on the main thread. This is used * to schedule increments of our value. */ private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case CONNECITON_STATE_CHANGE: Log.d("RemoteService","getting mHandler...."); int state_enum = (int) msg.arg1; final int N = mCallbacks.beginBroadcast(); Log.d("RemoteService","mCallbacks count....----.=="+N); for (int i = 0; i < N; i++) { try { Log.d("RemoteService","mCallbacks sending....----.=="+state_enum); mCallbacks.getBroadcastItem(i).connectStateChanged(state_enum); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing // the dead object for us. Log.d("RemoteService","getting mHandler. getBroadcastItem error..."); } } mCallbacks.finishBroadcast(); break; } } }; }
注意点2:onBind方法中必须返回一个IBinder ,否则app端bindservice的时候可能会失败。
注意点3:通过RemoteCallbackList来删除跨进程listener的接口。用于解决无法删除对应listener对象。因为是客户端传递给服务端的对象在服务端会生成一个不同的对象,但它们底层的Binder对象是同一个,利用这个特点,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删除掉。另外,当客户端进程终止后,它能够自动移除客户端所注册的listener。
static final RemoteCallbackList<IXmppConnectCallBack> mCallbacks = new RemoteCallbackList<IXmppConnectCallBack>();
注意点4:上述变量最初只有final,所以mCallbacks.beginBroadcast()一直返回0,导致app无法接收到sdk端的通知。原来是因为final在类的每次调用中,都会初始化一遍。基础不扎实啊
至此,sdk封装就完成了,app端已经可以正常收到回调
package com.dannytree.testusingaidl; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.Toast; import com.dannytree.mylibrary.IRemoteServiceInterface; import com.dannytree.mylibrary.IXmppConnectCallBack; import com.dannytree.mylibrary.RemoteService; public class MainActivity extends Activity implements View.OnClickListener{ IRemoteServiceInterface m_remoteServiceInterface =null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_bind).setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_bind: Intent intent = new Intent(this, RemoteService.class); //intent.setPackage("com.guiyi.hsim"); //注意这里的Context.BIND_AUTO_CREATE,这意味这如果在绑定的过程中, //如果Service由于某种原因被Destroy了,Android还会自动重新启动被绑定的Service。 // 你可以点击Kill Process 杀死Service看看结果 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); //mIsBound = true; //mCallbackText.setText("Binding."); break; } } @Override protected void onDestroy() { super.onDestroy(); if (m_remoteServiceInterface != null) { try { m_remoteServiceInterface.unregisterCallback(Act_mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } unbindService(mConnection); //unbindService(mcallbackConnection); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { Log.d("abrahamkang", "========= inside====onServiceConnected"); // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. m_remoteServiceInterface = IRemoteServiceInterface.Stub.asInterface(service); //mKillButton.setEnabled(true); //mCallbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { m_remoteServiceInterface.registerCallback(Act_mCallback); Log.d("abrahamkang", "=============registerCallback"); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. Log.d("abrahamkang", "=============registerCallback error"+e.toString()); } // As part of the sample, tell the user what happened. /*Toast.makeText(BindActivity.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show();*/ } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. m_remoteServiceInterface = null; Log.d("abrahamkang", "=============onServiceDisconnected "); // mKillButton.setEnabled(false); // mCallbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. // Toast.makeText(BindActivity.this, R.string.remote_service_disconnected, // Toast.LENGTH_SHORT).show(); } }; /** * 远程回调接口实现 */ private IXmppConnectCallBack Act_mCallback = new IXmppConnectCallBack.Stub() { @Override public void connectStateChanged(int state) throws RemoteException { Toast.makeText(MainActivity.this, "Connection state is " + state, Toast.LENGTH_SHORT).show(); } }; }
注意点5:在app中的AndroidManifest.xml中需要添加与sdk中service一致的包名才能bindservice成功。
<service
android:name="com.dannytree.mylibrary.RemoteService"
></service>