Android Studio 实现使用AIDL的IPC通讯
来源:互联网 发布:中国没有未来知乎 编辑:程序博客网 时间:2024/06/05 17:57
推荐一篇很全面的关于Android Service的博客 ,博客的后半段详细讲了AIDL通讯。
程序将实现这样一个简单功能:Service端提供多种饮料信息供选择,Client端先选择种类,再选择数量,随后计算总价。
新建一个Android项目,我这里取名DrunkService,然后新建一个包aidl。
在aidl包上右键新建AIDL,如下图(图1):
图1
名字随意起,但是建议I开头,仅仅是建议......;
接下来会看到AS产生一个和java平级的aidl文件夹,新建的xxx.aidl文件拥有和创建位置一样的包路径(图2)。
打开IDrunkSelect类如下图(图3):
类中会有一个默认的类,而这个basecType是我们不需要的,它存在的意思是提醒我们这里可以直接使用的六种基本类型(其实还有CharSequence、List、Map、自定义),删掉basicType,写我们自己的方法:
interface IDrunkSelect { /** * 饮料简单信息列表 */ List<String> getDrunkInfoList(); /** * 某个饮料详细信息 */ Drunks getDrunk(int id); /** * 修改库存 */ void subtractNum(int id, int subNum);}
Drunks是什么鬼?别急,这就说。
在IDrunkSelect 同包下新建普通类Drunks,继承Parcelable接口,代码如下:
关于Parcelable可以看这里Android序列化
package com.shareye.drunkservice.aidl;import android.os.Parcel;import android.os.Parcelable;/** * Created by ShuaiZhang on 2016/6/24. */public class Drunks implements Parcelable{ private int id; private String name; private double price; private int num; public Drunks(){ super(); } public Drunks(int id, String name, double price, int num){ this.id = id; this.name = name; this.price = price; this.num = num; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { //这里要保证顺序的正确定 dest.writeInt(id); dest.writeString(name); dest.writeDouble(price); dest.writeInt(num); } public static final Parcelable.Creator<Drunks> CREATOR = new Parcelable.Creator<Drunks>() { public Drunks createFromParcel(Parcel in) { Drunks s = new Drunks(); s.setId(in.readInt()); s.setName(in.readString()); s.setPrice(in.readDouble()); s.setNum(in.readInt()); return s; } public Drunks[] newArray(int size) { return new Drunks[size]; } };}
这个时候的Drunks还不可以在AIDL中使用,需要在AIDL中声明。
相同位置新建Drunks.aidl
代码如下:
// Drunks.aidlpackage com.shareye.drunkservice.aidl;// Declare any non-default types here with import statementsparcelable Drunks;
仔细看,默认生成的interface Drunks{}被删除掉了。这个很重要。
好了,然后Build一下。
依然不能通过编译,IDrunksService依然找不到Drunks,这里需要在IDrunksService中手动引入:
import com.shareye.drunkservice.aidl.Drunks;Build吧,这个时候就正常了。
新建一个DrunkDataUtil类作为数据源。
public class DrunksDataUtil { public static Drunks[] drunks= { new Drunks(1,"橙汁",3.50,30),new Drunks(2,"橙汁听装",2.00,20), new Drunks(3,"雪碧",3.50,20),new Drunks(4,"雪碧听装",2.00,25), new Drunks(5,"咖啡",5.00,27),new Drunks(6,"咖啡听装",3.5,15) };}
新建个类,继承IDrunkSelect.Stub,实现默认方法;这一步也可以省略,在之后的Service中使用匿名内部类也可以。但是匿名内部类有时候不太好控制。代码如下:
public class DrunkSelectImp extends IDrunkSelect.Stub { @Override public List<String> getDrunkInfoList() throws RemoteException { List<String> list = new ArrayList<>(); for(Drunks drunks : DrunksDataUtil.drunks){ list.add(drunks.getId()+"."+drunks.getName()+drunks.getPrice()); } return list; } @Override public Drunks getDrunk(int id) throws RemoteException { return DrunksDataUtil.drunks[id]; } @Override public void subtractNum(int id, int subNum) throws RemoteException { DrunksDataUtil.drunks[id].setNum(DrunksDataUtil.drunks[id].getNum()-subNum); }}
然后是特别简单的Service:
public class DrunkService extends Service { IBinder iBinder = new DrunkSelectImp(); @Override public IBinder onBind(Intent intent) { return iBinder; }}
重要AIDL部分的写完了,接下来就是简单界面代码了。
Activity_main布局文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/lst_drunk" android:layout_width="500dp" android:layout_height="500dp"/></RelativeLayout>
Item_lst_drunk布局文件
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <TextView android:id="@+id/txt_id" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/txt_name" android:layout_marginLeft="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/txt_price" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/txt_num" android:layout_marginLeft="40dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout></LinearLayout>
MainActivity代码:
package com.shareye.drunkservice;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ListView;import android.widget.TextView;import com.shareye.drunkservice.aidl.Drunks;public class MainActivity extends AppCompatActivity { private DrunkAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); adapter = new DrunkAdapter(); ListView lstView = (ListView) findViewById(R.id.lst_drunk); lstView.setAdapter(adapter); } @Override protected void onResume() { super.onResume(); //重新进入界面刷新数据 adapter.notifyDataSetChanged(); } private class DrunkAdapter extends BaseAdapter{ @Override public int getCount() { return DrunksDataUtil.drunks.length; } @Override public Drunks getItem(int position) { return DrunksDataUtil.drunks[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Drunks drunks = getItem(position); //演示,就不写ViewHolder了 convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_lst_main,null); TextView txtId = (TextView) convertView.findViewById(R.id.txt_id); txtId.setText(drunks.getId()+"."); TextView txtName = (TextView) convertView.findViewById(R.id.txt_name); txtName.setText(drunks.getName()+""); TextView txtPrice = (TextView) convertView.findViewById(R.id.txt_price); txtPrice.setText(drunks.getPrice()+""); TextView txtNum = (TextView) convertView.findViewById(R.id.txt_num); txtNum.setText(drunks.getNum()+""); return convertView; } }}
有没有发现忘记注册Service了?
<service android:name=".DrunkService"> <intent-filter> <action android:name="com.shareye.drunkservice.DrunkService" /> </intent-filter> </service>
接下来是Client端:
新建项目,我这里取名DrunkClient,这个随便来。
然后把上个项目的的AIDL移植到当前新建的项目中,方法如下:
1.打开项目源文件位置project-->app-->src-->main,这里可以看到一个aidl的文件夹;
2.复制该aidl文件夹;
3.类似第一步打开新建项目的main文件夹
4.粘贴,然后AS中就可以看到aidl的包和文件了
剩下就是通常的Android知识了,代码里做了详细注释,直接看代码就可以了。不过使用流程这里要说一下:进入后点击“开始”按钮,程序从Service端获取到饮料的列表信息,显示在下方的spinner中,点击选择某个饮料,选择的详细信息显示在spinner下面的面板,填写购买数量后点击确认,会看到详细信息中数量有所减少,打开Service端也可看到数量同样减少(这是废话)。如果将两个app都退出再打开,数据重置。
MainActivity.class代码:
public class MainActivity extends AppCompatActivity { //第一次为Spinner填充数据是不触发select事件 private boolean isFirst = true; //连接标识,主要是为了不重复解绑,导致崩溃 private boolean bBinder; //ADIL的接口 private IDrunkSelect iDrunkSelect; //Spinner的数据源,也就是信息列表的数据 private List<String> selectList; //。。。。。。 Spinner spinner; //。。。。。 private Button btnStart,btnOk; //Spinner的自定义Adapter MySpinnerAdapter spinnerAdapter; //绑定服务时的连接器 ServiceConnection connection = new ServiceConnection() { //成功连接时回调 @Override public void onServiceConnected(ComponentName name, IBinder service) { iDrunkSelect = IDrunkSelect.Stub.asInterface(service); bBinder = true;//标示已连接 Toast.makeText(MainActivity.this,bBinder+"",Toast.LENGTH_SHORT).show(); } //连接异常时触发回调 @Override public void onServiceDisconnected(ComponentName name) { bBinder =false;//已断开连接 } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnOk = (Button) findViewById(R.id.btn_buy); btnOk.setClickable(false); btnOk.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = spinner.getSelectedItemPosition(); EditText editText = (EditText) findViewById(R.id.edt_num); //这里应该判断数量小于总量的,懒得写了 int num = Integer.parseInt(editText.getText().toString()); try { iDrunkSelect.subtractNum(position,num);//沟通Service端减去被购买数量 getDrunkByPosition(position);//获取购买后信息 Toast.makeText(MainActivity.this,"Success",Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } }); btnStart = (Button) findViewById(R.id.btn_start); btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { selectList = iDrunkSelect.getDrunkInfoList();//获取饮料信息列表 spinnerAdapter.setList(selectList); } catch (RemoteException e) { e.printStackTrace(); } } }); //下面几个没啥好说的 spinner = (Spinner) findViewById(R.id.spn_select); selectList = new ArrayList<>(); spinnerAdapter = new MySpinnerAdapter(selectList,this); spinner.setAdapter(spinnerAdapter); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (isFirst) {//防护机制 isFirst = false; return; } getDrunkByPosition(position); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } /** * 通过AIDL获取选中项的完整信息 * @param position 下标 */ private void getDrunkByPosition(int position) { try { Drunks drunks = iDrunkSelect.getDrunk(position); showDrunks(drunks); btnOk.setClickable(true); } catch (RemoteException e) { e.printStackTrace(); } } /** * 显示某个选中项的全部信息 * @param drunks */ private void showDrunks(Drunks drunks) { TextView txtId = (TextView) findViewById(R.id.txt_id); txtId.setText("编号:"+drunks.getId()); TextView txtName = (TextView) findViewById(R.id.txt_name); txtName.setText("品名:"+drunks.getName()); TextView txtPrice = (TextView) findViewById(R.id.txt_price); txtPrice.setText("单价:"+drunks.getPrice()+""); TextView txtNum = (TextView) findViewById(R.id.txt_number); txtNum.setText("数量:"+drunks.getNum()); } @Override protected void onStart() { super.onStart(); //绑定服务,开始AIDL Intent intent = new Intent("com.shareye.drunkservice.test.DrunkService"); bindService(getExplicitIntent(this,intent), connection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); if (bBinder)//解绑 unbindService(connection); bBinder = false; } /** * 5.0不允许隐式启动,将隐式Intent转成显式。 */ public Intent getExplicitIntent(Context context, Intent implicitIntent) { // 检索所有能够响应该Intent的服务类 PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); // 如果查询结果不唯一,退出 if (resolveInfo == null || resolveInfo.size() != 1) { return null; } // 获取组件的信息,然后创建对应的ComponentName对象 ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); // 重用旧Intent对象的信息来新建一个Intent Intent explicitIntent = new Intent(implicitIntent); // 将前面得到的CompontName加入新intent explicitIntent.setComponent(component); return explicitIntent; } /** * 自定义Adapter,除了没做holder优化没啥可说的 */ private class MySpinnerAdapter extends BaseAdapter{ private List<String> list; private Context context; public MySpinnerAdapter(List<String> list, Context context) { this.list = list; this.context = context; } @Override public int getCount() { return list.size(); } @Override public String getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } public void setList(List<String> list){ this.list = list; notifyDataSetChanged(); } @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(context).inflate(R.layout.item_spn_drunk,null); TextView name = (TextView) convertView.findViewById(R.id.txt_describe); name.setText(getItem(position)); return convertView; } }}
完整Demo下载地址
上面的整个来说,其实是一个固定流程,把握套路后就简单了很多,根据个人理解和认识,画了下面一张图,希望有助于各位理解(图4)。
这里基于源码说名几个问题:
1.为什么DrunkSelectImp继承IDrunkSelect.Stub而不是继承IDrunkSelect?
这里先看一个Stub的声明截图(图5):
可以看到Stub是继承了IDrunkSelect的,也是DrunkSelectImp间接继承了IDrunkSelect,但是它这里又继承了BInder,这为什么呢?
打个比喻,两家分公司寄东西,自己不方便送就得找快递公司吧,这里的Binder就是类似“快递公司”的作用。先打上BInder的标记运输,再转回公司的模板使用。
2.按照1的说法,我们定义两个内容相同名字相同的接口不就可以使用了?我使用的到底是谁?
如果两边不统一,会报异常,具体原因我没找到,下面是查找原因时的可能地方,供参考一下。
Stub的第一行代码是一个final的String常量,该常量根据包名+类名来自动命名(图6)。
这是一个身份标识,它的作用就是检查是否互相匹配。而且可以看到我的截图多结了一个构造器,构造器内的方法直接是调用了一个父类的方法。这里接下来会用到。
接下来看一下(图7)我们如何从“快递”手里变回“分公司”自己用的
这段代码分析一下,我们最需要关心的是iin的那一个判断。点进去看一下iin的获取过程,也就是BInder类中的queryLocalInterface(String str)方法(图8):
mDescriptor又是谁?哪里来的?Stub的构造器所调用函数(Binder的)对比一下,就是它(图9)!
跨进程传过来的IBunder对象携带的信息,在和当下完整路径做匹配会返回一个null;只有本进程的调用才会在这里拿到inn非空对象。
由于这里iin为空,我们实际使用的是一个代理Proxy类。
完事!吐槽一下,这特么大概是我到目前为止写的篇幅最长的一篇文档了,真累!
- Android Studio 实现使用AIDL的IPC通讯
- android studio 使用AIDL实现IPC
- android使用AIDL实现跨进程通讯(IPC)
- android使用AIDL实现跨进程通讯(IPC)
- 示例:Android使用AIDL实现跨进程通讯(IPC)
- Android AIDL实现进程间通讯IPC
- Android进阶:AIDL实现IPC使用详解
- 在Android使用AIDL实现IPC机制
- 关于对Android使用AIDL进行IPC通讯的一点见解
- 使用AIDL实现IPC
- Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用
- Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用
- Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用
- android学习之使用AIDL实现进程间的通讯
- Android IPC机制之AIDL的使用
- Android IPC之AIDL的使用
- Android Studio AIDL 的使用 。
- Android Studio创建AIDL文件并实现进程间通讯
- c#打包安装程序[VS2010]
- 认识Composer
- Windows下mongoDb 安装、启动和初识
- Java基础学习总结——Java对象的序列化和反序列化
- makefile中的目标的依赖该怎么写?
- Android Studio 实现使用AIDL的IPC通讯
- [iOS 扩展转] iOS扩展开发-Today扩展&share扩展
- MD5Util (MD5加密摘要算法)。
- centos安装配置svn
- SVN还原文件到历史版本详解
- RecyclerView源码分析
- 阈值化
- 剑指Offer:面试题12——打印1到最大的n位数(java实现)
- 定价类型更新