Android進程間通訊AIDL使用及問題詳解(雷驚風)

   之前對AIDL用的不是很多,今天抽時間對其做一下詳細的瞭解,下面本人主要從以下幾個方面對AIDL做一下總結:

1.什麼是AIDL

2.爲什麼Android中要有AIDL

3.什麼時候使用AIDL

4.具體怎麼實現AIDL

下面我們就進入今天的分析,分析代碼Demo會在最後附上下載地址。

 

一.那麼首先什麼是AIDL呢?

AIDL全稱爲Android Interface definition language,顧名思義它是一種Android內部進程通信接口的描述語言,他媽怎麼這麼繞啊,簡單的說它就是Android進程(現在可以先知道每一個App就是一個單獨的進程【一個App也可以定義不同的進程】)間通信的橋樑,通過它我們可以定義進程間的通信接口(通過它我們可以在進程間進行相互操作)。

 

二.爲什麼Android中要有AIDL呢?

因爲Android中進程與進程之間是不能相互訪問的,每一個進程只能訪問自己進程內的數據及操作,每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存,都在自己的內存上存儲自己的數據,執行自己的操作,相互之間不能通信。

 

三.什麼時候使用AIDL

Android官方文檔介紹AIDL中有這麼一句話:【Note: Using AIDL is necessary onlyif you allow clients from different applications to access your servicefor IPC and want to handle multithreading in your service. If youdo not need to perform concurrent IPC across different applications, you should create yourinterface by implementing a Binder or,if you want to perform IPC, butdo not need to handle multithreading, implement yourinterface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.】只有當你允許來自不同的客戶端訪問你的服務並且需要處理多線程問題時你才必須使用AIDL,其他情況下你都可以選擇其他方法,如使用Messager,也能跨進程通訊。可見AIDL是處理多線程、多客戶端併發訪問的。而Messager是單線程處理。

 

四.怎麼實現AIDL

下面AIDL我們分成兩步:

1.Server端代碼編寫,及爲我們提供服務的一端,比如我們有好幾個App,有一個App中有一個超牛逼的算法,我們另外幾個App中也要用這個算法,這個Server端就是提供算法的那個App

2.編寫Client端代碼Client端爲其他幾個App

 

1.下面我們看一下第一步:server端代碼編寫。

我們以客戶端將兩個int值傳遞給服務端,服務端進行相加後返回爲例:

AS中新建一個項目,創建過程跟普通項目沒有區別。然後創建我們的*.aidl文件,名稱可以自己定,我這裏就叫IMyAidlInterface.aidl,在我們新建項目的app文件路徑上右鍵new->AIDL->AIDL File,如下圖:


創建成功的話會在項目中新增如下目錄結構:


可以看見與java同級出現了一個aidl目錄,其下邊的包路徑與java包路徑相同,再看我們新創建的.aidl文件內容,默認會有一個void basicTypes()方法,其中參數爲aidl支持的幾種傳參基本數據類型(Aidl默認支持的類型包話java基本類型(intlongboolean等)和(StringListMapCharSequence),如果要傳遞自定義類型,首先要讓自定義類型支持parcelable協議),然後我們可以刪掉默認方法,添加我們自己的方法,如下:


然後Build->rebuild project,在之前eclipse中會自動生成.java文件,AS不行,我們必須重新編譯一下項目,然後會在如下文件目錄結構中出現我們夢寐以求的.java文件。


其代碼如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\lylsoft\\android\\androidstudio\\mydemo\\AidlClient\\app\\src\\main\\aidl\\com\\jason\\aidl\\aidldemo\\IMyAidlInterface.aidl
 */
package com.jason.aidl.aidldemo;
// Declare any non-default types here with import statements
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.jason.aidl.aidldemo.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.jason.aidl.aidldemo.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.jason.aidl.aidldemo.IMyAidlInterface interface,
 * generating a proxy if needed.
 */
public static com.jason.aidl.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jason.aidl.aidldemo.IMyAidlInterface))) {
return ((com.jason.aidl.aidldemo.IMyAidlInterface)iin);
}
return new com.jason.aidl.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jason.aidl.aidldemo.IMyAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int add(int a, int b) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}

