Android AIDL SERVICE 双向通信 详解

http://www.cnblogs.com/punkisnotdead/p/5062631.html

起因 是这个blog 提到了 用webview 的时候 用开启子进程的方式 可以极大避免内存泄露。然后有很多人私信我 这种情况下

如何 相互通信的问题。当然广播是一个比较不错的选择,只不过广播的方法 能够传递的值比较有限。messenger 也只能做

单向传递消息。(当然你如果用2个 是可以双向的。单独的messenger是只能单向的)。

so,这里给出一个简单的小例子,教你如何处理 AIDL service双向通信的问题.

首先来建立一下这个例子的 模型,

1.我们假定有一个service 运行在 独立进程上,这个进程 就好像是餐厅一样。

2.我们的主进程呢,就好像是一个个顾客, 每次我们进入餐厅的时候 ,餐厅都会告诉我们 谁谁谁 进入了餐厅。

3.上述2条 我们注意看一下 餐厅的容量是有限的,所以我们的顾客进去以后吃完了就必须要出来。不然 餐厅的资源就有可能会被浪费 其他顾客就无法进入。

4.所以有一种场景是 当你的主进程也就是顾客 进入餐厅以后,万一你的主进程因为某种原因被杀死了,(比如退到后台的时候 内存不够 被kill掉)那你的service进程就必须要保证

把这个顾客移出掉,不然有限的资源 迟早会被耗尽,而且逻辑上也说不通。

下面就来实现这个需求。

首先看下 项目结构:

然后看一下我们的aidl文件:

 1 // RestaurantAidlInterface.aidl
 2 package com.example.administrator.aidlmessageexample;
 3 import com.example.administrator.aidlmessageexample.NotifyCallBack;
 4 // Declare any non-default types here with import statements
 5 //这个就是aidl文件
 6 interface RestaurantAidlInterface {
 7
 8     //新来了一个顾客
 9     void join(IBinder token,String name);
10     //走了一个顾客
11     void leave();
12     //注册回调接口
13     void registerCallBack(NotifyCallBack cb);
14     void unregisterCallBack(NotifyCallBack cb);
15 }
1 // NotifyCallBack.aidl
2 package com.example.administrator.aidlmessageexample;
3
4 // Declare any non-default types here with import statements
5
6 interface NotifyCallBack {
7     void notifyMainUiThread(String name,boolean joinOrLeave);
8 }

