Android跨程式通訊

Jimmie發表於2019-04-02
  • Bundle
  • 檔案共享
  • ContentProvider
  • Socket
  • Messenger
  • AIDL

Bundle

先看下撥打電話的程式碼

Uri uri = Uri.parse("smsto:10086");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
Bundle bundle = new Bundle();
bundle.putString("sms_body", "SMS Text");
intent.putExtras(bundle);
//  intent.putExtra("sms_body", "SMS Text");
startActivity(intent);
複製程式碼

可見,通過將資料儲存在Intent的Bundle中,可以在不同程式/APP間傳遞資料。

檔案共享

通過將資料寫入到一個檔案中,不同程式可以對這個檔案進行讀取訪問,來達到跨程式通訊目的。 不過,多程式同時訪問一個檔案,存在併發和IO效能低的問題。

ContentProvider

Android四大元件之一,提供訪問資料的統一格式。資料來源可以是檔案、資料庫。 可以對外提供訪問的介面,實現跨程式/APP訪問。

private void readContacts() {
    //用於查詢電話號碼的URI
    Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
    // 查詢的欄位
    String[] projection = {
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,//通訊錄姓名
            ContactsContract.CommonDataKinds.Phone.DATA1, "sort_key",//通訊錄手機號
            };
    Cursor cursor = getContentResolver().query(phoneUri, projection, null, null, null);
    while ((cursor.moveToNext())) {
        String name = cursor.getString(0);
        String phone = cursor.getString(1);
    }
}
複製程式碼

Socket

Socket主要分為兩種

  • StreamSocket:基於TCP協議的封裝,以流的方式提供資料互動服務,提供了穩定的雙向通訊,通過“三次握手”建立連線,傳輸資料具有較高的穩定性。 Java中客戶端使用Socket類,伺服器端使用ServerSocket類。
  • DatagramSocket:基於UDP協議的封裝,以資料包文的方式提供資料互動服務,提供了不穩定的單向通訊,具有更好的執行效率,由於基於無連線的方式,傳輸資料不穩定,不保證資料的完整性。 Java中使用DatagramPacket類,表示資料包包;DatagramSocket類,進行端到端通訊。

Messager

底層也是通過封裝AIDL來實現的,所以使用的方式和AIDL基本類似。

  1. 在服務端程式Service中建立Messenger物件,用來接收客戶端發來的Message資料,和獲取客戶端Messenger物件,並給客戶端發Message資料。
  2. 建立客戶端Messenger物件,用來接收服務端資料。
  3. 客戶端繫結服務端服務,並獲取服務端Messenger物件,用來給服務端發Message資料。
  4. 通過服務端Messenger發訊息,將客戶端Messenger物件,新增到Message.replyTo。
public class MsgerService extends Service {
    private Messenger mServerMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            // 接收客戶端發過來的訊息
            switch (msg.what) {
                case 1000:

                    Toast.makeText(getBaseContext(), "" + msg.arg1, Toast.LENGTH_SHORT).show();

                    Message cMsg = Message.obtain();
                    cMsg.what = msg.what;
                    Bundle bundle = new Bundle();
                    bundle.putString("name", "Jim");
                    cMsg.obj = bundle;

                    // 獲取客戶端的Messenger物件,需要客戶端在傳送訊息時設定
                    Messenger cMsger = msg.replyTo;
                    try {
                        // 給客戶端傳送訊息
                        cMsger.send(cMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    });
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mServerMessenger.getBinder();
    }
}
複製程式碼
public class ClientActivity extends Activity {
    private TextView mNameTxt;

