201709012工作日记--activity与service的通信机制
service生命周期
Service主要包含本地类和远程类。
Service不是Thread,Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。
通信方式有很多但是需要根据需求去选择合适我们的:
目前Android 组件之间主流的通信方式如上所述的5种,哪种对于项目更加合适,需要考虑的适用情况有:
- 进程内通信还是进程间通信
- 一对一通信,还是一对多
- 单向还是双向
- 性能
- 其他(安全性,代码可读性,实现复杂性)等等;
Activity和Service之间的通信方式
1.1 使用扩展Binder类(本例从activity向service传送数据)
使用场景:如果你的服务仅供本地应用使用,不需要跨进程工作,则可以实现扩展Binder 类,让你的客户端通过该类直接访问服务中的公共方法。此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。
通信思路:
1、在Service中创建一个Binder实例,然后再onBind()方法中返回这个实例。
2、在客户端中使用bindService从 onServiceConnected() 回调方法接收Binder实例,这个Binder实例就是Service的回调方法onBind()方法返回的Binder,然后使用这个Binder提供的方法调用绑定服务,得到Service实例。
3、然后就可以使用这个Service实例来调用其方法,这样就可以对Sevice服务端进行操作。
4、如果Service端想要给客户端返回数据咋办呢?这个简单,既然已经获得了Service实例,那么就可以在Service当中设置一个接口,然后在客户端实现这个接口,这样Service端就可以通过这个接口给客户端发送消息了!
MainService.java
1 public class MainService extends Service{ 2 3 private String TAG = "MainService"; 4 public ServiceBinder mBinder = new ServiceBinder(); /* 数据通信的桥梁 */ 5 6 /* 重写Binder的onBind函数,返回派生类 */ 7 @Override 8 public IBinder onBind(Intent arg0) { 9 return mBinder; 10 } 11 12 @Override 13 public void onCreate() { 14 Toast.makeText( MainService.this, "Service Create...", Toast.LENGTH_SHORT).show(); 15 super.onCreate(); 16 } 17 18 @Override 19 public int onStartCommand(Intent intent, int flags, int startId) { 20 Toast.makeText(MainService.this, "Service StartCommand", Toast.LENGTH_SHORT).show(); 21 return super.onStartCommand(intent, flags, startId); //在这里可以接受从activity传递过来的数据 22 } 23 24 @Override 25 public void onDestroy() { 26 Toast.makeText( MainService.this, "Service Destroy", Toast.LENGTH_SHORT).show(); 27 } 28 29 /* 第一种模式通信:Binder */ 30 public class ServiceBinder extends Binder { 31 32 public void startDownload() throws InterruptedException { 33 /* 模拟下载,休眠2秒 */ 34 Toast.makeText( MainService.this, "模拟下载2秒钟,开始下载...", Toast.LENGTH_SHORT).show(); 35 Thread.sleep(2); 36 Toast.makeText( MainService.this, "下载结束...", Toast.LENGTH_SHORT).show(); 37 } 38 } 39 }
MainActivity.java
1 public class MainActivity extends Activity { 2 /* 通过Binder,实现Activity与Service通信 */ 3 private MainService.ServiceBinder mBinderService; 4 private ServiceConnection connection = new ServiceConnection() { 5 @Override 6 public void onServiceDisconnected(ComponentName name) { 7 } 8 9 @Override 10 public void onServiceConnected(ComponentName name, IBinder service) { 11 mBinderService = (MainService.ServiceBinder) service; 12 try { 13 mBinderService.startDownload(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 }; 19 20 @Override 21 public void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 25 Intent bindIntent = new Intent(MainActivity.this, MainService.class); 26 bindService(bindIntent, connection, BIND_AUTO_CREATE); //在Intent中设置bundle,可以在这里传递数据 27 } 28 }
下面我们又有新的需求:目前上面的代码可以将数据从MainActivity.java传递到Service端,通过Intent的传参或者设置Bundle传参。但是我们如果需要从Service中获取数据该怎么办呢?
1.2 使用扩展Binder类(本例从service向activity传送数据--利用回调)
如果是我们需要从服务中返回一些数据到Activity中的时候,实现起来就有各种各样的方法,比如说使用回调,使用广播等等,今天说的是使用回调的方法。下面的这个例子摘自:http://www.cnblogs.com/Fndroid/p/5187444.html 理解这个例子之前还要好好体会回调函数的作用:
先上服务端的代码,MyService.java
1 public class MyService extends Service { 2 private boolean connecting = false; 3 private Callback callback; 4 5 @Nullable 6 @Override 7 public IBinder onBind(Intent intent) { 8 return new Binder(); 9 } 10 11 public class Binder extends android.os.Binder { 12 public MyService getService() { 13 return MyService.this; 14 } 15 } 16 17 @Override 18 public void onCreate() { 19 super.onCreate(); 20 connecting = true; 21 new Thread(new Runnable() { 22 23 @Override 24 public void run() { 25 int i = 0; 26 while (connecting == true) { 27 i++; 28 if (callback != null) { 29 callback.onDataChange(i + ""); 30 } 31 try { 32 Thread.sleep(1000); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38 }).start(); 39 } 40 41 public void setCallback(Callback callback) { 42 this.callback = callback; 43 } 44 45 public static interface Callback { 46 void onDataChange(String data); 47 } 48 49 @Override 50 public void onDestroy() { 51 super.onDestroy(); 52 connecting = false; 53 } 54 }
这段代码和上面的代码没有任何区别,只是添加了回调接口,
public static interface Callback { void onDataChange(String data); }
在线程中,回调这个接口的函数,这个回调函数将会返回数据给,MainActivity,并在MainActivity中处理这些从service端传递过来的数据。
@Override
24 public void run() {
25 int i = 0;
26 while (connecting == true) {
27 i++;
28 if (callback != null) {
29 callback.onDataChange(i + "");
30 }
31 try {
32 Thread.sleep(1000);
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 }
37 }
38 }).start();
MainActivity.java:
1 public class MainActivity extends AppCompatActivity implements View.OnClickListener, 2 ServiceConnection { 3 4 private TextView tvOut; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 tvOut = (TextView) findViewById(R.id.tvOut); 11 findViewById(R.id.btnBindService).setOnClickListener(this); 12 } 13 14 @Override 15 public void onClick(View v) { 16 bindService(new Intent(this, MyService.class), this, BIND_AUTO_CREATE); 17 } 18 19 @Override 20 public void onServiceConnected(ComponentName name, IBinder service) { 21 MyService.Binder binder = (MyService.Binder) service; 22 MyService myService = binder.getService(); 23 myService.setCallback(new MyService.Callback() { 24 @Override 25 public void onDataChange(String data) { 26 Message msg = new Message(); 27 msg.obj = data; 28 handler.sendMessage(msg); 29 } 30 }); 31 } 32 33 @Override 34 public void onServiceDisconnected(ComponentName name) { 35 36 } 37 38 private Handler handler = new Handler() { 39 @Override 40 public void handleMessage(Message msg) { 41 super.handleMessage(msg); 42 tvOut.setText(msg.obj.toString()); 43 } 44 }; 45 }
注意:开启的服务是在主线程中运行的,如果在服务中开启了线程,那么在子线程中就不能直接更新UI在这里面无法直接更新界面,因为开启了线程,需要使用Handler机制。
在服务中的onCreate方法中,我们打开了一个线程来模拟服务的运行,并在线程每隔1s中给私有变量i赋值递增,然后我们编写了一个公有的接口Callback,并且定义了一个该接口的私有成员,并且在onCreate方法中调用了接口里面的函数onDataChange。接下来我们自定义了一个Binder的子类并在这个类中定义了函数返回当前的这个Service,这里的目的就是在Activity中可以访问到这个Service的回调接口Callback并实现该接口的方法。
因为服务绑定后,会从onBind方法中返回一个Binder对象,这个对象会在onServiceConnectde方法中获取到,所以我们先从Binder对象中获取到我们从服务传递过来的MyService对象,然后调用MyService对象的setCallback方法来设置我们需要的处理逻辑,这里是把i的值打印出来,因为服务中开启了线程,所以这里也不能直接更新UI。
总结:回调机制是Java中的一个重要特性,在Android中使用到的地方很广泛,例如我们给按钮设定点击事件等。这里的回调,其实是通过在发送端定义回调接口,并且调用接口的回调方法,然后在接收端实现该接口的方法。只要接口被调用了,就会回调接收端的被实现了的方法,这样数据就能传递过来。
2. Handler+Binder VS Handler+Message
主要思路:通常我们使用Handler+Message的方式进行通信,都是在同一个进程中,子线程持有一个主线程的Handler对象,并向主线程发送消息。而Android既然可以使用bindler机制进行跨进行通信,所以我们当然可以将Handler与bindler结合起来进行跨进程发送消息。查看API就可以发现,Messenger就是这种方式的实现。具体的使用请看下面的Messenger通信。
3. 广播 (推荐LocalBroadcastManager)
主要思路:
需要在MainActivity中注册能够接收Service发送的数据更新广播:com.spt.activity.CountService;并获取到更新后的数据,显示即可。还需要注意的是:开启服务后,还要停止服务。否则该服务会一直在后台运行。 还发现,使用这种方式同样能够停止该Service:stopService(new Intent(MainActivity.this, CountService.class));
MainActivity.java
1 /** 2 * <功能描述> Service和Activity之间的数据交互;具体表现为: 1. 从Service获取数据源,传递到Activity中; 2. 3 * 在Activity中作数据更新; 3. Service中的onCreate()在UI线程中执行,作延时需要在子线程中执行; 4 * 5 * @author Administrator 6 */ 7 public class MainActivity extends Activity { 8 private static final String TAG = "Demo"; 9 private TextView mTvContent; 10 private Intent mIntent; 11 @Override 12 protected void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 initView(); 15 registerReceiver(); 16 initData(); 17 } 18 /** 19 * <功能描述> 初始化视图 20 * 21 * @return void [返回类型说明] 22 */ 23 private void initView() { 24 setContentView(R.layout.activity_main); 25 mTvContent = (TextView) findViewById(R.id.tv_data); 26 } 27 /** 28 * <功能描述> 初始化数据 29 * 30 * @return void [返回类型说明] 31 */ 32 private void initData() { 33 mIntent = new Intent(MainActivity.this, CountService.class); 34 startService(mIntent); 35 } 36 /** 37 * <功能描述> 注册广播 38 * 39 * @return void [返回类型说明] 40 */ 41 private void registerReceiver() { 42 IntentFilter intentFilter = new IntentFilter(); 43 intentFilter.addAction("com.spt.activity.CountService"); 44 MainActivity.this.registerReceiver(new BroadcastReceiver() { 45 @Override 46 public void onReceive(Context context, Intent intent) { 47 Bundle bundle = intent.getExtras(); 48 mTvContent.setText(bundle 49 .getInt("com.spt.CountService.count_data") + ""); 50 Log.d(TAG, "count_data=" + mTvContent.getText()); 51 } 52 }, intentFilter); 53 } 54 @Override 55 protected void onDestroy() { 56 super.onDestroy(); 57 if (true) { 58 stopService(mIntent); 59 } 60 } 61 }
CountService.java
1 public class CountService extends Service { 2 private int countService = 0; 3 private boolean isServiceRunning = false; 4 @Override 5 public void onCreate() { 6 super.onCreate(); 7 isServiceRunning = true; 8 // 创建子线程作计数 9 new Thread(new Runnable() { 10 @Override 11 public void run() { 12 while (isServiceRunning) { 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 countService++; 19 Intent intent = new Intent(); 20 intent.putExtra("com.spt.CountService.count_data", countService); 21 intent.setAction("com.spt.activity.CountService"); 22 // Service中发送广播 23 sendBroadcast(intent); 24 } 25 } 26 }).start(); 27 } 28 @Override 29 public IBinder onBind(Intent intent) { 30 return null; 31 } 32 @Override 33 public void onDestroy() { 34 super.onDestroy(); 35 isServiceRunning = false; 36 countService = 0; 37 } 38 }
总结和疑问:
1. 使用Broadcast广播方式实现服务和宿主之间的数据交互,容易造成性能不高的问题;
2. 广播发送的时间不确定性因素,导致数据交互有延时;
4. Messenger
主要思路:先贴上完整的服务端和客户端通信的代码,再做分析。
(1)Server端
import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; public class MessengerService extends Service { private static final int MSG_SUM = 0x110; //最好换成HandlerThread的形式 private Messenger mMessenger = new Messenger(new Handler() { @Override public void handleMessage(Message msgfromClient) { Message msgToClient = Message.obtain(msgfromClient);//返回给客户端的消息 switch (msgfromClient.what) { //msg 客户端传来的消息 case MSG_SUM: msgToClient.what = MSG_SUM; try { //模拟耗时 Thread.sleep(2000); msgToClient.arg2 = msgfromClient.arg1 + msgfromClient.arg2; msgfromClient.replyTo.send(msgToClient); } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } break; } super.handleMessage(msgfromClient); } }); @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
服务端就一个Service,可以看到代码相当的简单,只需要去声明一个Messenger信使对象,然后onBind方法返回mMessenger.getBinder();然后坐等客户端将消息发送到handleMessage想法,根据message.what去判断进行什么操作,然后做对应的操作,最终将结果通过 msgfromClient.replyTo.send(msgToClient);返回。
(2)Client 客户端
import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final int MSG_SUM = 0x110; private Button mBtnAdd; private LinearLayout mLyContainer; //显示连接状态 private TextView mTvState; private Messenger mService; private boolean isConn; private Messenger mMessenger = new Messenger(new Handler() { @Override public void handleMessage(Message msgFromServer) { switch (msgFromServer.what) { case MSG_SUM: TextView tv = (TextView) mLyContainer.findViewById(msgFromServer.arg1); tv.setText(tv.getText() + "=>" + msgFromServer.arg2); break; } super.handleMessage(msgFromServer); } }); private ServiceConnection mConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) //返回的binder对象,下面通过这个对象创建远程信使 { mService = new Messenger(service); isConn = true; mTvState.setText("connected!"); } @Override public void onServiceDisconnected(ComponentName name) { mService = null; isConn = false; mTvState.setText("disconnected!"); } }; private int mA; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //开始绑定服务 bindServiceInvoked(); mTvState = (TextView) findViewById(R.id.id_tv_callback); mBtnAdd = (Button) findViewById(R.id.id_btn_add); mLyContainer = (LinearLayout) findViewById(R.id.id_ll_container); mBtnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { int a = mA++; int b = (int) (Math.random() * 100); //创建一个tv,添加到LinearLayout中 TextView tv = new TextView(MainActivity.this); tv.setText(a + " + " + b + " = caculating ..."); tv.setId(a); mLyContainer.addView(tv); Message msgFromClient = Message.obtain(null, MSG_SUM, a, b); msgFromClient.replyTo = mMessenger; if (isConn) { //往服务端发送消息 mService.send(msgFromClient); } } catch (RemoteException e) { e.printStackTrace(); } } }); } private void bindServiceInvoked() { Intent intent = new Intent(); intent.setAction("com.zhy.aidl.calc"); bindService(intent, mConn, Context.BIND_AUTO_CREATE); Log.e(TAG, "bindService invoked !"); } @Override protected void onDestroy() { super.onDestroy(); unbindService(mConn); } }
代码也不复杂,首先bindService,然后在onServiceConnected中拿到回调的service(IBinder)对象,通过service对象去构造一个mService =new Messenger(service);然后就可以使用mService.send(msg)给服务端了。那么服务端会收到消息,处理完成会将结果返回,传到Client端的mMessenger中的Handler的handleMessage方法中。
(3)注册service,以及布局文件
<service android:name=".MessengerService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.zhy.aidl.calc"></action> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
<LinearLayout android:id="@+id/id_ll_container" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <TextView android:id="@+id/id_tv_callback" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Messenger Test!"/> <Button android:id="@+id/id_btn_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="add"/> </LinearLayout>
上面的代码摘抄自:http://blog.csdn.net/lmj623565791/article/details/47017485#quote
消息传递流程分析:
service创建一个信使对象mMessenger,然后通过onBind()返回给client,client通过获取到的IBinder对象创建客户端的信使对象mService,然后利用mService.send(msg)就可以给服务器端发送消息了。服务器端坐等客户端将消息发送到handleMessage()接收从客户端发送过来的消息,根据message.what去判断进行什么操作,然后做对应的操作,最终将结果通过 msgfromClient.replyTo.send(msgToClient)将信息返回给客户端,最终完成了service和client之间的双向通信。
1.服务端通过
mMessenger = new Messenger(mHandler)
创建一个信使对象。
2.服务端通过onBinde()方法返回一个binder
return mMessenger.getBinder();
3.客户端使用bindService(intent, mConn, Context.BIND_AUTO_CREATE)请求远程连链接;通过远程链接返回的binder得到一个信使(远程信使)对象
public void onServiceConnected(ComponentName name, IBinder service) { rMessenger = new Messenger(service); ...... }
4.客户端可以使用这个远程信使对象向远程发送消息:rMessenger.send(msg); 这样远程服务端的Handler对象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中进行处理。(该Handler对象就是第一步服务端创建Messenger时使用的参数mHandler)。
5.经过这5个步骤貌似只有客户端向服务端发送消息,这样的消息传递是单向的,那么如何实现双向传递呢? 首先需要在第5步稍加修改,在send(msg)前通过msm.replyTo = mMessenger将自己的信使设置到消息中,这样服务端接收到消息时同时也得到了客户端的信使对象了,然后服务端可以通过send()完成从服务端向客户端发送消息的功能,这样客服端可以在自己的Handler对象的handlerMessage方法中接收服务端发送来的message进行处理。 双向通信宣告完成。
总的来说就是创建信使,信使传递信息。
5. 开源组件(EventBus,otto)
主要思路:利用反射或者注释的方式实现对注册类的注册,然后遍历当前的注册表,通过key进行查询,然后dispatch,调用相应的事件处理方法。(不同的开源框架实现有所区别)
6. AIDL
主要思路://TODO