然后看看我们的service:

  1 package com.example.administrator.aidlmessageexample;
  2
  3 import android.app.Service;
  4 import android.content.Intent;
  5 import android.os.IBinder;
  6 import android.os.IBinder.DeathRecipient;
  7 import android.os.RemoteCallbackList;
  8 import android.os.RemoteException;
  9 import android.support.annotation.Nullable;
 10
 11 import java.util.ArrayList;
 12 import java.util.List;
 13 import java.util.Random;
 14
 15 /**
 16  * Created by Administrator on 2016/1/25.
 17  */
 18 public class RestaurantService extends Service {
 19
 20     //这个list 就是用来存储当前餐厅有多少顾客 注意我们为什么没有用顾客的名字来存储?
 21     //而是用了这个CustomerClient的类 看这个类的注释即可明白
 22     private List<CustomerClient> mClientsList = new ArrayList<>();
 23
 24     //上面用CustomerClient 的原因是因为害怕客户端异常销毁时,服务器收不到消息 造成资源浪费等异常
 25     //同样的 我们在服务端通知客户端消息的时候 也害怕 服务端 会异常销毁 导致客户端收不到消息
 26     //好在谷歌早就为我们考虑到这种情况  提供了RemoteCallbackList 来完成对应的功能
 27     //避免我们再重复一遍上述的过程
 28     private RemoteCallbackList<NotifyCallBack> mCallBacks = new RemoteCallbackList<>();
 29
 30
 31     private final RestaurantAidlInterface.Stub mBinder = new RestaurantAidlInterface.Stub() {
 32
 33
 34         @Override
 35         public void join(IBinder token, String name) throws RemoteException {
 36             CustomerClient cl = new CustomerClient(token, name);
 37             mClientsList.add(cl);
 38             notifyCallBack(name, true);
 39         }
 40
 41         @Override
 42         public void leave() throws RemoteException {
 43             //顾客离开的时候 我们随机让他离开一个就行了
 44             int length = mClientsList.size();
 45             int randomIndex = new Random().nextInt(length-1);
 46             mClientsList.remove(randomIndex);
 47             notifyCallBack(mClientsList.get(randomIndex).mCustomerName, false);
 48         }
 49
 50         @Override
 51         public void registerCallBack(NotifyCallBack cb) throws RemoteException {
 52             mCallBacks.register(cb);
 53         }
 54
 55         @Override
 56         public void unregisterCallBack(NotifyCallBack cb) throws RemoteException {
 57             mCallBacks.unregister(cb);
 58         }
 59     };
 60
 61     private void notifyCallBack(String customerName, boolean joinOrLeave) {
 62         final int len = mCallBacks.beginBroadcast();
 63         for (int i = 0; i < len; i++) {
 64             try {
 65                 // 通知回调
 66                 mCallBacks.getBroadcastItem(i).notifyMainUiThread(customerName, joinOrLeave);
 67             } catch (RemoteException e) {
 68                 e.printStackTrace();
 69             }
 70         }
 71         mCallBacks.finishBroadcast();
 72     }
 73
 74
 75     @Override
 76     public void onDestroy() {
 77         //销毁回调资源 否则要内存泄露
 78         mCallBacks.kill();
 79         super.onDestroy();
 80     }
 81
 82     @Nullable
 83     @Override
 84     public IBinder onBind(Intent intent) {
 85         return mBinder;
 86     }
 87
 88     //http://developer.android.com/intl/zh-cn/reference/android/os/Binder.html#linkToDeath(android.os.IBinder.DeathRecipient, int)
 89     //实际上 这个接口 就是用来 当客户端自己发生崩溃时, 我们的服务端也能收到这个崩溃的消息
 90     //并且会调用binderDied 这个回调方法,所以你看这个内部类的代码 就明白了 无非就是保证当客户端异常销毁的时候
 91     //我们服务端也要保证收到这个消息 然后做出相应的应对
 92     final class CustomerClient implements DeathRecipient {
 93
 94         public final IBinder mToken;
 95
 96         public CustomerClient(IBinder mToken, String mCustomerName) {
 97             this.mToken = mToken;
 98             this.mCustomerName = mCustomerName;
 99         }
100
101         public final String mCustomerName;
102
103         @Override
104         public void binderDied() {
105             //我们的应对方法就是当客户端 也就是顾客异常消失的时候 我们要把这个list里面 的对象也移出掉
106             if (mClientsList.indexOf(this) >= 0) {
107                 mClientsList.remove(this);
108             }
109
110         }
111     }
112 }
 1 <!-- 这个地方用开启子进程的方式来实现这个service 注意你们可以把主进程关闭以后 看看这个子进程
 2         service list里面持有的那些对象能否收到 这个异常关闭的消息-->
 3         <service
 4             android:name=".RestaurantService"
 5             android:enabled="true"
 6             android:exported="true"
 7             android:process="com.android.test.process">
 8
 9             <intent-filter>
10                 <action android:name="com.example.administrator.aidlmessageexample.RestaurantAidlInterface" />
11             </intent-filter>
12
13         </service>

