1. 程式人生 > >Android IPC 程序間通訊

Android IPC 程序間通訊

IPC (程序間通訊)

部落格導讀:

這裡寫圖片描述

什麼是IPC

IPC(Inter-Process Communication) 程序間通訊,是指兩個不同程序之間資料交換的過程。

在明確其之前,需要先搞懂幾個概念:

  • 執行緒:CPU可排程的最小單位,是程式執行流的最小單元;執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。
  • 程序: 一個執行單元,在PC 和移動裝置上一般指一個程式或者應用,一個程序可以包含多個執行緒。每一個程序都有它自己的地址空間,一般情況下,包括文字區域(text region)、資料區域(data region)和堆疊(stack region)。

在Android程式中,一般情況下一個程式就是一個程序(在無特別的程式碼實現下),UI執行緒即主執行緒。如果有耗時操作,則會導致主執行緒堵死。而在Android中主執行緒負責UI,和使用者互動,如果堵塞UI執行緒會影響使用者體驗。所以Android要求要將耗時操作放在子執行緒中執行。

IPC 使用場景

  • 程式因為自身原因,需要採用多程序模式來實現。
    • 有些模組由於特殊原因需要執行執行在單獨的程序中。
    • 為了加大一個應用可使用的記憶體所以需要通過多程序來獲取記憶體空間。
  • 當前應用需要向其他應用獲取資料。由於是兩個應用,即兩個程序。

在Android 中,每一個應用可使用的記憶體大小有限制,早起的一些版本在16M左右,不同的裝置有不同的大小。可以通過多程序獲取多份記憶體空間。

Android多程序

如何開啟多程序

Android中開啟多程序只有一種方法,便是給四大元件指定android
:process屬性,除此之外沒有其他方法。

請注意,不能指定某一個執行緒或者實體類指定其所執行的程序。

通過jni呼叫底層去開啟多程序也是一種方法,但屬於特殊情況,不進行考慮。

首先編寫三個Activity,並在AndroidManifest.xml中註冊:

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
>
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.example.ipc.SecondActivity" android:process=":remote" /> <activity android:name="com.example.ipc.ThirdActivity" android:process=".remote" />

對MainActivity不進行指定,則預設為當前程序。

對SecondActivity指定屬性android:process=”:remote”。

對ThirdActivity指定屬性android:process=”.remote”。

注意SencodActivity和ThirdActivity的程序引數不同。

把三個頁面都開啟,通過DDMS可以看到三個程序的開啟:

這裡寫圖片描述

那麼SecondActivity和ThirdActivity ,程序名不同有什麼區別嗎;

如果程序名以:開始,表示是要在當前的程序名前附加上當前的包名,表示該程序是本應用的私有程序,其他應用不可以和其跑在同一個程序。
如果程序名不以:開始,表示不需附加包名資訊,是一個完全的命名。同時該程序是全域性程序,其他應用可以通過ShareUID和其跑在同一個程序中。

開啟多程序存在的問題

通過如上方式,很簡單的變開啟了多程序,但是,如果僅僅這樣的話,會有大問題。 看下面一個例子。

新增一個公有的類,新增靜態欄位:

public class PublicContant {

    public static int m = 1;

}

在MainActivity中Log一下並修改欄位:

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


        Log.i("info", PublicContant.m+"");

        PublicContant.m++;
    }

在SecondActivity中列印log:

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

        Log.i("info", PublicContant.m+"");      
    }

根據上面的邏輯,Log資訊應該是1,和2 。但是呢,不是這樣的。

這裡寫圖片描述

原因分析:

Android 為每一個程序都分配一個獨立的虛擬機器,不同的虛擬機器在記憶體分配上有不同的地址空間,這就導致在不同虛擬機器中訪問同一個類物件會產生多個副本。

對於當前來說,程序com.example.ipc和com.example.ipc:remote都存在一個PublicContant類,並且這兩個類是相互不干擾的,一個程序中修改了該值的物件,對其他程序中的該值不會造成任何影響。