可以看到,在我們編譯的出的.java文件中有一個Stub內部類,我們在MainActivity同目錄下創建一個Service,創建一個Stub類實現add()方法後在onBind()方法中返回,代碼如下:

package com.jason.aidl.aidldemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.jason.aidl.aidldemo.Person;

public class MyAidlService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Log_LYL", "Onstart");
        return super.onStartCommand(intent, flags, startId);

    }
    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
    IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
            };
}

到這裏我們的Server端代碼就完成了?沒有,我們要知道,我們其他項目中沒有這個Service,所以我們要想在其他App中打開這個service必須通過隱式意圖,所以我們必須在我們的Manifest.xml中添加一個action,如下:

<service
    android:name=".MyAidlService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.lyl.aidl"/>
    </intent-filter>
</service>

到這裏服務端工作算是基本完成了。

2.下面看第二步編寫Client端代碼。

新建一個Client端項目,我們將服務端整個aidl下的文件統統考本到Client端與java同級目錄下,如下圖:



編譯項目後會在Client端也生成一個.java文件,且與Server端生成的.java文件一模一樣,然後我們就可以在我們的activity_main.xml中佈局,然後在MainAcitivity中進行綁定service進行遠程調用運算了,xml代碼與activity代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="客戶端"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="15sp" />

    <EditText
        android:id="@+id/et_num1"
        android:layout_width="200dp"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/et_num2"
        android:layout_width="200dp"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="add"
        android:text="加運算" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="結果:" />

        <TextView
            android:id="@+id/tv"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2" />
    </LinearLayout>
</LinearLayout>

package com.jason.aidl.client.aidlclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.jason.aidl.aidldemo.IMyAidlInterface;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    private EditText et_num1, et_num2;
    private TextView tv;
    private IMyAidlInterface mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_num1 = (EditText) findViewById(R.id.et_num1);
        et_num2 = (EditText) findViewById(R.id.et_num2);
        tv = (TextView) findViewById(R.id.tv);
        Intent intent = new Intent();
        intent.setAction("com.lyl.aidl");

        Intent intent1 = new Intent(createExplicitFromImplicitIntent(this, intent));
        bindService(intent1, mServiceC, Context.BIND_AUTO_CREATE);
    }

    public void add(View v) {
        int a = Integer.valueOf(et_num1.getText().toString());
        int b = Integer.valueOf(et_num2.getText().toString());
        try {
            int res = mService.add(a, b);
            tv.setText(res+"");

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    ServiceConnection mServiceC = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    /**
     * 兼容Android5.0中service的intent一定要顯性聲明
     *
     * @param context
     * @param implicitIntent
     * @return
     */
    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        //通過queryIntentActivities()方法,查詢Android系統的所有具備ACTION_MAIN和CATEGORY_LAUNCHER
        //的Intent的應用程序,點擊後,能啓動該應用,說白了就是做一個類似Home程序的簡易Launcher 。
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }

        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);

        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);

        // Set the component to be explicit
        explicitIntent.setComponent(component);

        return explicitIntent;
    }
}

這裏用到了createExplicitFromImplicitIntent()方法是因爲Android5.0serviceintent一定要顯性聲明否則會報如下錯誤:

