美文网首页AndroidAndroid开发经验谈Android开发
IPC机制之AIDL的学习笔记(一)

IPC机制之AIDL的学习笔记(一)

作者: vison123 | 来源:发表于2017-08-21 20:39 被阅读63次

    前言

    ipc机制简介

    IPC机制是什么?最简单的名词解释就叫跨进程通信,意思是两个不同的进程之间可以相互协作完成一些事情。比如a进程调用b进程的一些方法来达到更新列表的目的。在Android系统中一个应用默认只有一个进程,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题,为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。

    Andriod多进程使用方法

    • 通过 JNI 在 native 层 fork 一个新的进程。(比较少用)
    • 在AndroidManifest.xml的声明四大组件的标签中增加”android:process”属性即可
      1.以:为前缀:在当前进程前面加上包名。属于当前App的私有进程,其他App的组件不可以和她跑在同一个进程中。
      2.以.为前缀:不会附加包名。全局进程,其他App通过 ShareUID 的方式可以和她跑在一个进程中。

    效果图展示

    gif

    正文

    AIDL简介

    描述

    • AIDL(Android接口描述语言)是一个IDL语言,它可以生成一段代码,可以是一个在Android设备上运行的两个进程使用内部通信进程进行交互。在Android上,一个进程通常无法访问另一个进程的内存。AIDL只有在你允许来自不同应用的客户端跨进程通信访问你的Service,并且想要在你的Service种处理多线程的时候才是必要的。 简单地来说,就是多个客户端,多个线程并发的情况下要使用 AIDL 。

    语法

    • 通常引引用方式传递的其他AIDL生成的接口,必须要import 语句声明。
      Java编程语言的主要类型 (int, boolean等) —不需要 import 语句。
      在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件中支持哪些数据类型呢?
      如下所示:
      1、基本数据类型(int,long,char,boolean,float,double,byte,short八种基本类型);
      2、String和CharSequence;
      3、List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
      4、Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
      5、Parcelable:所有实现了Parcelable接口的对象;
      6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
      以上6中数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。

    • 关于Aidl中in out inout修饰参数的理解【摘自你真的理解AIDL中的in,out,inout么?
      写得很详细,摘录记录一下笔记
      AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

    AIDL的使用步骤

    • 在AS中我们可以在同一个project中实现多进程的测试,只要在服务端的service在manifest文件中指定android:process=":remote"属性,它就是运行在另外一个进程的服务了。当然,这种做法是比较简单的。另外一种方法是将服务端和客户端分别运行不同的项目中。
      今天项目是要建立两个module,一个服务端module,一个客户端module。虽然上面两种方法的机制是一样的。但是运行在两个module的两个细节还是挺多的。今天主要写一下AIDL的使用方法和其中的细节采坑。

    目录两个module的目录接口

    客户端
    服务端

    服务端

    1.创建Person.java【关于Parcelable的创建不在这里介绍,aidl中客户端和服务端的数据必须是可以序列化的】

    package com.example.newserver;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Created by Administrator on 2017/8/21.
     */
    
    public class Person implements Parcelable {
        private int id;
        private String name;
        private String age;
    
        public Person() {
        }
    
        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 String getAge() {
            return age;
        }
    
        public void setAge(String age) {
            this.age = age;
        }
    
        protected Person(Parcel in) {
            id = in.readInt();
            name = in.readString();
            age = in.readString();
        }
    
        public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public Person[] newArray(int size) {
                return new Person[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(id);
            dest.writeString(name);
            dest.writeString(age);
        }
    
        public void readFromParcel(Parcel dest){
            id = dest.readInt();
            name = dest.readString();
            age = dest.readString();
        }
    }
    

    2.创建AIDL文件
    两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
    注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用
    首先创建 Person.aidl

    // Person.aidl
    package com.example.newserver;
    
    // Declare any non-default types here with import statements
    
    parcelable Person;
    

    然后创建IMyAidlInterface.aidl

    // IMyAidlInterface.aidl
    package com.example.newserver;
    import com.example.newserver.Person;
    
    // Declare any non-default types here with import statements
    
    interface IMyAidlInterface {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void addPerson(in Person person);
         List<Person> getPersonList();
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
    }
    

    注意点:

    • Person.aidl与Person.java的包名应当是一样的。
      关于这点有的人说需要把这两个文件同时放在aidl文件夹下就可以了,但是有的人说这样没用,系统会找不到java文件,Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度。Gradle 默认是将 java 代码的访问路径设置在 java 包下的。如果不配置,它就不会在aidl文件夹下找到java文件。关于这点的解决方法是在gradle文件下配置一下soureSets
    sourceSets {
            main {
                java.srcDirs = ['src/main/java', 'src/main/aidl']
            }
        }
    

    这样他就会在aidl文件夹找java文件了,其实发现后来运行的时候不配置也没有报错。不知道什么原因,如果你的工程包类找不到。你就尝试加这个东西吧。
    当然还有另外一种解决方法。就是保证aidl【com.example.server】和java【com.example.server】的文件夹名字是相同的。然后把person.java文件放在java文件夹下面就不会有什么问题啦,不过我一般都会采用第二种方法,放在java文件下面。关于两种方法你都可以试一下。感觉第一种比较简单实用。

    3.创建service【MyService.java】

    
    public class MyService extends Service {
    
       ArrayList<Person> mPersonArrayList = new ArrayList<>();
    
       IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
           @Override
           public void addPerson(Person person) throws RemoteException {
               if (null == mPersonArrayList) {
                   mPersonArrayList = new ArrayList<>();
               }
               mPersonArrayList.add(person);
           }
    
           @Override
           public List<Person> getPersonList() throws RemoteException {
               if (null != mPersonArrayList) {
                 return mPersonArrayList;
               }
               return null;
           }
    
           @Override
           public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
    
           }
       };
    
       @Nullable
       @Override
       public IBinder onBind(Intent intent) {
           return mBinder;
       }
    }
    

    基本上就是实现接口的方法,这里会创建一个Binder返回给客户端,这个Binder是已经实现了定义的接口方法的,所以客户端会根据这个binder调用相关的接口方法。这样就实现了客户端调用了服务端定义的接口方法了,关于binder的原理可以看看这一篇的,写的比较详细Binder的原理讲解,我们可以在这里了解一下binder的原理。通过上述步骤实际上就实现了客户端与服务端的通信了,关于客户端怎么拿到binder,具体又怎么用binder的,我们等会看看客户端的代码就知道了。

    客户端实现

    1.将aidl整个文件夹复制到客户端module下面去
    而且在aidl中涉及的java文件也要拷贝过去。例如本例中的person文件。由于我在服务端module中的person.java文件放在了java文件目录下面,所以我拷贝的时候也将它放在了客户端module对应的java文件夹下面。这个时候就有个问题要注意的,因为这里真的是完全拷贝。
    我们看看客户端person.java

    person

    这里package的目录页必须是server目录的person.java文件的目录,也就是

    package com.example.newserver;
    

    而不是

    package com.example.client;
    

    否则变异会报错。上面标红只是警告,编译不会有问题的。
    还有客户端这里的aidl文件夹下面的aidl文件,涉及到person导包的,都是要导入server下面的person的,即

    image.png

    这里复制过来的文件路径都不能因为迁移到客户端改动。

    2.我们来看看 MainActivity中怎么获取和使用Binder吧

    public class MainActivity extends AppCompatActivity {
      
        IMyAidlInterface binder;
    
        ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                binder = IMyAidlInterface.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mContext = this;
            initView();
        }
    
        private void initView() {
            etId = (EditText) findViewById(R.id.et_id);
            etName = (EditText) findViewById(R.id.et_name);
            etAge = (EditText) findViewById(R.id.et_age);
            btnAdd = (Button) findViewById(R.id.btn_add);
            btnGet = (Button) findViewById(R.id.btn_get);
            mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
            final MyAdapter myAdapter = new MyAdapter(mContext);
            mRecyclerView.setAdapter(myAdapter);
            btnAdd.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    name = etName.getText().toString();
                    id = etId.getText().toString();
                    age = etAge.getText().toString();
                    if (checkData()) {
                        try {
                            Person person = new Person();
                            person.setName(name);
                            person.setId(Integer.valueOf(id));
                            person.setAge(age);
                            binder.addPerson(person);
                            clearPerson();
                            Toast.makeText(mContext,"添加英雄成功,点击获取任务可查看英雄列表",Toast.LENGTH_LONG).show();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
    
            btnGet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        if (null != binder) {
                            mList = (ArrayList<Person>) binder.getPersonList();
                        }
                        if (null != mList && mList.size() > 0) {
                            myAdapter.addList(mList);
                        } else {
                            Toast.makeText(mContext,"还没有添加英雄,赶紧添加一个吧~",Toast.LENGTH_SHORT).show();
                        }
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            });
            Intent intent = new Intent();
            intent.setPackage("com.example.newserver");
            intent.setAction("com.example.newserver.aidl.IMyAidlInterface");
            bindService(intent,mConnection,mContext.BIND_AUTO_CREATE);
        }
    }
    

    上面我只看核心的实现。定义一个刚才我们在服务端定义的接口变量。然后在ServiceConnection中的onServiceContected方法中将IBinder接口转换一下这样我们就可以使用IMyAidlInterface 这个接口来调用服务端定义的方法了。

    运行代码

    • 首先我们先跑我们的服务端的代码,然后点击主界面让他运行在后台中。
    • 然后跑一下客户端代码。这样就可以看到效果了。

    结语

    我们看到关于实现是非常简单的,今天先写一下实现过程,下一章在写它到底是怎么实现,
    然后我们可以脱离aidl代码来实现这个功能。

    代码传送门
    传送门~

    相关文章

      网友评论

        本文标题:IPC机制之AIDL的学习笔记(一)

        本文链接:https://www.haomeiwen.com/subject/mwzjlxtx.html