插件化知識詳細分解及原理 之Binder機制

轉http://blog.csdn.net/yulong0809/article/details/56841993。

最近一直在研究插件化的東西,我看了網上大多都是直接上來就開始講解原理然后寫個demo,這樣對于很多沒有入門的朋友不是很好的理解,下面我會通過自己的研究過程,一步一步循序漸進的將插件化需要的知識點都梳理一遍及講解,其實學習插件化的好處并不全因為它是一門熱門的技術,插件化涉及的知識點很多,可以讓我們對android的理解及境界上都會有一個質的飛躍,在我將所有設計的知識點都大概講一遍后會用一個demo來實現插件化,里面將設計所有講過的知識。

插件化其實就是動態加載,動態加載又包括了代碼加載和資源加載。

可以干什么:

插件化最早出現是因為65535問題出現的,用于查分多個dex并動態加載dex來防止65535問題

現在很多公司用插件化來做模塊的動態加載,這樣既能實現web端一樣的隨時更新,還能減少apk包的體積,其實就是加載一個未安裝的apk。

熱修復,熱修復其實也是動態加載原理

換膚,使用動態的資源加載可以實現換膚功能

還可以通過hook系統的一些類做一些你想做的壞事。

目前比較有名的插件化框架:

任玉剛的:dynamic-load-apk,這個項目使用的是一種代理的方式去實現?

https://github.com/singwhatiwanna/dynamic-load-apk

360的:DroidPlugin,這個項目是通過hook系統類來實現?

https://github.com/Qihoo360/DroidPlugin

目前比較火的熱修復框架:

阿里的:andfix,用于對方法的修復,可以立即生效,不支持資源及類替換?

https://github.com/alibaba/AndFix

騰訊的:tinker,除了不支持立即生效,全部支持

https://github.com/Tencent/tinker

美團的:robust,不開源

如果要使用插件化來作為模塊化的話,那么就需要解決兩個問題

代碼的加載,就是使用ClassLoader加載代碼

資源的加載,使用AssetManager的隱藏方法,addAsssetPath方法加入一個資源路徑來獲取這個資源的Resource資源

還有一個問題就是對四大組件的生命周期管理

準備:

在了解插件化之前首先需要了解及掌握的知識點?

一、Binder機制?

二、代理模式,?

三、反射?

四、類加載及dex加載?

五、應用啟動過程及類加載過程?

六、實現插件化完整demo及思路分析?

七、動態加載資源及解決資源沖突問題

Binder機制:

其實Binder看你怎么去理解,如果從代碼角度的話他是一個類,如果從硬件角度的話他是一個驅動,如果從IPC角度的話他是一種通信機制,是framework層的各種ServiceManager的鏈接橋梁,?

我們知道我們平時使用的系統服務對象其實都是系統的,他們存在的進程和我們的應用并不在一個進程中,但是為什么我們能直接使用呢?其實就是因為Binder的存在,跨進程通信,再說大白話一點就是使用了我們經常說的aidl,Binder很復雜,這里只是為了插件化做鋪墊,想深入理解請自行查閱資料。

進程間通信過程

1.首先客戶端要鏈接服務端

2.然后服務端會返回一個客戶端的對象(代理對象)

3.然后客戶端使用這個代理對象其中的方法時,系統會先調用服務端的方法,然后將運算的結果返回給客戶端(要知道其實并不是用了這個對象的方法,而是去服務端里運算,然后在返回給客戶端的)

我們通過自己寫一個aidl,然后和系統的源碼進行對比

//我們自己寫的aidl的接口

//IMyAidlInterface.aidl

package com.huanju.chajianhuatest;

import com.huanju.chajianhuatest.aidlmode.TestBean;

interface IMyAidlInterface {

? ? /**

? ? * Demonstrates some basic types that you can use as parameters

? ? * and return values in AIDL.

? ? */

? ? void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,

? ? ? ? ? ? double aDouble, String aString);

? ? String getS(in TestBean s);

? ? TestBean getInfoBean(out TestBean b);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

系統會幫我們自動創建一個IMyAidlInterface.java的文件,我們去看看

>

package com.huanju.chajianhuatest;

