推荐一篇很全面的关于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.aidl
package com.shareye.drunkservice.aidl;
// Declare any non-default types here with import statements
parcelable 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 AIDL的使用方法。包括AIDL文件的创建与配置、 Parcelable接口的实现、Service端与客户端的交互过程等。
1651

被折叠的 条评论
为什么被折叠?