01-10 19:17:43.733 14662-14662/com.jason.aidl.client.aidlclient E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jason.aidl.client.aidlclient, PID: 14662
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.jason.aidl.client.aidlclient/com.jason.aidl.client.aidlclient.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.lyl.aidl }
                                                                                      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2381)
                                                                                      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2443)
                                                                                      at android.app.ActivityThread.access$800(ActivityThread.java:157)
                                                                                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                                                      at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                                      at android.os.Looper.loop(Looper.java:135)
                                                                                      at android.app.ActivityThread.main(ActivityThread.java:5344)
                                                                                      at java.lang.reflect.Method.invoke(Native Method)
                                                                                      at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
                                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
                                                                                   Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.lyl.aidl }
                                                                                      at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1781)
                                                                                      at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1880)
                                                                                      at android.app.ContextImpl.bindService(ContextImpl.java:1858)
                                                                                      at android.content.ContextWrapper.bindService(ContextWrapper.java:539)
                                                                                      at com.jason.aidl.client.aidlclient.MainActivity.onCreate(MainActivity.java:37)
                                                                                      at android.app.Activity.performCreate(Activity.java:6033)
                                                                                      at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
                                                                                      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2334)
                                                                                      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2443) 
                                                                                      at android.app.ActivityThread.access$800(ActivityThread.java:157) 
                                                                                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
                                                                                      at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                                      at android.os.Looper.loop(Looper.java:135) 
                                                                                      at android.app.ActivityThread.main(ActivityThread.java:5344) 
                                                                                      at java.lang.reflect.Method.invoke(Native Method) 
                                                                                      at java.lang.reflect.Method.invoke(Method.java:372) 
                                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908) 
                                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703) 

運行我們的Server端及Client端,在Client端的兩個editText中輸入兩個數字點擊按鈕運行結果如下:


看見運行結果與我們預期一樣,可見我們通過AIDL實現了AppApp之間的遠程調用。

 

以上只是一個簡單的基本數據類型操作,那麼我們怎麼通過AIDL進行對象的操作呢,我們在之前代碼上做些修改就行了,看下邊吧。

我們在Server端與我們創建的.aidl同目錄下創建一個Person.java的類與Person.aidl文件,目錄結構如下


Person.java中成員變量包括一個name,一個age,ctrl+insert添加set與get方法,然後實現Parcelable接口序列化,裏邊有些方法需要自己實現Creator啊writeToParcel啊什麼的,自己弄一下就行,兩次ctrl+enter就行,然後自己寫一個readFromParcel(Parcel dest)方法,要保證賦值順序與writeToParcel中一致,否則會出問題。具體可以看下邊代碼:

package com.jason.aidl.aidldemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
    private String name;
    private int age;
    public Person() {
    }
    protected Person(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }
    public void readFromParcel(Parcel dest) {
        name = dest.readString();
        age = dest.readInt();
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Person.java完成了,看一下Person.aidl文件代碼,很簡單,如下:

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

就兩行代碼一個引入包,一個parcelable Person;這裏注意前邊的那個單詞首字母是消協的,對,沒錯,相信自己的眼睛。


然後修改我們的IMyAidlInterface.aidl代碼如下:

// IMyAidlInterface.aidl
package com.jason.aidl.aidldemo;

// Declare any non-default types here with import statements
import com.jason.aidl.aidldemo.Person;
interface IMyAidlInterface {
            //處理基本類型;
             int add(int a,int b);
             //處理對象;
             String inPerson(in Person p);
             String outPerson(out Person p);
             String inOutPerson(inout Person p);
}

上面的代碼中你需要手動引入Person類,因爲在這裏系統不會幫你引入,切記切記。還有你還會發現在方法的參數類型前有in、out、inout幾個東西,不懂不要後邊我們再分析。

Rebuild我們的項目你會發現報如下錯誤:



擦,爲什麼呢,因爲AS是gradle構建項目的,如果不配置,它默認不會從aidl文件夾下尋找資源,所以我們需要在我們app下的build.gradle中添加如下配置:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}

之後在次rebuild就沒問題了,運行後你會發現我們生成的.java文件中多了上邊的幾個剛剛操作對象的方法,代碼就不粘了,太多了,都是系統生成的,本篇就不分析.java中的代碼了,然後修改Service中的代碼如下:

package com.jason.aidl.aidldemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.jason.aidl.aidldemo.Person;

public class MyAidlService extends Service {


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Log_LYL", "Onstart");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }

    IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }

        @Override
        public String inPerson(Person p) throws RemoteException {
            String old = "name:" + p.getName() + " age:" + p.getAge();
            Log.d("Log_LYL:inPerson_", old);
            p.setName("李四");
            p.setAge(13);
            return "name:" + p.getName() + " age:" + p.getAge();
        }

        @Override
        public String outPerson(Person p) throws RemoteException {
            String old = "name:" + p.getName() + " age:" + p.getAge();
            Log.d("Log_LYL:outPerson_", old);
            p.setName("週六");
            p.setAge(20);
            return "name:" + p.getName() + " age:" + p.getAge();
        }

        @Override
        public String inOutPerson(Person p) throws RemoteException {
            String old = "name:" + p.getName() + " age:" + p.getAge();
            Log.d("Log_LYL:inOutPerson_", old);
            p.setName("弓七");
            p.setAge(57);
            return "name:" + p.getName() + " age:" + p.getAge();
        }
    };
}

到這裏Server端的代碼就算是修改完成了,然後把我們添加了Person.java與Person.aidl文件的整個文件夾copy覆蓋掉我們Client端之前拷貝的包重新編譯,rebuild項目,報錯... 沒改app下的build.gradle吧,改。然後修改我們Client端的MainActivity中的textView賦值的try catch代碼塊,如下:

try {
    int res = mService.add(a, b);
    Person p1 = new Person();
    p1.setName("劉大");
    p1.setAge(3);
    Person p2 = new Person();
    p2.setName("趙二");
    p2.setAge(3);
    Person p3 = new Person();
    p3.setName("張三");
    p3.setAge(3);

    tv.setText(res + "\n" + mService.inPerson(p1) + "\n" + mService.outPerson(p2) + "\n" + mService.inOutPerson(p3)+"\n" + p1.toString() + "\n" + p2.toString() + "\n" + p3.toString());

} catch (RemoteException e) {
    e.printStackTrace();
}

然後運行Server端與Client端結果如下:


再看我們Server端服務中的打印信息:


我們通過Client端的顯示與Server端的打印信息分析一下上邊定義的時候的那個in、out、inout的作用,再看一下我們在.aidl中定義的三個方法:String inPerson(inPerson p);String outPerson(outPerson p);String inOutPerson(inoutPerson p);

先看第一個inPerson中定義的爲in,從Client端我們在textView賦值中調用inPerson傳入的Person值name爲“劉大”,在服務端打印中可知我們在服務端順利接收到了,然後我們將Person的name值改爲“李四”,但是我們Client端的Person本身沒有改變(通過UI中Person{name=劉大age=3}可知)。

Client端調用outPerson()傳入的p2對象在服務端沒有接收到(通過01-10 20:24:11.038 6451-6478/com.jason.aidl.aidldemo D/Log_LYL:outPerson_: name:null age:0打印信息可知),但是服務端修改了p2我們在Client端收到了修改後的p2(通過Person{name=週六age=20}可知)。

Client端調用inoutPerson()傳入p3對象在服務端可以接收到修改後在Client端也能發現P3被修改了。

由以上三種情況可以知道in的作用是Client端給Server端傳遞數據,Server段修改修改數據對客戶端沒有影響;而參數爲out時,Client端給服務端傳的值服務端是收不到的,服務端可以修改Client端傳遞過去的值,會影響到Client端隊形;最後inout就是二者的結合了,Client端傳遞的值Server端能夠收到,Server端修改了數據,會影響Client端對象數據,他們是雙向操作的。

 

到此關於AIDl前邊的幾個問題就都回答完了,總結一下我在編寫過程中遇到的幾個問題:

1.Client端在綁定Server端使用隱式方式時,要兼容Android5.0以上版本。

2.傳遞對象實現Parcelable接口時readFromParcel(Parcel dest)中獲取變量值順序要與writeFromParcel(Parcel dest)一致。

3.傳遞對象時,記得配置buidle.grable,兩端都要配置。

好了,AIDL基礎篇就到這裏吧,希望對你有幫助,謝謝!最後附上代碼下載地址:

http://download.csdn.net/detail/liuyonglei1314/9734165

Aidl更深入瞭解可以看我的另一篇博文:

http://blog.csdn.net/liuyonglei1314/article/details/54849197

發佈了29 篇原創文章 · 獲贊 29 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章