    /**
     * 客戶端Messenger物件,用來接收服務端資料
     */
    private Messenger mClientMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1000:
                    // 接收服務端資料
                    Bundle bundle = (Bundle) msg.obj;
                    mNameTxt.setText(bundle.getString("name"));
                    break;
            }
        }
    });

    /**
     * 服務端Messenger物件,建立連線時獲取,用來給服務端發訊息
     */
    private Messenger mServerMessenger;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 獲取服務端Messenger物件
            mServerMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mServerMessenger = null;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_common);

        mNameTxt = (TextView) findViewById(R.id.name);

        // 繫結遠端服務
        Intent intent = new Intent(this, MsgerService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);

        findViewById(R.id.bind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                int number = (int) (Math.random() * 100);
                Message msg = Message.obtain();
                msg.what = 1000;
                msg.arg1 = number;
                msg.replyTo = mClientMessenger;

                // 給服務端傳送訊息
                if (mServerMessenger != null) {
                    try {
                        mServerMessenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}
複製程式碼

AIDL

AIDL(Android Interface Definition Language)指的就是介面定義語言,通過它可以讓客戶端與服務端在程式間使用共同認可的程式設計介面來進行通訊 AIDL使用的步驟相對較多,主要總結為三個基本步驟:

  • 建立AIDL介面

  • 根據AIDL建立遠端Service服務

  • 繫結遠端Service服務

(1)建立AIDL介面

定義aidl介面檔案

在Android Studio中已經整合好了這個檔案的建立方式,直接右擊工程,點選New -> AIDL -> AIDL File,然後輸入介面的名稱就好,將會在src/main目錄下建立一個與java目錄平級,且裡面的包名與java目錄裡的包名一致,字尾為.aidl的檔案

// Declare any non-default types here with import statements

interface IMyAidlTest {
    /**
     * 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);
}
複製程式碼

上面這個檔案是Android Studio自動建立的模版檔案,裡面的basicTypes方法不需要使用到可以刪掉。 AIDL對資料型別的支援包括Java中的所有基本資料型別,還有String、CharSequence、List、Map。

自定義AIDL的資料型別

在AIDL提供的預設資料型別無法滿足需求的情況下,就需要自定義資料型別了 比如我們有個Product類,需要用來傳遞資料,那麼這個類必須要實現Parcelable介面,並在AIDL中新建一個相同類名的aidl檔案進行宣告,並且這個aidl檔案所在的路徑必須要和java檔案裡的實體類路徑保持一致,如以下檔案Product.aidl

package demo.csdn.zhuwentao.bean;

parcelable Product;
複製程式碼

然後在IMyAidlTest.aidl中使用import匯入進來,除了AIDL預設支援的資料型別外,其它自定義的型別都需要通過此方法匯入進來,包名路徑需要精確到類名。

interface IMyAidlTest {
    void addProduct(in Product person);
    List<Product> getProductList();
}
複製程式碼

這裡的方法只作為介面宣告的作用,以上定義的介面最終會在Service服務裡實現具體的操作邏輯。

根據aidl檔案生成java介面檔案

這個步驟Android Studio已經幫我們整合好了,只需要點選 Build -> Make Project,或者點選AS上的那個小錘子圖示就可以,構建完後將會自動根據我們定義的IMyAidlTest.aidl檔案生成IMyAidlTest.java介面類,可以在build/generated/source/aidl/debug/路徑下找到這個類。

(2)根據AIDL建立遠端Service服務

上一步中建立好的IMyAidlTest.java介面檔案,需要使用Service來進行繫結,這裡就需要我們新建一個Service服務。

/**
 * 根據AIDL建立遠端Service服務端
 */
public class MyAidlService extends Service {

    private List<Product> mProducts;
    public MyAidlService() {
    }

    private IBinder mIBinder = new IMyAidlTest.Stub() {

        @Override
        public void addProduct(Product product) throws RemoteException {
            mProducts.add(product);
        }

        @Override
        public List<Product> getProductList() throws RemoteException {
            return mProducts;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mProducts = new ArrayList<>();
        return mIBinder;
    }
}
複製程式碼

mIBinder物件例項化了IMyAidlTest.Stub,並在回撥介面中實現了最終的處理邏輯 當與客戶端繫結時,會觸發onBind()方法,並返回一個Binder物件給客戶端使用,客戶端就可以通過這個類呼叫服務裡實現好的介面方法 記得要在配置檔案中加入宣告,並使用android:process屬性指定其執行在新的程式中。

<service
    android:name=".MyAidlService"
    android:process=":process"/>
複製程式碼

配置好以上步驟後,跨程式通訊的服務端就配置好了

(3)繫結遠端Service服務

跨程式通訊服務端實現好了後,就可以在客戶端中開始呼叫它了,首先在Activity中先建立好服務連線物件

private IMyAidlTest mAidlTest;

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mAidlTest = IMyAidlTest.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mAidlTest = null;
    }
};
複製程式碼

再通過Intent的bindService來繫結Service服務,建立起連線

Intent intent = new Intent(getApplicationContext(), MyAidlService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
複製程式碼

啟動成功後,onServiceConnected方法將會在建立連線時被回撥,回撥時將生成一個介面實現mAidlTest物件,這個物件就是我們進行跨程式操作呼叫物件 接下來就是通過這個mAidlTest物件來操作AIDL方法就好了。

private void addProduct(String name, int price) {
    Product pro = new Product();
    pro.mName = name;
    pro.mPrice = price;

    try {
        // 通過mAidlTest呼叫AIDL方法
        mAidlTest.addProduct(pro);
        List<Product> proLists = mAidlTest.getProductList();

        mAIDLTv.setText(proLists.toString());
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}
複製程式碼

以上就是AIDL使用的基本步驟了。

參考:www.jianshu.com/p/b17f1276e…

相關文章