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)。

图2

        打开IDrunkSelect类如下图(图3):

图3

        类中会有一个默认的类,而这个basecType是我们不需要的,它存在的意思是提醒我们这里可以直接使用的六种基本类型(其实还有CharSequenceListMap、自定义),删掉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)。

图4


 

        这里基于源码说名几个问题:

        1.为什么DrunkSelectImp继承IDrunkSelect.Stub而不是继承IDrunkSelect

        这里先看一个Stub的声明截图(图5):

图5

        可以看到Stub是继承了IDrunkSelect的,也是DrunkSelectImp间接继承了IDrunkSelect,但是它这里又继承了BInder,这为什么呢?

打个比喻,两家分公司寄东西,自己不方便送就得找快递公司吧,这里的Binder就是类似“快递公司”的作用。先打上BInder的标记运输,再转回公司的模板使用。

        2.按照1的说法,我们定义两个内容相同名字相同的接口不就可以使用了?我使用的到底是谁?

        如果两边不统一,会报异常,具体原因我没找到,下面是查找原因时的可能地方,供参考一下。

        Stub的第一行代码是一个finalString常量,该常量根据包名+类名来自动命名(图6)。

图6

        这是一个身份标识,它的作用就是检查是否互相匹配。而且可以看到我的截图多结了一个构造器,构造器内的方法直接是调用了一个父类的方法。这里接下来会用到。

        接下来看一下(图7)我们如何从“快递”手里变回“分公司”自己用的

图7

        这段代码分析一下,我们最需要关心的是iin的那一个判断。点进去看一下iin的获取过程,也就是BInder类中的queryLocalInterface(String str)方法(图8):

图8

        mDescriptor又是谁?哪里来的?Stub的构造器所调用函数(Binder的)对比一下,就是它(图9)!

图9

        跨进程传过来的IBunder对象携带的信息,在和当下完整路径做匹配会返回一个null;只有本进程的调用才会在这里拿到inn非空对象。

        由于这里iin为空,我们实际使用的是一个代理Proxy类。



        完事!吐槽一下,这特么大概是我到目前为止写的篇幅最长的一篇文档了,真累!








0 0
原创粉丝点击