public interface IMyAidlInterface extends android.os.IInterface {

? ? public static abstract class Stub extends android.os.Binder implements com.huanju.chajianhuatest.IMyAidlInterface {

? ? ? ? private static final java.lang.String DESCRIPTOR = "com.huanju.chajianhuatest.IMyAidlInterface";

? ? ? ? public Stub() {

? ? ? ? ? ? this.attachInterface(this, DESCRIPTOR);

? ? ? ? }

? ? ? ? public static com.huanju.chajianhuatest.IMyAidlInterface asInterface(android.os.IBinder obj) {

? ? ? ? ? ? if ((obj == null)) {

? ? ? ? ? ? ? ? return null;

? ? ? ? ? ? }

? ? ? ? ? ? android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

? ? ? ? ? ? if (((iin != null) && (iin instanceof com.huanju.chajianhuatest.IMyAidlInterface))) {

? ? ? ? ? ? ? ? return ((com.huanju.chajianhuatest.IMyAidlInterface) iin);

? ? ? ? ? ? }

? ? ? ? ? ? return new com.huanju.chajianhuatest.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_basicTypes: {

? ? ? ? ? ? ? ? ? ? data.enforceInterface(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? int _arg0;

? ? ? ? ? ? ? ? ? ? _arg0 = data.readInt();

? ? ? ? ? ? ? ? ? ? long _arg1;

? ? ? ? ? ? ? ? ? ? _arg1 = data.readLong();

? ? ? ? ? ? ? ? ? ? boolean _arg2;

? ? ? ? ? ? ? ? ? ? _arg2 = (0 != data.readInt());

? ? ? ? ? ? ? ? ? ? float _arg3;

? ? ? ? ? ? ? ? ? ? _arg3 = data.readFloat();

? ? ? ? ? ? ? ? ? ? double _arg4;

? ? ? ? ? ? ? ? ? ? _arg4 = data.readDouble();

? ? ? ? ? ? ? ? ? ? java.lang.String _arg5;

? ? ? ? ? ? ? ? ? ? _arg5 = data.readString();

? ? ? ? ? ? ? ? ? ? this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);

? ? ? ? ? ? ? ? ? ? reply.writeNoException();

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? case TRANSACTION_getS: {

? ? ? ? ? ? ? ? ? ? data.enforceInterface(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _arg0;

? ? ? ? ? ? ? ? ? ? if ((0 != data.readInt())) {

? ? ? ? ? ? ? ? ? ? ? ? _arg0 = com.huanju.chajianhuatest.aidlmode.TestBean.CREATOR.createFromParcel(data);

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? _arg0 = null;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? java.lang.String _result = this.getS(_arg0);

? ? ? ? ? ? ? ? ? ? reply.writeNoException();

? ? ? ? ? ? ? ? ? ? reply.writeString(_result);

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? case TRANSACTION_getInfoBean: {

? ? ? ? ? ? ? ? ? ? data.enforceInterface(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _arg0;

? ? ? ? ? ? ? ? ? ? _arg0 = new com.huanju.chajianhuatest.aidlmode.TestBean();

? ? ? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _result = this.getInfoBean(_arg0);

? ? ? ? ? ? ? ? ? ? reply.writeNoException();

? ? ? ? ? ? ? ? ? ? if ((_result != null)) {

? ? ? ? ? ? ? ? ? ? ? ? reply.writeInt(1);

? ? ? ? ? ? ? ? ? ? ? ? _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? reply.writeInt(0);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if ((_arg0 != null)) {

? ? ? ? ? ? ? ? ? ? ? ? reply.writeInt(1);

? ? ? ? ? ? ? ? ? ? ? ? _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? reply.writeInt(0);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? return super.onTransact(code, data, reply, flags);

? ? ? ? }

? ? ? ? private static class Proxy implements com.huanju.chajianhuatest.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 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {

? ? ? ? ? ? ? ? android.os.Parcel _data = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? android.os.Parcel _reply = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? _data.writeInterfaceToken(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? _data.writeInt(anInt);

? ? ? ? ? ? ? ? ? ? _data.writeLong(aLong);

? ? ? ? ? ? ? ? ? ? _data.writeInt(((aBoolean) ? (1) : (0)));

? ? ? ? ? ? ? ? ? ? _data.writeFloat(aFloat);

? ? ? ? ? ? ? ? ? ? _data.writeDouble(aDouble);

? ? ? ? ? ? ? ? ? ? _data.writeString(aString);

? ? ? ? ? ? ? ? ? ? mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);

? ? ? ? ? ? ? ? ? ? _reply.readException();

? ? ? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? ? ? _reply.recycle();

? ? ? ? ? ? ? ? ? ? _data.recycle();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? @Override

? ? ? ? ? ? public java.lang.String getS(com.huanju.chajianhuatest.aidlmode.TestBean s) throws android.os.RemoteException {

? ? ? ? ? ? ? ? android.os.Parcel _data = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? android.os.Parcel _reply = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? java.lang.String _result;

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? _data.writeInterfaceToken(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? if ((s != null)) {

? ? ? ? ? ? ? ? ? ? ? ? _data.writeInt(1);

? ? ? ? ? ? ? ? ? ? ? ? s.writeToParcel(_data, 0);

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? _data.writeInt(0);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? mRemote.transact(Stub.TRANSACTION_getS, _data, _reply, 0);

? ? ? ? ? ? ? ? ? ? _reply.readException();

? ? ? ? ? ? ? ? ? ? _result = _reply.readString();

? ? ? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? ? ? _reply.recycle();

? ? ? ? ? ? ? ? ? ? _data.recycle();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return _result;

? ? ? ? ? ? }

? ? ? ? ? ? @Override

? ? ? ? ? ? public com.huanju.chajianhuatest.aidlmode.TestBean getInfoBean(com.huanju.chajianhuatest.aidlmode.TestBean b) throws android.os.RemoteException {

? ? ? ? ? ? ? ? android.os.Parcel _data = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? android.os.Parcel _reply = android.os.Parcel.obtain();

? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _result;

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? _data.writeInterfaceToken(DESCRIPTOR);

? ? ? ? ? ? ? ? ? ? mRemote.transact(Stub.TRANSACTION_getInfoBean, _data, _reply, 0);

? ? ? ? ? ? ? ? ? ? _reply.readException();

? ? ? ? ? ? ? ? ? ? if ((0 != _reply.readInt())) {

? ? ? ? ? ? ? ? ? ? ? ? _result = com.huanju.chajianhuatest.aidlmode.TestBean.CREATOR.createFromParcel(_reply);

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? _result = null;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if ((0 != _reply.readInt())) {

? ? ? ? ? ? ? ? ? ? ? ? b.readFromParcel(_reply);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? ? ? _reply.recycle();

? ? ? ? ? ? ? ? ? ? _data.recycle();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return _result;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

? ? ? ? static final int TRANSACTION_getS = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

? ? ? ? static final int TRANSACTION_getInfoBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);

? ? }

? ? public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

? ? public java.lang.String getS(com.huanju.chajianhuatest.aidlmode.TestBean s) throws android.os.RemoteException;

? ? public com.huanju.chajianhuatest.aidlmode.TestBean getInfoBean(com.huanju.chajianhuatest.aidlmode.TestBean b) throws android.os.RemoteException;

}

看著代碼好像很多,但是其實沒什么,我們分析一下結構

1.我們根據上面的代碼,創建的類繼承了IInterface接口

2.內部類Stub繼承Binder

3.看asInterface方法,判斷如果不是一個進程會返回代理類

4.每個方法都對應一個id,用于在跨進程訪問時確定訪問的是哪個方法,通過transact方法再調用服務端的onTransact方法

我們再看一下系統的類,就看我們最熟悉的ActivityManager,要知道ActivityManager其實是ActivityManagerService在我們進程中的一個代理包裝類,他內部全部使用 ActivityManagerNative.getDefault()去進程操作,那么我么直接看 ActivityManagerNative的部分代碼

1.asInterface

? ? /** {@hide} */

public abstract class ActivityManagerNative extends Binder implements IActivityManager

{

? ? /**

? ? * Cast a Binder object into an activity manager interface, generating

? ? * a proxy if needed.

? ? */

? ? static public IActivityManager asInterface(IBinder obj) {

? ? ? ? if (obj == null) {

? ? ? ? ? ? return null;

? ? ? ? }

? ? ? ? IActivityManager in =

? ? ? ? ? ? (IActivityManager)obj.queryLocalInterface(descriptor);

? ? ? ? if (in != null) {

? ? ? ? ? ? return in;

? ? ? ? }

? ? ? ? return new ActivityManagerProxy(obj);

? ? }


2.onTransact

@Override

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)

? ? ? ? throws RemoteException {

? ? switch (code) {

? ? case START_ACTIVITY_TRANSACTION:

? ? {

? ? ? ? data.enforceInterface(IActivityManager.descriptor);

? ? ? ? IBinder b = data.readStrongBinder();

? ? ? ? IApplicationThread app = ApplicationThreadNative.asInterface(b);

? ? ? ? String callingPackage = data.readString();

? ? ? ? Intent intent = Intent.CREATOR.createFromParcel(data);

? ? ? ? String resolvedType = data.readString();

? ? ? ? IBinder resultTo = data.readStrongBinder();

? ? ? ? String resultWho = data.readString();

? ? ? ? int requestCode = data.readInt();

? ? ? ? int startFlags = data.readInt();

? ? ? ? ProfilerInfo profilerInfo = data.readInt() != 0

? ? ? ? ? ? ? ? ? ProfilerInfo.CREATOR.createFromParcel(data) : null;

? ? ? ? Bundle options = data.readInt() != 0

? ? ? ? ? ? ? ? ? Bundle.CREATOR.createFromParcel(data) : null;

? ? ? ? int result = startActivity(app, callingPackage, intent, resolvedType,

? ? ? ? ? ? ? ? resultTo, resultWho, requestCode, startFlags, profilerInfo, options);

? ? ? ? reply.writeNoException();

? ? ? ? reply.writeInt(result);

? ? ? ? return true;

? ? }


代碼太多我們就看這兩個就好了,有沒有感覺很熟悉,和我們自己寫的aidl幾乎沒有區別,他雖然叫做ActivityManagerNative,其實他就是我們自己寫的aidl里的內部類Stub。

下面我們分析一下aidl的運行過程

我們直接看如果是遠程的話返回了代理對象,我們看代理對象的方法,這個方法就是aidl結果定義的方法,看他怎么實現的?

>

@Override

? ? ? ? public com.huanju.chajianhuatest.aidlmode.TestBean getInfoBean(com.huanju.chajianhuatest.aidlmode.TestBean b) throws android.os.RemoteException {

? ? ? ? ? ? android.os.Parcel _data = android.os.Parcel.obtain();

? ? ? ? ? ? android.os.Parcel _reply = android.os.Parcel.obtain();

? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _result;

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? _data.writeInterfaceToken(DESCRIPTOR);

? ? ? ? ? ? ? ? mRemote.transact(Stub.TRANSACTION_getInfoBean, _data, _reply, 0);

? ? ? ? ? ? ? ? _reply.readException();

? ? ? ? ? ? ? ? if ((0 != _reply.readInt())) {

? ? ? ? ? ? ? ? ? ? _result = com.huanju.chajianhuatest.aidlmode.TestBean.CREATOR.createFromParcel(_reply);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? _result = null;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if ((0 != _reply.readInt())) {

? ? ? ? ? ? ? ? ? ? b.readFromParcel(_reply);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } finally {

? ? ? ? ? ? ? ? _reply.recycle();

? ? ? ? ? ? ? ? _data.recycle();

? ? ? ? ? ? }

? ? ? ? ? ? return _result;

? ? ? ? }

? ? }


客戶端發起請求

1.首先創建輸出類型data

2.創建接受類型reply

3.創建需要的參數

4.將參數寫入data中

5.發起遠程調用,當前線程掛起調用mRemote.transact()方法,這個方法的實現在Binder中,他會調用服務端的onTransact方法,直到有返回結果

6.從_result中取回返回結果_result

服務端接到請求會走到onTransact方法,這個方法運行在服務端的Binder線程池中,我們再看看怎么實現的

>

? case TRANSACTION_getInfoBean: {

? ? ? ? ? ? ? ? data.enforceInterface(DESCRIPTOR);

? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _arg0;

? ? ? ? ? ? ? ? _arg0 = new com.huanju.chajianhuatest.aidlmode.TestBean();

? ? ? ? ? ? ? ? com.huanju.chajianhuatest.aidlmode.TestBean _result = this.getInfoBean(_arg0);

? ? ? ? ? ? ? ? reply.writeNoException();

? ? ? ? ? ? ? ? if ((_result != null)) {

? ? ? ? ? ? ? ? ? ? reply.writeInt(1);

? ? ? ? ? ? ? ? ? ? _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? reply.writeInt(0);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if ((_arg0 != null)) {

? ? ? ? ? ? ? ? ? ? reply.writeInt(1);

? ? ? ? ? ? ? ? ? ? _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? reply.writeInt(0);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }


1.通過id確定訪問哪個方法

2.然后從目標參數data取出需要的參數

3.然后調用請求的相應方法

4.將返回值寫入reply

5.返回true,這里需要說一下,如果返回false,代表客戶端訪問失敗,我們實際當中可根據這個特性來做遠程的校檢,畢竟我們的遠程方法并是不想讓任何人都可以訪問的。?

,通過id確定訪問哪個方法,然后從目標參數data取出需要的參數,然后調用相應方法,將返回值寫入reply,

好了,到這里Binder的通信過程就完了,其實我們看到了只要我們理解了我們自己寫的aidl的流程及原理,那么系統層的通信也是這樣的。下一篇我們繼續說代理模式及反射。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 雖然跨進程通信的內在機制十分復雜。但是Android為我們提供了一套非常簡單的機制,讓我們僅需要很少的步驟就能完成...
    passerbywhu閱讀 665評論 0 0
  • 上篇文章介紹了IPC機制的基本概念以及簡單使用,文章鏈接:Android 關于IPC機制的理解(一) 這篇文章主要...
    老實任閱讀 745評論 0 2
  • AIDL(Android Interface Definition Language),即Android接口定義語...
    小編閱讀 1,494評論 4 7
  • 早上練了半個多小時的口語跟讀聯系。以前總想著要快點結束,總是草草了事。但內心總是有個聲音再說,這種敷衍了事的事了行...
    小魚記事錄閱讀 306評論 0 0
  • 散上幾片 往事在杯中沉淀 青綠便誘惑著我們 細嚼每一滴清香 也不是茶葉的滋味 騰騰熱氣飄散的記憶 只在唇邊留下絲絲甜蜜
    茜茜核桃媽閱讀 234評論 0 0