執行在同一個程序中的元件是屬於同一個虛擬機器和同一個Application的。同理,執行在不同程序中的元件是屬於兩個不同的虛擬機器和Application的。

根據如上所述,多程序所造成的問題分為如下幾個方面:

  1. 靜態成員和單例模式完全失效;
    如上分析,建立了不同的記憶體,多個物件,當然單例什麼的都無效了。
  2. 執行緒同步機制完全失效 ;
    不是一塊記憶體區域,執行緒鎖當然無效了。
  3. SharedPreference的可靠性下降 ;
    sharedPreference的底層實現是通過讀寫XML檔案,兩個程序去讀寫,併發顯然是可能出現問題的。
  4. Application會多次建立。

序列化和反序列化

在瞭解多程序通訊之前,我們需要了解兩個基礎的概念,序列化和反序列化。

  • 序列化:將物件轉化為可儲存的位元組序列(注意是物件);
  • 反序列:將位元組序列恢復為物件的過程。

序列化和反序列的用途:

  • 以某種儲存形式使自定義物件序列化;
  • 將物件從一個地方傳遞到另一個地方;
  • 通過序列化在程序間傳遞物件。

在Android中實現序列化的方式有兩種,Serializable和Parcelable。

Serializable

Serializable是Java提供的一個序列化介面,他是一個空介面,是類實現該介面即可實現序列化。

/**
 * Serializable 序列化物件
 */
public class Book implements Serializable {
    /**
     *  序列化和反序列的關鍵
     */
    private static final long serialVersionUID = 1L;


    public int bookId;

    public String bookName;

}

在實現Serializable時候,編譯器會提示,讓我們新增serialVersionUID欄位,該欄位是一個關鍵的欄位,後面會說。

相應的實現好了,那麼如何寫入和讀取呢?

寫入:

public void writeSerializable() {
        try {
            // 構造物件
            Book book = new Book();
            // 構造序列化輸出位元組流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xxx.txt"));
            // 序列化物件
            oos.writeObject(book);
            // 關閉流
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

讀取:

 public void readSerializable() {
        try {
            // 建立序列化讀取位元組流
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "xxx.txt"));
            // 反序列化(讀取)物件
            Book book = (Book) ois.readObject();
            // 關閉流
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在序列化時,如果我們序列化物件之後,改變了我們的類結構(新增或改變欄位),甚至是修改了欄位的型別,修改了類名,那麼我們能反序列化成功嗎。

那麼關鍵就在於serialVersionUID欄位。

如果我們不指定的話。在序列化時,會計算當前類結構的hash值並將該值賦給serialVersionUID,當反序列時,會比對該值是否相同,如果不相同,則無法序列化成功。

我們也可以手動指定,手動指定的好處是在類結構發生變化時,能夠最大程度的反序列,當然前提是隻是刪除或添加了欄位,如果是變數型別發生了變化,則依然無法反序列成功。

serialVersionUID 的工作機制:序列化時系統會把當前類的serialVersionUID寫入序列化檔案中,當反序列化時候系統會去檢測檔案中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致說明序列化類的版本和當前類的版本是相同的,這個時候可以成功反序列化,否則就說明當前類和序列化的類相比發生了某些變化。所以,我們最好指定serialVersionUID,避免他自定生成。

Parcelable

Parcelable是Android中特有的一種序列化方式,在intent傳值時,通常使用該方式。

該方式實現序列化,依然實現Parcelable,然後實現一些該介面的方法。

public class Book implements Parcelable {

    public int bookId;

    public String bookName;

    @Override
    public int describeContents() {
        // 返回當前物件的內容描述。幾乎所有情況下都是返回0
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 將當前物件寫入到序列化結構中       
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }


    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {

        @Override
        public Book createFromParcel(Parcel source) {
            //從序列化後的物件中建立原始的值
            Book book = new Book();
            book.bookId = source.readInt();
            book.bookName = source.readString();

            return book;
        }

        @Override
        public Book[] newArray(int size) {
            //建立指定長度的原始物件陣列
            return new Book[size];
        }
    };


}

Parcelable實現兩個方法,建立一個欄位:

  • 實現describeContents():返回當前物件的內容描述。幾乎所有情況下都是返回0。
  • 實現public void writeToParcel(Parcel dest, int flags):// 將當前物件寫入到序列化結構中
  • 構造Parcelable.Creator欄位,該物件需要實現兩個方法:
    • public Book createFromParcel(Parcel source):從序列化後的物件中建立原始的值。
    • public Book[] newArray(int size):建立指定長度的原始物件陣列。

Serializable和Parcelable的比較

  • Serializable是Java中的序列化介面,其使用起來簡單但是開銷較大,序列化和反序列化需要大量的I/O操作。
  • Parcelable是Android中的序列化方式,更適用於Android的平臺上,他的缺點是使用起來稍微麻煩,但是效率很高。
  • Parcelable適合程序間的通訊,執行期。Serializable適合檔案儲存即網路傳輸。

Android 程序間通訊的方式

使用Bundle 傳輸資料

Android中的四大元件中,其中有三大元件(Activity,Service,Receiver)都支援Intent中傳遞Bundle資料,如果看其原始碼,會發現其也是實現了Parcelable介面,所以其能夠在不同程序中傳輸。

當然在傳輸的過程中,其所傳輸的資料必須支援序列化。比如基本資料型別,字串,Parcelable的實現類,Serializable的實現類。由於該方法非常常用,不在多說。

檔案共享

檔案共享: 將物件序列化之後儲存到檔案中,在通過反序列,將物件從檔案中讀取。

在MainActvity中寫寫入物件

 /**
     * 寫入序列化物件
     */
    public void wirte() {
        Book book = new Book();
        book.bookId = 1;
        book.bookName = "si";
        try {

            // 構造序列化輸出位元組流
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream(PATH));
            // 序列化物件
            oos.writeObject(book);
            // 關閉流
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(book);

    }

在SecondActivity中,讀取檔案(反序列化)

public void read() {
        Book book = null;
        try {
            // 建立序列化讀取位元組流
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    MainActivity.PATH));
            // 反序列化(讀取)物件
            book = (Book) ois.readObject();
            // 關閉流
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(book);
    }

輸出結果如下:

06-28 09:20:47.916: com.example.ipc(程序名) I/System.out(12399): Book [bookId=1, bookName=si]
06-28 09:20:53.376: com.example.ipc:remote(程序名) I/System.out(12866): Book [bookId=1, bookName=si]

分屬不同的程序成功的獲取到了共享的資料。

通過共享檔案這種方式來共享資料對檔案的格式是沒有具體的要求的。比如可以是檔案,也可以是Xml、JSON 等。只要讀寫雙方約定一定的格式即可。

同文件共享方式也存在著很大的侷限性。即併發讀/ 寫的問題。讀/寫會造成資料不是最新。讀寫很明顯會出現錯誤。

檔案共享適合在對資料同步要求不高的程序之間進行通訊。並且要妥善處理併發讀寫的問題。

SharedPreference 底層檔案的方式。不適合在多程序中共享資料。

Messenger

Messenger 可以翻譯為信使,通過該物件,可以在不同的程序中傳遞Message物件。注意,兩個單詞不同。

下面就通過服務端(Service)和客戶端(Activity)的方式進行演示。

客戶端向服務端傳送訊息,可分為以下幾步。

服務端

  • 建立Service
  • 構造Handler物件,實現handlerMessage方法。
  • 通過Handler物件構造Messenger信使物件。
  • 通過Service的onBind()返回信使中的Binder物件。

客戶端

  • 建立Actvity
  • 繫結服務
  • 建立ServiceConnection,監聽繫結服務的回撥。
  • 通過onServiceConnected()方法的引數,構造客戶端Messenger物件
  • 通過Messenger向服務端傳送訊息。

實現服務端

public class MessengerService extends Service {

    /**
     * 構建handler 物件
     */
    public static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            // 接受客戶端傳送的訊息

            String msgClient = msg.getData().getString("msg");

            Log.i("messenger","接收到客戶端的訊息--"+msgClient);

        };
    };


    // 通過handler 構建Mesenger 物件
    private final Messenger messenger = new Messenger(handler);

    @Override
    public IBinder onBind(Intent intent) {
        // 返回binder 物件
        return messenger.getBinder();
    }
}

