Binder的使用及手動實現AIDL

Binder是Android一個十分重要進程間通訊機制,Android系統的不少核心服務AMS,PMS,WMS的使用都是創建在Binder之上的。在對Activity啓動流程,App安裝流程源碼梳理過程當中,Binder也是咱們常常碰到的。所以,在咱們閱讀這些源碼以前,要弄清Binder是如何使用的。java

Binder的使用

咱們知道,Binder是基於C/S結構的,就像http接口請求調用。 這裏咱們想一想調用接口時,客戶端和服務端作了什麼:android

  • 客戶端封裝數據,指定某個接口名稱,發送數據給服務端
  • 服務端等待接收到某個接口請求時,解析請求參數,查詢操做相關服務(數據庫等)數據,而後封裝數據,返回數據
  • 客戶端返回數據,封裝數據(將流封裝成對象)

客戶端實現

Binder和以上流程相似,咱們使用AS分別建立Client,Server兩個項目。咱們模擬一個考試成績查詢場景,即經過學生名稱在服務端查詢該學生的成績。 那麼在客戶端,就有了以下實現:git

//學生
public class Student implements Parcelable {
    public String name;
    ...
}
複製代碼
public class ScoreProxy {
    private IBinder mRemote;
    private static final int TRANSACTION_query = 1;

    public ScoreProxy(IBinder mRemote) {//經過IBinder對象想服務端發送數據
        this.mRemote = mRemote;
    }

    public int query(Student student) {
        Parcel _data = android.os.Parcel.obtain();
        Parcel _reply = android.os.Parcel.obtain();
        int result = -1;
        try {
            _data.writeInterfaceToken("ScoreQuery");
            _data.writeParcelable(student, 0);
            mRemote.transact(TRANSACTION_query, _data, _reply, 0);
            result = _reply.readInt();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return result;
    }
}
複製代碼

query方法中,咱們經過·_data·傳入Student參數,經過_reply接收查詢結果,經過IBiner對象發送數據。github

服務端實現

在服務端,一樣建立Student類(包名相同),而後新建一個ScoreQueryService服務,並經過ScoreStubBinder類用於接收處理客戶端傳遞端數據。數據庫

public class ScoreQueryService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ScoreStub();
    }

    private static class ScoreStub extends Binder {
        private static final int TRANSACTION_query = 1;
        private Map<String, Integer> scoreMap = new HashMap<>();//模擬數據查詢

        public ScoreStub() {
            scoreMap.put("張三", 100);
            scoreMap.put("李四", 89);
            scoreMap.put("王五", 60);
        }

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            if (code == TRANSACTION_query) {
                data.enforceInterface("ScoreQuery");
                Student student = data.readParcelable(Student.class.getClassLoader());
                int score = query(student);
                Log.e("Server","query:"+student+",result:"+score);
                reply.writeInt(score);
                return true;
            }
            return super.onTransact(code, data, reply, flags);
        }

        private int query(Student s) {
            Integer score = scoreMap.get(s.getName());
            return score != null ? score : -1;
        }
    }
}
複製代碼

在清單文件中註冊這個服務bash

<service android:name="com.iamyours.service.ScoreQueryService" android:enabled="true" android:exported="true">
    <intent-filter>
        <action android:name="com.iamyours.score" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
複製代碼

服務調用

在安裝完Serverapk後,在Client端調用成績查詢服務,以下app

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                query();
            }
        });
        bindServices();
    }

    private ScoreProxy scoreProxy;

    private void bindServices() {
        Intent intent = new Intent();
        intent.setAction("com.iamyours.score");
        intent.setPackage("com.iamyours.server");//Server端applicationId
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.e("Client", "onServiceConnected");
                scoreProxy = new ScoreProxy(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("Client", "onServiceDisconnected:");

            }
        }, BIND_AUTO_CREATE);

    }

    private void query() {
        Student s = new Student("張三");
        int result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
        s = new Student("李四");
        result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
        s = new Student("馬雲");
        result = scoreProxy.query(s);
        Log.e("client", "result:" + result);
    }
}
複製代碼

最終調用結果以下: 服務端ide

com.iamyours.server E/Server: query:Student{name='張三'},result:100
com.iamyours.server E/Server: query:Student{name='李四'},result:89
com.iamyours.server E/Server: query:Student{name='馬雲'},result:-1
複製代碼

客戶端ui

com.iamyours.client E/client: result:100
com.iamyours.client E/client: result:89
com.iamyours.client E/client: result:-1
複製代碼

至此咱們簡單經過Binder實現一個成績查詢服務。this

使用APT實現AIDL

會看上面的代碼,咱們發現不少代碼是耦合在一塊兒的,ScoreProxyScoreStub有許多和業務無關的代碼,若是一個功能,數據的發送接收處理會產生相似的模版代碼。實際業務開發場景下,在客戶端咱們只須要定義接口方法,參數(就像Retrofit),並使用它。在服務端,咱們應該根據接口實現對應的業務邏輯。在它們中間數據如何傳輸,如何處理卻不是咱們關心的。 所以,在使用時,只須要定義好接口,而且在服務端實現它便可。而中間的數據傳輸相關的代碼是通用相似的,咱們能夠經過APT生成。 好比咱們定義了一個ISayHello的接口以下,並用自定義註解@AIDL聲明它:

@AIDL
public interface ISayHello {
    void sayHello();
    int sayHelloTo(String name);
    int query(Student s);
}
複製代碼