然后再看看 客户端 也就是主进程的编写:

  1 package com.example.administrator.aidlmessageexample;
  2
  3 import android.content.ComponentName;
  4 import android.content.DialogInterface;
  5 import android.content.Intent;
  6 import android.content.ServiceConnection;
  7 import android.os.Binder;
  8 import android.os.Bundle;
  9 import android.os.IBinder;
 10 import android.os.IInterface;
 11 import android.os.Parcel;
 12 import android.os.RemoteException;
 13 import android.support.design.widget.FloatingActionButton;
 14 import android.support.design.widget.Snackbar;
 15 import android.support.v7.app.AppCompatActivity;
 16 import android.support.v7.widget.Toolbar;
 17 import android.view.View;
 18 import android.view.Menu;
 19 import android.view.MenuItem;
 20 import android.widget.Button;
 21 import android.widget.TextView;
 22 import android.widget.Toast;
 23
 24 import org.w3c.dom.Text;
 25
 26 import java.io.FileDescriptor;
 27 import java.util.Random;
 28
 29 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 30
 31     private Button bt, bt2, bt3, bt4;
 32
 33     private RestaurantAidlInterface mService;
 34
 35     private TextView tv;
 36
 37
 38     private ServiceConnection mServiceConnection = new ServiceConnection() {
 39         @Override
 40         public void onServiceConnected(ComponentName name, IBinder service) {
 41             mService = RestaurantAidlInterface.Stub.asInterface(service);
 42             try {
 43                 //我们这个demo里面 只注册了一个回调 实际上可以注册很多个回调 因为service里面 我们存的是list callback
 44                 mService.registerCallBack(mNotifyCallBack);
 45             } catch (RemoteException e) {
 46                 e.printStackTrace();
 47             }
 48
 49         }
 50
 51         @Override
 52         public void onServiceDisconnected(ComponentName name) {
 53             try {
 54                 mService.unregisterCallBack(mNotifyCallBack);
 55             } catch (RemoteException e) {
 56                 e.printStackTrace();
 57             }
 58             mService = null;
 59         }
 60     };
 61
 62
 63     private NotifyCallBack mNotifyCallBack = new NotifyCallBack.Stub() {
 64
 65         @Override
 66         public void notifyMainUiThread(String name, boolean joinOrLeave) throws RemoteException {
 67             String toastStr = "";
 68             if (joinOrLeave) {
 69                 toastStr = name + "进入了餐厅";
 70             } else {
 71                 toastStr = name + "离开了餐厅";
 72             }
 73             tv.setText(toastStr);
 74         }
 75     };
 76
 77
 78     @Override
 79     protected void onCreate(Bundle savedInstanceState) {
 80         super.onCreate(savedInstanceState);
 81         setContentView(R.layout.activity_main);
 82         bt = (Button) this.findViewById(R.id.bt);
 83         bt2 = (Button) this.findViewById(R.id.bt2);
 84         bt3 = (Button) this.findViewById(R.id.bt3);
 85         bt4 = (Button) this.findViewById(R.id.bt4);
 86         tv = (TextView) this.findViewById(R.id.tv);
 87         bt.setOnClickListener(this);
 88         bt2.setOnClickListener(this);
 89         bt3.setOnClickListener(this);
 90         bt4.setOnClickListener(this);
 91
 92         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
 93         setSupportActionBar(toolbar);
 94
 95         FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
 96         fab.setOnClickListener(new View.OnClickListener() {
 97             @Override
 98             public void onClick(View view) {
 99                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
100                         .setAction("Action", null).show();
101             }
102         });
103     }
104
105     @Override
106     public boolean onCreateOptionsMenu(Menu menu) {
107         // Inflate the menu; this adds items to the action bar if it is present.
108         getMenuInflater().inflate(R.menu.menu_main, menu);
109         return true;
110     }
111
112     @Override
113     public boolean onOptionsItemSelected(MenuItem item) {
114         // Handle action bar item clicks here. The action bar will
115         // automatically handle clicks on the Home/Up button, so long
116         // as you specify a parent activity in AndroidManifest.xml.
117         int id = item.getItemId();
118
119         //noinspection SimplifiableIfStatement
120         if (id == R.id.action_settings) {
121             return true;
122         }
123
124         return super.onOptionsItemSelected(item);
125     }
126
127     @Override
128     public void onClick(View v) {
129         switch (v.getId()) {
130             case R.id.bt:
131                 bindService();
132                 break;
133             case R.id.bt2:
134                 unbindService();
135                 break;
136             case R.id.bt3:
137                 addCustomer();
138                 break;
139             case R.id.bt4:
140                 leaveCustomer();
141                 break;
142         }
143
144     }
145
146     private void bindService() {
147         Intent intent = new Intent(RestaurantAidlInterface.class.getName());
148         bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
149     }
150
151     private void unbindService() {
152         unbindService(mServiceConnection);
153     }
154
155     private void leaveCustomer() {
156         try {
157             // mService.registerCallBack(mNotifyCallBack);
158             mService.leave();
159         } catch (RemoteException e) {
160             e.printStackTrace();
161         }
162     }
163
164     private void addCustomer() {
165         try {
166             mService.join(new Binder(), getRandomString(6));
167         } catch (RemoteException e) {
168             e.printStackTrace();
169         }
170     }
171
172     public static String getRandomString(int length) { //length表示生成字符串的长度
173         String base = "abcdefghijklmnopqrstuvwxyz0123456789";
174         Random random = new Random();
175         StringBuffer sb = new StringBuffer();
176         for (int i = 0; i < length; i++) {
177             int number = random.nextInt(base.length());
178             sb.append(base.charAt(number));
179         }
180         return sb.toString();
181     }
182 }