注意:MessengerService需要在AndroidManifest.xml中註冊。

實現客戶端

public class MessengerActivity extends AppCompatActivity {

    /**
     * Messenger 物件
     */
    private Messenger mService;

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // IBinder 物件
            // 通過服務端返回的Binder 物件 構造Messenger 
            mService = new Messenger(service);

            Log.i("messenger", "客戶端以獲取服務端Messenger物件");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

    };

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

        // 啟動服務
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    /**
     *  佈局檔案中添加了一個按鈕,點選該按鈕的處理方法
     * @param view
     */
    public void send(View view) {
        try {
            // 向服務端傳送訊息
            Message message = Message.obtain();

            Bundle data = new Bundle();

            data.putString("msg", "lalala");

            message.setData(data);
            // 傳送訊息
            mService.send(message);

            Log.i("messenger","向服務端傳送了訊息");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

輸出結果如下:
這裡寫圖片描述

註釋很清楚,不在多說,按照流程實現即可。 其中有一點需要注意:

我們是通過Message作為媒介去攜帶資料的。但是,Message的obj 並沒有實現序列化(實現Serializable或Parcelable),也就是其不能儲存資料。必須使用message.setData()方法去傳入一個Bundle物件,Bundle中儲存需要傳入的資料。

傳遞時使用的是Messenger.send(Message)方法。

服務端向客戶端傳送了訊息,那麼服務端向客戶端傳送訊息也類似:

關鍵點: 客戶端向服務端傳送訊息是,通過msg.replyTo將客戶端Messenger物件傳給服務端。

客戶端程式碼進行修改:

  • 建立客戶端Handler和Messenger物件。
  • 修改send()方法。
/**
     * 構建handler 物件
     */
    public static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            // 接受服務端傳送的訊息

            String msgService = msg.getData().getString("msg");

            Log.i("messenger","接收到服務端的訊息--"+msgService);

        };
    };


    // 通過handler 構建Mesenger 物件
    private final Messenger messengerClient = new Messenger(handler);


    /**
     *  佈局檔案中添加了一個按鈕,點選該按鈕的處理方法
     * @param view
     */
    public void send(View view) {
        try {
            // 向服務端傳送訊息
            Message message = Message.obtain();

            Bundle data = new Bundle();

            data.putString("msg", "lalala");

            message.setData(data);

            // ----- 傳入Messenger 物件
            message.replyTo = messengerClient;

            // 傳送訊息
            mService.send(message);

            Log.i("messenger","向服務端傳送了訊息");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服務端程式碼修改:

/**
     * 構建handler 物件
     */
    public static Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            // 接受客戶端傳送的訊息

            String msgClient = msg.getData().getString("msg");

            Log.i("messenger", "接收到客戶端的訊息--" + msgClient);

            // 獲取客戶端Messenger 物件

            Messenger messengetClient = msg.replyTo;

            // 向客戶端傳送訊息
            Message message = Message.obtain();

            Bundle data = new Bundle();

            data.putString("msg", "ccccc");

            message.setData(data);

            try {
                // 傳送訊息
                messengetClient.send(message);
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        };
    };

結果不再演示了。

AIDL

AIDL是一種介面定義語言,用於約束兩個程序間的通訊規則,供編譯器生成程式碼,實現Android裝置上的兩個程序間通訊(IPC)。

程序之間的通訊資訊,首先會被轉換成AIDL協議訊息,然後傳送給對方,對方收到AIDL協議訊息後再轉換成相應的物件。

AIDL的關鍵便是Binder,關於Binder,後面的部落格會分析。在這裡之將如何使用它。

因為需要服務端和客戶端共用aidl檔案,所以最好單獨建一個包,適合拷貝到客戶端。

服務端:

  • 新增如下包名:com.example.ipc.aidl
  • 建立BookAidl.java,該物件需要作為傳輸。所以需要實現Parcelable。
public class BookAidl implements Parcelable {

    public int bookId;

    public String bookName;

    public BookAidl() {
        super();

    }

    public BookAidl(int bookId, String bookName) {
        super();
        this.bookId = bookId;
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<BookAidl> CREATOR = new Creator<BookAidl>() {

        @Override
        public BookAidl[] newArray(int size) {
            return new BookAidl[size];
        }

        @Override
        public BookAidl createFromParcel(Parcel source) {

            BookAidl book = new BookAidl();
            book.bookId = source.readInt();
            book.bookName = source.readString();
            return book;
        }
    };

    @Override
    public String toString() {
        return "BookAidl [bookId=" + bookId + ", bookName=" + bookName + "]";
    }

}
  • 建立.aidl檔案。因為需要用到BookAidl物件,所以需要先宣告。

建立BookAidl.aidl檔案,並手動新增。

package com.example.ipc.aidl;

Parcelable BookAidl;

建立IBookManager.aidl檔案,介面檔案,面向客戶端呼叫:

package com.example.ipc.aidl;

import com.example.ipc.aidl.BookAidl;

interface IBookManager{
    List<BookAidl> getBookList();
    void addBook(in BookAidl book);
}

寫完之後clean一下工程,之後會在gen目錄下生成對應的java檔案。此java中的具體含義後面會解釋,在此不做多述。

  • 繼續編寫服務端,建立Service類。
public class BookService extends Service {

    /**
     * 支援執行緒同步,因為其存在多個客戶端同時連線的情況
     */
    private CopyOnWriteArrayList<BookAidl> list = new CopyOnWriteArrayList<>();


    /**
     * 構造 aidl中宣告的介面的Stub物件,並實現所宣告的方法
     */
    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<BookAidl> getBookList() throws RemoteException {
            return list;
        }

        @Override
        public void addBook(BookAidl book) throws RemoteException {
            list.add(book);
            Log.i("aidl", "服務端添加了一本書"+book.toString());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //加點書
        list.add(new BookAidl(1, "java"));
        list.add(new BookAidl(2, "android"));

    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回給客戶端的Binder物件
        return mBinder;
    }
}

在Service中,主要乾了兩件事情:

  • 實現aidl檔案中的介面的Stub物件。並實現方法。
  • 將Binder物件通過onBinder返回給客戶端。

為了省事,在這裡不在另起一個工程了,直接將Service在另一個程序中執行。

<service
        android:name="com.example.ipc.BookService"
        android:process=":remote" />

開始編寫客戶端

因為在同一個工程中,不需要拷貝aidl包中的檔案。如果不在同一個工程,需要拷貝。

public class BookActivity extends AppCompatActivity{

    /**
     * 介面物件
     */
    private IBookManager mService;

    /**
     * 繫結服務的回撥
     */
    private ServiceConnection conn = new ServiceConnection(){

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            // 獲取到書籍管理的物件
            mService = IBookManager.Stub.asInterface(service);

            Log.i("aidl", "連線到服務端,獲取IBookManager的物件");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_book);

        // 啟動服務
        Intent intent = new Intent(this,BookService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);

    }
    /**
     * 獲取服務端書籍列表
     * @param view
     */
    public void getBookList(View view){

        try {
            Log.i("aidl","客戶端查詢書籍"+mService.getBookList().toString());
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 新增書籍
     */
    public void add(View view){

        try {
            // 呼叫服務端新增書籍
            mService.addBook(new BookAidl(3,"iOS"));
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

客戶端的程式碼和之前的Messenger很類似:

  • 繫結服務,監聽回撥。
  • 將回調中的IBinder service通過IBookManager.Stub.asInterface()轉化為藉口物件。
  • 呼叫藉口物件的方法。

效果

這裡寫圖片描述

總結來說可分為如下幾步

服務端:

  • 服務端建立.aidl檔案和宣告介面
  • 建立類,繼承Service,並實現onBind方法
  • 在Service類中定義aidl中宣告介面的Stub物件,並實現aidl介面中宣告的方法
  • 在onBind方法中返回Stub物件
  • 在AndroidManifest.xml中註冊Service並宣告其Action

客戶端

  • 使用服務端提供的aidl檔案
  • 在Activity定義aidl介面物件
  • 定義ServiceConnection物件,監聽繫結服務的回撥
  • 回撥中通過方法獲取藉口物件

ContentProvider

作為android 四大元件之一,雖然用的地方不是太多。但是其確實是多程序通訊的一種方式。例如,獲取通訊錄資訊,這明顯跨應用了,肯定是多程序通訊啊。

其底層實現和Messenger一樣,都是通過Binder,後面會專門分析Binder物件。

ContentProvider很多介紹,在這不在多提。

Socket

Socket也稱為“套接字”,是網路通訊中的概念,它分為流式套接字和使用者資料報套接字,分別對應於網路傳輸中的傳輸控制層的TCP和UDP。

該方面使用的是JAVA 方面的知識。該舉例只是說明一個思路。不做細緻的實現。

服務端

public class SocketService extends Service {

    /**
     * 連線的狀態
     */
    private boolean isConnState = true;


    @Override
    public void onCreate() {
        super.onCreate();

        // 啟動TCP 服務
        new Thread(new TCPServer()).start();
    }

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


    @Override
    public void onDestroy() {
        // 結束TCP 服務
        isConnState = false;
        super.onDestroy();
    }

    /**
     * 服務端TCP 服務,相當於伺服器,接受Socket 連線
     * @author MH
     *
     */
    class TCPServer implements Runnable{

        @Override
        public void run() {

            try {
                // 監聽本地的12345 埠
                ServerSocket ss = new ServerSocket(12345);

                while(isConnState){

                    // 獲取客戶端的Socket 物件
                    Socket socket = ss.accept();

                    // 獲取輸入流  --- 
                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    // 通過輸入流讀取客戶端的訊息
                    //String line = br.readLine();
                    // 輸出流
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    // 通過輸出流向客戶端傳送訊息
                    //bw.write("....");

                    // 關閉連線
                    socket.close();
                }


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

}

服務啟動時,在onCreate方法中啟動了TCPServer,該執行緒時刻接受客戶端的請求。

客戶端

    public void conn(){
        try {
            // 指定ip和埠
            Socket s = new Socket("localhost", 12345);

            // ----- 和服務端類似
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //String line = br.readLine();
            // 輸出流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            //bw.write("....");
            // 關閉連線
            s.close();
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

關於Socket,在此只是一個簡單的示範。

Android 程序間通訊不同方式的比較

  • Bundle:四大元件間的程序間通訊方式,簡單易用,但傳輸的資料型別受限。
  • 檔案共享: 不適合高併發場景,並且無法做到程序間的及時通訊。
  • Messenger: 資料通過Message傳輸,只能傳輸Bundle支援的型別
  • ContentProvider:android 系統提供的。簡單易用。但使用受限,只能根據特定規則訪問資料。
  • AIDL:功能強大,支援實時通訊,但使用稍微複雜。
  • Socket:網路資料交換的常用方式。不推薦使用。

Binder 的細緻分析

在實現多程序通訊時,其中Messenger,ContentProvider,AIDL的底層實現都是Binder,很有必要對其進行繼續分析。