最終咱們但願自動生成在客戶端的ISayHelloProxy代理類以及服務端的實現類ISayHelloStub實現類,大概是這樣的:

public abstract class ISayHelloStub extends Binder implements ISayHello {
  private static final String DESCRIPTOR = "com.iamyours.interfaces.ISayHello";

  private static final int TRANSACTION_sayHello = android.os.IBinder.FIRST_CALL_TRANSACTION + 0;

  private static final int TRANSACTION_sayHelloTo = android.os.IBinder.FIRST_CALL_TRANSACTION + 1;

  private static final int TRANSACTION_query = android.os.IBinder.FIRST_CALL_TRANSACTION + 2;

  public static ISayHello asInterface(IBinder iBinder) {
    return new Proxy(iBinder);
  }

  @Override
  public abstract void sayHello();

  @Override
  public abstract int sayHelloTo(String var0);

  @Override
  public abstract int query(Student var0);

  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    String descriptor = DESCRIPTOR;
    switch(code){
    case TRANSACTION_sayHello:{
    data.enforceInterface(descriptor);
    this.sayHello();
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_sayHelloTo:{
    data.enforceInterface(descriptor);
    String _arg0  = data.readString();
    int _result = this.sayHelloTo(_arg0);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    case TRANSACTION_query:{
    data.enforceInterface(descriptor);
    com.iamyours.bean.Student _arg0 = data.readParcelable(com.iamyours.bean.Student.class.getClassLoader());
    int _result = this.query(_arg0);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    default: {
    return super.onTransact(code, data, reply, flags);
    }
    }
  }

  private static class Proxy implements ISayHello {
    private IBinder mRemote;

    Proxy(IBinder mRemote) {
      this.mRemote = mRemote;
    }

    @Override
    public void sayHello() {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      mRemote.transact(TRANSACTION_sayHello, _data, _reply, 0);
      _reply.readException();
      }catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
    }

    @Override
    public int sayHelloTo(String var0) {
      int _result = 0;
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      _data.writeString(var0);
      mRemote.transact(TRANSACTION_sayHelloTo, _data, _reply, 0);
      _reply.readException();
      _result = _reply.readInt();}catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
      return _result;
    }

    @Override
    public int query(Student var0) {
      int _result = 0;
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try{
      _data.writeInterfaceToken(DESCRIPTOR);
      _data.writeParcelable(var0,0);
      mRemote.transact(TRANSACTION_query, _data, _reply, 0);
      _reply.readException();
      _result = _reply.readInt();}catch(Exception e){e.printStackTrace();}finally{
      _reply.recycle();
      _data.recycle();
      }
      return _result;
    }
  }
}
複製代碼

能夠看到有很大部分是數據傳輸相關的,咱們只需經過APT生成便可,而Stub中的sayHello等方法經過抽象交給要實現最終業務的子類,從而實現代碼解耦。 咱們可使用javapoet庫生成代碼,在AbstractProcessor子類中的process方法遍歷找到AIDL註解,獲取接口中的方法列表:

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Map<Element, List<AidlMethod>> sources = new HashMap<>();
        for (Element e : roundEnvironment.getElementsAnnotatedWith(AIDL.class)) {
            List<AidlMethod> methods = new ArrayList<>();
            sources.put(e, methods);
            List<? extends Element> list = elementUtils.getAllMembers((TypeElement) e);
            for (Element ee : list) {
                boolean isAbstract = ee.getModifiers().contains(Modifier.ABSTRACT);
                if (isAbstract) {
                    methods.add(createAidlMethod(ee));
                }
            }
        }
        generateAIDL(sources);
        return true;
    }
複製代碼

其中AidlMethod包含了方法名,返回類型,參數列表

public class AidlMethod {
    public Class retCls;//PrimitiveType,基本類型,int,double等
    public ClassName retClsName;
    public List<ParamData> params;
    public int code;
    public String name;
}
複製代碼

而後經過createAidlMethod方法獲取接口方法的數據

private AidlMethod createAidlMethod(Element e) {
        AidlMethod aMethod = new AidlMethod();
        Type.MethodType mt = (Type.MethodType) e.asType();
        Type retType = mt.getReturnType();
        aMethod.name = e.getSimpleName() + "";
        if (retType instanceof Type.JCPrimitiveType) {
            aMethod.retCls = getPrimitiveType(retType);
        } else {
            if (!"void".equals(retType + "")) {
                aMethod.retClsName = ClassName.bestGuess(retType + "");
            }
        }

        List<Type> types = mt.getParameterTypes();
        List<ParamData> params = new ArrayList<>();
        for (Type t : types) {
            ParamData p = new ParamData();
            if (t instanceof Type.JCPrimitiveType) {
                p.cls = getPrimitiveType(t);
            } else if (t instanceof Type.ClassType) {
                Type.ClassType ct = (Type.ClassType) t;
                String cname = ct + "";
                if ("java.lang.String".equals(cname) || isParcelable(ct)) {
                    p.clsName = ClassName.bestGuess(cname);
                } else {
                    throw new RuntimeException("--unSupport param:" + t + ",in method:" + mt + " source:" + e);
                }
            } else {
                throw new RuntimeException("unSupport param:" + t + ",in method:" + mt + " source:" + e);
            }
            params.add(p);
        }
        aMethod.params = params;
        System.out.println(aMethod);
        return aMethod;
    }
複製代碼

最後在generateAIDL方法中生成StubProxy類代碼(詳細代碼能夠看這裏)。

代碼地址

github.com/iamyours/Bi…

相關文章
相關標籤/搜索