最后跑一下效果(客户端进程异常结束 服务端进程收到消息 无法演示在gif里面,你们可以回去自己演示 看log日志 即可。直接用adb shell 命令 结束客户端进程 就行了)

时间: 2024-08-08 05:36:37

Android AIDL SERVICE 双向通信 详解的相关文章

android中Service使用详解

service用于长期在后台处理任务,而不需要对用户可见. service有2种基本的启动方式: startService():使用这种方式,来进行单一的任务,不需要返回结果给调用者 bindService():与上面的相反. 下面是一些关于服务的重要说明,非常值得详细了解的: 继承service,实现自己的service: 在manifest中声明service,服务位于主线程,并不会创建自己的子线程. 下面是一些重写的方法: onCreate();当服务被创建时调用,只调用一次. onSta

Android Service使用详解

Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问.Service可以在后台执行长时间运行操作而不提供用户界面,除非系统必须回收内存资源,否则系统不会停止或销毁服务.服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行. 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC) 需要注意的是,Service是在主线程里执行操作的,可能会因为执行耗时操作而导致ANR 一.基础知识 Service可以分为以下三种形式

Android四大组件--Activity详解

Android四大组件--Activity详解 分类: android android应用android开发 本文的主要内容包括1.activity的建立.配置和使用:2.activity的跳转和传值:3.startActivityForResult:4.activity的生命周期. 1.activity的建立.配置和使用 Activity是一个应用中的组件,它为用户提供一个可视的界面,方便用户操作,比如说拔打电话.照相.发邮件或者是浏览地图等.每个activity会提供一个可视的窗口,一般情况

Android之Zygote启动详解

我们知道Android系统是基于Linux内核的,在Linux系统中所有的进程都是init进程的子进程.Zygote也一样它是在系统启动的过程中由init进程创建的,在系统启动脚本init.rc中: <span style="font-size:14px;">@init.rc service zygote /syste/bin/app_process -Xzygote /system/bin -zygote --start-system-server class main

Android 接口回调机制详解

在使用接口回调的时候发现了一个经常犯的错误,就是回调函数里面的实现有可能是用多线程或者是异步任务去做的,这就会导致我们期望函数回调完毕去返回一个主函数的结果,实际发现是行不通的,因为如果回调是多线程的话你是无法和主函数同步的,也就是返回的数据是错误的,这是非常隐秘的一个错误.那有什么好的方法去实现数据的线性传递呢?先介绍下回调机制原理. 回调函数 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数

Android Google Map v2详解之:开发环境配置

Android Google Map v2详解之:开发环境配置                                       --转载请注明出处:coder-pig 说在前面: 说到地图定位,现在越来越多的社交app都加入了地图和定位的功能模块,用户很多的时候 也会用到这些东东,比如,到外面吃饭,次次吃饭前都要拍下照片发到朋友圈,定个位,然后发条说说, 炫耀一下自己今天吃了什么高大上的东东,炫耀和攀比心理我懂,不过,一次下班去吃饭,看到一妹子 吃饭,拍照+发朋友圈,足足用了大概20

Android开发之BroadcastReceiver详解

BroadcastReceiver,顾名思义就是"广播接收者"的意思,它是Android四大基本组件之一,这种组件本质上是一种全局的监听器,用于监听系统全局的广播消息.它可以接收来自系统和应用的的广播. 由于BroadcastReceiver是一种全局的监听器,因此它可以非常方便地实现系统不同组件之间的通信.比如Activity与通过startService()方法启动的Service之间通信,就可以借助于BroadcastReceiver来实现. BroadcastReceiver简

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============

给 Android 开发者的 RxJava 详解

作者:扔物线 前言 我从去年开始使用 RxJava ,到现在一年多了.今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 .而最近这几个月,我也发现国内越来越多的人开始提及 RxJava .有人说『RxJava 真是太好用了』,有人说『RxJava 真是太难用了』,另外更多的人表示:我真的百度了也谷歌了,但我还是想问: RxJava 到底是什么? 鉴于 RxJava 目前这种既火爆又神秘的现状,而我又在一年的使用