《Android藝術開發探索》學習筆記之IPC

鋸齒流沙發表於2017-12-26

IPC:Inter-Process-Communication,即程式間通訊或者跨程式通訊,是指兩個程式之間進行資料交換的過程。任何一個作業系統都有相應的IPC,Android是一種基於Linux核心的移動作業系統,它的程式間通訊方式並不能完全繼承自Linux,相反,它有自己的程式間通訊方式,在Android中最有特色的程式間通訊方式就是Binder了。

執行緒:CPU排程的最小單元,同時執行緒是一種有限的系統資源。

程式:指一個執行單元,在PC和移動裝置上指一個程式或者一個應用。

Android中的多程式模式

開啟多程式:

1、通過給四大元件(Activity、Service、Receiver、ContentProder)在AndroidMenifest中指定android:process屬性,開啟多程式模式。

2、通過JNI在native層fork一個新的程式(非常規)。

<service
            android:name="com.lwj.mytestpro.aidl.BookManagerService"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>

                <action android:name="com.lwj.ipc.aidl.BookManagerService"/>
            </intent-filter>
</service>
複製程式碼

IPC.png

android:process=":remote"系統為此建立一個單獨的名為包名:remote的程式。

多程式造成的問題

一般來說,使用多程式會造成如下幾個方面的問題: (1)靜態成員和單例模式完全失效。 (2)執行緒同步機制完全失效。 (3)SharedPreferences的可靠性下降。 (4)Application會多次建立。

關於(1)(2)問題本質上相似:Android為每個應用分配一個獨立的虛擬機器,或者說為每個程式都分配一個獨立的虛擬機器,不同的虛擬機器在記憶體分配上有不同的地址空間,這就導致不同的虛擬機器中訪問同一個類的物件會產生多分副本。

關於問題(3):因為SharedPreferences不支援兩個程式同時執行寫操作,否則會導致一定的機率的資料丟失。這是一位SharedPreferences底層是通過讀/寫XML檔案來實現的,併發寫顯然是可能出問題的。

關於問題(4):當一個元件跑在一個新的程式中的時候,由於系統在建立新的程式時分配獨立的虛擬機器,所以這個過程其實就是一個啟動應用的過程,因此相當於系統又把這個應用重新啟動了一遍,既然重啟了,那麼自然會建立新的Application。可以理解為:執行在同一個程式的元件是屬於同一個虛擬機器和同一個Application的,同理,執行在不同程式中的元件是屬於兩個不同的虛擬機器和Application的。

IPC的基礎概念介紹

Serializable介面

Serializable是Java所提供的一個序列化介面,也是一個空介面,為物件提供標準的序列化和反序列化操作。使用Serializable來實現序列化相當簡單,只需要在類的宣告指定一個類似下面的標識即可自動實現預設的序列化過程。

public class User implements Serializable {

    private static final long serialVersionUID = 1230212L;

    public int userId;
    public String userName;
    public boolean isMale;

}
複製程式碼

通過Serializable方式實現物件的序列化,實現起來很簡單,幾乎所有的工作都被系統自動完成,如何進行物件的序列化和反序列化也非常簡單,只需要採用ObjectOutputStream和ObjectInputStream即可輕鬆完成。

IPC.png

上述程式碼演示了採用Serializable方式序列化物件的典型過程。只需要把實現的Serializable介面的物件寫到檔案中就可以快速恢復了,恢復後的物件newUser和user的內容完全一樣,但是兩者並不是同一個物件。

serialVersionUID意義:指定serialVersionUID的值,編譯器自動生成hash值,這樣序列化和反序列化的serialVersionUID值相同,可以正常進行反序列化。如果不指定,反序列化時當前類有所改變(增減成員變數)那麼系統會重現計算當前類的hash值並把它複製給serialVersionUID,這時候序列化和反序列化的serialVersionUID不一致,於是反序列化失敗,程式出現crash。

Parcelable介面

Parcelable:介面,類實現此介面,其物件就可以實現序列化並可以通過Intent和Binder傳遞。

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {

    }

    public int getBookId() {
        return bookId;
    }

    public String getBookName() {
        return bookName;
    }

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

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

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

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

序列化過程需要實現的功能有序列化、反序列化和內容描述。

序列化:由writeToParcel方法完成,最終通過Parcel的一系列write方法完成。

反序列化:由CREATOR 來完成,其內部表明瞭如何建立序列化物件和陣列,並通過Parcel的一系列read方法完成反序列化過程。

內容描述功能:由describeContents方法來完成,幾乎所有情況下這個方法都應該返回0,當且僅當物件中存在檔案描述符時,此方法返回1.

Serializable、Parcelable比較:Serializable是Java中的序列化介面,開銷很大,序列化和反序列化需要大量的I/O操作,而Parcelable是Android中的序列化方式,這是Android推薦的序列化方式,使用稍微麻煩,但是效率高,因此首選Parcelable。Parcelable主要用在記憶體序列化,通過Parcelable將物件序列化到儲存裝置中或者物件序列化後通過網路傳輸都是可以的,但是過程稍微複雜,因此兩者對比下,建議使用Serializable。

Binder

直觀來說,Binder是Android中的一個類,實現了IBinder介面。

從IPC來說,Binder是Android中的一種跨程式通訊方式,Binder還可以理解為虛擬的物理裝置,它的儲存驅動是/dev/binder,該通訊方式在Linux是沒有的。

從Android Framework來說,Binder是ServiceManager連線各種Manager(ActivityManager、WindowManager等等)和ManagerService的橋樑。

從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含服務端業務呼叫的Binder物件,通過這個Binder物件,客戶端就可以獲取服務端的服務或資料,這裡的服務包括普通服務和基於AIDL的服務。

Android開發中,Binder 主要用在Service中,包括AIDL和Messenger,其中Service總的Binder不涉及程式間通訊,而Messenger的底層其實就是AIDL。

Binder的工作機制

IPC.png

Android中的IPC方式

Android中實現IPC方式有: (1)AIDL (2)Messenger (3)檔案共享 (4)Bundle (5)ContentProvider (6)Socket

使用AIDL

1、服務端 服務端首先要建立一個Service來監聽客戶端的連線請求,然後建立AIDL檔案,將暴露給客戶端的介面在這個AIDL檔案中宣告,最後在Service中實現AIDL介面即可。

2、客戶端 繫結服務端的Service,繫結成功後,將服務端返回的Binder物件轉換成AIDL介面所屬的型別,接著可以呼叫AIDL中的方法了。

AIDL支援的資料型別:

IPC.png

自定義的Parcelable物件和AIDL物件必須顯式的import進來,不管是否和當前的AIDL檔案在同一個包下。

AndroidStudio新建一個AIDL檔案

專案右鍵——>new——>AIDL

IPC.png

1)建立一個Book.java 檔案實現Parcelable介面

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {

    }

    public int getBookId() {
        return bookId;
    }

    public String getBookName() {
        return bookName;
    }

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

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

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

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

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

2)Book.java資料物件需要建立相對應的Book.aidl檔案

IPC.png

3)AIDL介面的建立

建立一個字尾為aidl的檔案,如下是IOnNewBookArrivedListener.aidl檔案

// IOnNewBookArrivedListener.aidl
package com.lwj.ipc.aidl;

// Declare any non-default types here with import statements
import com.lwj.ipc.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}
複製程式碼

這裡需要用到Book,需要顯式的import進來import com.lwj.ipc.aidl.Book;

再建立一個AIDL介面、用於註冊和解註冊IOnNewBookArrivedListener介面的方法,和addBook方法添書籍。

IBookManager.aidl

// IBookManager.aidl
package com.lwj.ipc.aidl;
import com.lwj.ipc.aidl.Book;
import com.lwj.ipc.aidl.IOnNewBookArrivedListener;
interface IBookManager {
 List<Book>getBookList();
 void addBook(in Book book);
 void registerListener(IOnNewBookArrivedListener listener);
 void unregisterListener(IOnNewBookArrivedListener listener);
}
複製程式碼

同樣BookIOnNewBookArrivedListener需要顯式import進來。

以上的檔案最好放到同一個包下面,也就是aidl包的下面。

4)建立Service,實現AIDL介面

建立Service,並且再AndroidManifest.xml中註冊

<service
            android:name="com.lwj.mytestpro.aidl.BookManagerService"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>

                <action android:name="com.lwj.ipc.aidl.BookManagerService"/>
            </intent-filter>
        </service>
複製程式碼

註冊Service的同時,android:process=":remote"開啟了一個程式,也就是讓服務端跟客戶端不同的程式,來演示跨程式通訊。

BookManagerService檔案實現AIDL介面

public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    //存放書本的集合
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //存放監聽的集合,必須使用RemoteCallbackList,否則無法解註冊
    private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();

    //實現AIDL介面
    private Binder binder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListeners.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
           mListeners.unregister(listener);
        }
    };


    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
        new Thread(new ServiceWorker()).start();
    }

    /**
     * 把新加的書本新增到集合中
     * 並且通知有新的書本增加
     * @param book
     * @throws RemoteException
     */
    private void onNewBookArrived(Book book)throws RemoteException{
        mBookList.add(book);
        final int N = mListeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListeners.getBroadcastItem(i);
            if (listener != null){
                //通知
                listener.onNewBookArrived(book);
            }
        }
        mListeners.finishBroadcast();
    }

    /**
     * 演示,每個一秒中增加一本書
     */
    private class ServiceWorker implements Runnable{

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new book#"+bookId);
                try {
                    //加入集合
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //許可權驗證
        int check = checkCallingOrSelfPermission("com.lwj.aidl.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED){
            return null;
        }
        return binder;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }
}

複製程式碼

這裡新增了一個許可權驗證

IPC.png

BookManagerActivity 和Service的跨程式通訊

BookManagerActivity客戶端實現:

public class BookManagerActivity extends AppCompatActivity {

    private static final String TAG = "BookManagerActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoterBookManager;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.i("lwjtag","receive new book:"+msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    };
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoterBookManager = bookManager;
                Book mBook = new Book(3,"Android 藝術開發探索");
                bookManager.addBook(mBook);
                List<Book> list = bookManager.getBookList();
                Log.i("lwjtag","query book list type:"+list.getClass().getCanonicalName());
                if (list != null && list.size() > 0){
                    for (Book book : list) {
                        Log.i("lwjtag","query book list:"+book.getBookName());
                    }
                }
                bookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
        }

        @Override
        public IBinder asBinder() {
            return mRemoterBookManager.asBinder();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this,BookManagerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if (mRemoterBookManager != null && mRemoterBookManager.asBinder().isBinderAlive()){
            try {
                mRemoterBookManager.unregisterListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_book_manager, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

複製程式碼

binder連線池

如果有10、100個不同的業務模組都需要AIDL來進行程式間通訊,那該怎麼處理?這種情況不可能建立10或者100個Service來處理,這就需要減少Service數量,將所有的AIDL 檔案放到同一個Service中去管理。

整個工作機制:每個業務模組建立自己的AIDL介面並實現此介面,這個時候不同的業務模組之間是不能有耦合的,所有的細節我們單獨開來,然後向服務端提供自己的唯一標識和其對應的Binder物件;對於服務端來說,只需要一個Service就可以了,服務端只是提供一個queryBinder介面,這個介面能夠根據業務模組的特徵來返回相應的Binder物件給他們,不同的業務模組拿到所需的Binder物件後就可以進行遠端方法呼叫了,由此可見,Binder連線池的主要作用就是將每個業務模組的Binder請求統一轉發到遠端的Service中執行,從而避免了重複建立Service的過程,工作原理如下圖所示:

IPC.png

例項:提供兩個AIDL介面(ISecurityCenter.aidl和Icompute.aidl)來模擬上面多業務模組都使用AIDL的情況。

1) Icompute.aidl:

// Icompute.aidl
package com.lwj.ipc.aidl;

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

interface Icompute {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     * binder執行緒池中的
     */
   int add(int a,int b);
}
複製程式碼

2) ISecurityCenter.aidl:

// ISecurityCenter.aidl
package com.lwj.ipc.aidl;

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

interface ISecurityCenter {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     * binder執行緒池中的
     */
     //加密
     String encrypt(String content);
     //解密
     String decrypt(String password);
}

複製程式碼

3) IBinderPool.aidl:提供查詢Binder的介面

// IBinderPool.aidl
package com.lwj.ipc.aidl;

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

interface IBinderPool {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     * binder執行緒池介面
     */
     IBinder queryBinder(int binderCoder);
}
複製程式碼

4)IcomputeImpl:實現Icompute介面

import android.os.RemoteException;

import com.lwj.ipc.aidl.Icompute;

/**
 * Created by Administrator on 2017/5/20 0020.
 */
public class IcomputeImpl extends Icompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a+b;
    }
}
複製程式碼

5)ISecurityCenterImpl:實現ISecurityCenter介面

import android.os.RemoteException;

import com.lwj.ipc.aidl.ISecurityCenter;

/**
 * Created by Administrator on 2017/5/20 0020.
 */
public class ISecurityCenterImpl extends ISecurityCenter.Stub {
    private static final char  SECRET_CODE = '^';
    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}

複製程式碼

6)BinderPool實現查詢binderpool介面的方法和邏輯

public class BinderPool {
    private static final String TAG = "BinderPool";
    public static final int BINDER_NONE = -1;
    public static final int BINDER_COMPUTE = 0;
    public static final int BINDER_SECURITY_CENTER = 1;

    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPool sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private BinderPool(Context context) {
        //獲取上下文
        this.mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    /**
     * 連線BinderPool
     * 啟動Service
     */
    private void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent(mContext, BinerPoolService.class);
        mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 單例模式實現
     * @param context
     * @return
     */
    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    /**
     * 根據code查詢binder並且返回Binder物件
     * @param binderCode
     * @return
     */
    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;

        try {
            if (mBinderPool != null) {
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    /**
     * 連線Service,拿到binderPool物件
     */
    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                //死亡監聽
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    /**
     * 死亡重連
     */
    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.i(TAG, "binder died.");
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    /**
     * 實現binderpool介面方法
     */
    public static class BinderPoolImpl extends IBinderPool.Stub {
        public BinderPoolImpl() {
            super();
        }

        @Override
        public IBinder queryBinder(int binderCoder) throws RemoteException {
            IBinder binder = null;
            switch (binderCoder) {
                case BINDER_SECURITY_CENTER:
                    binder = new ISecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new IcomputeImpl();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}
複製程式碼

7)建立Service,另開程式。

IPC.png

public class BinerPoolService extends Service {
    private static final String TAG = "BinerPoolService";
    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

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

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }
}
複製程式碼

8)BinderPoolActivity實現程式間通訊

public class BinderPoolActivity extends AppCompatActivity {

    private ISecurityCenter sercurityCenter = null;
    private Icompute mCompute;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder_pool);
        new Thread(new Runnable() {
            @Override
            public void run() {
                dowork();
            }
        }).start();
    }
    private void dowork(){
        //獲取binderPool例項
        BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);
        //查詢isecurityBinder
        IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
        //得到ISecurityCenter例項
        sercurityCenter = (ISecurityCenter)ISecurityCenterImpl.asInterface(securityBinder);
        Log.i("lwjtag", "visit ISecurityCenter");
        String msg = "helloworld-安卓";
        System.out.println("content:"+msg);
        try {
            String password = sercurityCenter.encrypt(msg);
            System.out.println("encrypt:"+password);
            System.out.println("decrypt:"+sercurityCenter.decrypt(password));
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        Log.i("lwjtag","visit ICompute");
        IBinder computeBinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE);
        mCompute = (Icompute) IcomputeImpl.asInterface(computeBinder);
        try {
            System.out.println("3+5="+mCompute.add(3,5));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_binder_pool, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

複製程式碼

結果:

IPC.png

這裡需要額外說明一下,為什麼要線上程中去執行呢?這是因為在Binder連線池的實現中,我們通過CountDownLath將binderService這一非同步操作轉換成了同步操作,這就意味著,它有可能是耗時的,然後就是Binder方法的呼叫過程也可能是耗時的,因此不建議放在主執行緒去執行。注意到BinderPool是一個單例實現,因此在同一個程式中只會初始化一次,所以如果我們提前初始化BinderPool,那麼可以優化程式體驗,比如我們可以放到Application中提前對BinderPool進行初始化,雖然不能保證我們呼叫BinderPool時它一定是初始化好的,但在絕大多數情況下,這種初始化(繫結遠端服務)的時間開銷(如果BinderPool沒有提前初始化完成的話)是可以接受的。另外,BinderPool中有短線重連的機制,當遠端服務意外終止時,BinderPool會重新建立連線,這個時候如果業務模組中的Binder呼叫出現了異常,也需要手動去重新獲取最新的Binder物件,這個是需要注意的。

BinderPool大大方便了日常的開發,極大的提高了AIDL的開發效率,並且避免大量的Service建立,建議在開發工作中引入BinderPool機制。

注意:

AIDL:解決5.0以上找不到類的方法,需要再app下的build.gradle加入下的android加入以下語句即可。

    sourceSets {
        main {
//            Manifest.srcFile ['src/main/AndroidManifest.xml']
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }
複製程式碼

使用Bundle

三元件(Activity、Service、Receiver)都支援在Intent中傳遞Bundle資料的,由於Bundle實現了Parcelable介面,所以可以方便地在不同的程式間通訊。

傳送的資料必須能夠被序列化,如:基本型別、實現Parcelable和Serializable介面,以及Android支援的特殊物件,具體內容可以看Bundle這個類,就可以看到其支援的型別。

特殊使用場景:如A程式正在進行一個計算,計算完成後它需要啟動B程式的一個元件並把計算結果傳送給B程式,可是遺憾的是計算結果不支援放入Bundle中,因此無法通過Intent傳輸,這個時候使用AIDL略顯複雜。可以考慮以下方式:我們可以通過Intent啟動B程式的一個Service元件(如IntentService),讓Service在後臺進行計算,計算完畢後再啟動B程式中真正要啟動的目標元件,由於Service也執行在B程式中,所以目標元件就可以直接獲取計算結果,這樣一來就輕鬆解決了跨程式的問題。這種方式的核心思想是在於將原來需要在A程式的計算任務轉移到B程式的後臺Service中執行,這樣就成功地避免了程式間通訊問題,而且只用了很小的代價。

使用檔案共享

檔案共享也是一種不錯的程式間通訊方式,兩個程式通過讀寫同一個檔案來交換資料,比如A程式把資料寫入檔案,B程式通過讀取這個檔案來獲取資料。windows上,一個檔案如果被加入了排斥鎖將會導致其他執行緒無法對其進行訪問,包括讀寫,而由於Android系統基於Linux,使得其併發讀/寫檔案可以沒有限制的進行,甚至兩個執行緒同時對同一個檔案進行讀寫操作都是允許的,儘管這可能出問題,通過檔案交換資料很好使用,除了交換一些文字資訊外,還可以序列化一個物件到檔案系統中的同時從另一個程式中恢復這個物件。如下采用的便是這一方式。

public class User implements Serializable {
    private static final long serialVersionUID = 519067123721295773L;//一般需要手動指定此值,避免反序列化失敗
    public int userId;
    public String userName;
    public boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    public User() {

    }

    public int getUserId() {
        return userId;
    }

    public String getUserName() {
        return userName;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setIsMale(boolean isMale) {
        this.isMale = isMale;
    }
}
複製程式碼

//序列化

    private void persistToFile(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                User user = new User(1,"hello world",false);
                File dir = new File(MyConstants.FILE_PAHT);
                if (!dir.exists()){
                    dir.mkdirs();
                }
                File cachedFile = new File(MyConstants.FILE_PAHT);
                ObjectOutputStream out = null;
                try {
                    out = new ObjectOutputStream(new FileOutputStream(cachedFile));
                    out.writeObject(user);
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    MyUtils.close(out);
                }
            }
        }).start();
    }
複製程式碼

//反序列化

    private void recoverFromFile(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                User user = null;
                File cacheFile = new File(MyConstants.FILE_PAHT);
                if (cacheFile.exists()){
                    ObjectInputStream in = null;
                    try {
                        in = new ObjectInputStream(new FileInputStream(cacheFile));
                        user = (User)in.readObject();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }finally {
                        MyUtils.close(in);
                    }
                }
            }
        }).start();
    }
複製程式碼

關閉輸出和輸入流

public class MyUtils {

    public static void close(Closeable stream){
        try {
            stream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
複製程式碼

通過檔案共享這種方式共享資料對檔案格式沒有具體要求,可以是文字檔案,也可以是XML檔案,只要讀/寫雙方約定資料格式即可。

使用Messenger

Messenger翻譯為信使,通過它可以在不同的程式間傳遞Message物件,在Message中放入我們要傳遞的資料,就可以輕鬆的實現資料在程式間傳遞。Messenger是一種輕量級的IPC方案,底層使用AIDL實現。Messenger對AIDL做了封裝,使得我們可以更加簡便的進行程式間通訊。同時,由於它一次處理一個請求,因此在服務端不用考慮執行緒同步的問題,這是因為服務端中不存在併發執行的情形。

步驟: 1)服務端程式 建立Service處理客戶端請求,同時建立一個Handler並通過它建立一個Messenger物件,然後在Service的onBind中返回這個Messenger物件底層的Binder即可。

2)客戶端程式 繫結服務端的Service,繫結成功後用服務端返回的IBinder物件建立一個Messenger,通過Messenger就可以向服務端傳送訊息了,發訊息型別為message物件。如果需要服務端能夠迴應客戶端,就和服務端一樣,我們還需要建立一個Handler並建立一個新的Messenger,並把這個Messenger物件通過Message的replyTo引數傳遞服務端,服務端通過replyTo引數可以迴應客戶端。

MessengerService

/**
 * 使用Messenger實現程式間通訊
 */
public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //返回messenger底層binder
        return mMessenger.getBinder();
    }

    //建立Handler
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MyConstants.MSG_FROM_CLIENT:
                    Log.i("lwjtag","receive msg from Client:"+msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message replyMessgae = Message.obtain(null,MyConstants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","嗯,你的訊息我已經收到,稍後會回覆你。");
                    replyMessgae.setData(bundle);
                    try {
                        client.send(replyMessgae);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
    //建立一個Messenger物件
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
}
複製程式碼

MessengerActivity

public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";
    private Messenger mService;
    //建立Messenger物件
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    //建立handler物件
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MyConstants.MSG_FROM_SERVICE:
                    Log.i("lwjtag","receive msg from Service:"+msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }

        }
    }


    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg","hello,this is client");
            msg.setData(data);
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);//傳送message
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this,MessengerService.class);
        //繫結Service
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //解綁
        unbindService(mConnection);
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_messenger, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

複製程式碼

IPC.png

IPC.png

在Messenger中進行資料傳遞就必須將資料放入Message中,而Messenger和Message都實現了Parcelable介面,因此可以跨程式傳輸。簡單來說,Message所支援的資料型別就是Messenger所支援的傳輸型別,實際上,通過Messenger來傳輸Message,Message中能使用的載體只有what、arg1、arg2、Bundle和replyTo。Message中的另一個欄位不支援跨程式傳輸,即便是2.2以後,也僅僅是系統提供的實現了Parcelable介面的物件才能通過它來傳輸,這就以為這我們自定義的Parcelable物件是無法通過object欄位來傳輸的。

IPC.png

使用ContentProvider

Contentprovider是Android中提供的專門用於不同應用間的資料共享的方式,從這一點來看,其天生就適合程式間通訊,其底層實現是Binder。

系統預置了許多ContentProvider,比如通訊錄資訊、日程表資訊等,要跨程式訪問這些資訊,只需要通過ContentResolver的query、insert和delete方法即可。

自定義ContentProvider,演示如何在其他的應用中獲取Contentprovider中的資料從而實現程式間通訊的目的。建立BookProvider,繼承Contentprovider並且實現六個抽象方法即可:onCreate、query、update、insert、delete和getType。

BookProvider

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    public static final String AUTHORITY = "com.lwj.ipc.provider";//contentProvider唯一
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CONDE = 1;
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private Context mContext;
    private SQLiteDatabase mDb;

    static {
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CONDE);
    }

    /**
     * 獲取表名
     * @param uri
     * @return
     */
    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DbOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CONDE:
                tableName = DbOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }

    @Override
    public boolean onCreate() {
        Log.i(TAG, "onCreate,current thread:" + Thread.currentThread().getName());
        mContext = getContext();
        //ContentProvider 建立時,初始化資料庫,注意:這裡僅僅是為了演示,事件使用中不推薦在主執行緒中進行耗時的資料庫操作
        initProviderData();
        return true;
    }

    private void initProviderData() {
        mDb = new DbOpenHelper(mContext).getWritableDatabase();
        mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from " + DbOpenHelper.USER_TABLE_NAME);

        mDb.execSQL("insert into book values(3,'Android');");
        mDb.execSQL("insert into book values(4,'Ios');");
        mDb.execSQL("insert into book values(5,'Html5');");

        mDb.execSQL("insert into user values(1,'jake',1);");
        mDb.execSQL("insert into user values(2,'jasmine',0);");

    }

    /**
     * 查詢
     * @param uri
     * @param projection
     * @param selection
     * @param selectionArgs
     * @param sortOrder
     * @return
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG, "query,current thread:" + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (tableName == null) {
            try {
                throw new IllegalAccessException("Unsupported uri:" + uri);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
    }

    /**
     * 返回一個Uri請求所對應的MIME型別(媒體型別)
     * MIME型別:圖片,視訊等。
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        Log.i(TAG, "getType");
        return null;
    }

    /**
     * 插入資料
     * @param uri
     * @param values
     * @return
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.i(TAG, "insert");
        String table = getTableName(uri);
        if (table == null) {
            try {
                throw new IllegalAccessException("Unsupported uri:" + uri);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        mDb.insert(table, null, values);
        mContext.getContentResolver().notifyChange(uri, null);
        return uri;
    }

    /**
     * 刪除操作
     * @param uri
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.i(TAG, "delete");
        String table = getTableName(uri);
        if (table == null) {
            try {
                throw new IllegalAccessException("Unsupported uri:" + uri);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        int count = mDb.delete(table, selection, selectionArgs);
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    /**
     * 更新
     * @param uri
     * @param values
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.i(TAG, "delete");
        String table = getTableName(uri);
        if (table == null) {
            try {
                throw new IllegalAccessException("Unsupported uri:" + uri);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        int row = mDb.update(table, values, selection, selectionArgs);
        if (row > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return row;
    }
}
複製程式碼

AndroidManifest.xml註冊

<provider
            android:name="com.lwj.ipc.contentprovider.BookProvider"
            android:authorities="com.lwj.ipc.provider"
            android:permission="com.wlj.PROVIDER"
            android:process=":provider"/>
複製程式碼

DbOpenHelper

public class DbOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = " book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";
    private static final int DB_VERSION = 1;
    //圖書和使用者資訊
    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + " name TEXT)";
    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT," + "sex INT)";

    public DbOpenHelper(Context context) {
        super(context,DB_NAME,null,DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //TODO
    }
}

複製程式碼

使用contentprovider ProviderActivity

public class ProviderActivity extends AppCompatActivity {

    private static final String TAG = "BookProvider";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Uri uri = Uri.parse("content://com.lwj.ipc.provider");
//        getContentResolver().query(uri,null,null,null,null);
//        getContentResolver().query(uri,null,null,null,null);
//        getContentResolver().query(uri,null,null,null,null);

        Uri bookUri = Uri.parse("content://com.lwj.ipc.provider/book");

        ContentValues values = new ContentValues();
        values.put("_id", 6);
        values.put("name", "程式設計的藝術");
        getContentResolver().insert(bookUri, values);
        Cursor cursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,null,null);
        while (cursor.moveToNext()){
            Book book = new Book();
            book.bookId = cursor.getInt(0);
            book.bookName = cursor.getString(1);
            Log.i(TAG,"query book id:"+book.getBookId()+ "----bookName:"+book.getBookName());
        }
        cursor.close();

        Uri userUri = Uri.parse("content://com.lwj.ipc.provider/user");
        Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
        while (userCursor.moveToNext()){
            User user = new User();
            user.userId = userCursor.getInt(0);
            user.userName = userCursor.getString(1);
            user.isMale = userCursor.getInt(2) == 1;
            Log.i(TAG,"query usre id:"+user.getUserId()+"----userName:"+user.getUserName());
        }
        userCursor.close();

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}
複製程式碼

IPC.png

注意:query、update、insert、delete四大方法是存在多執行緒併發的訪問的,因此方法內部要做好執行緒同步。在本例中,由於採用的是SQLite並且只有一個SQLitedatabase的連線,所以可以正確應對多執行緒的情況。具體原因是SQLiteDatabase內部對資料庫的操作是有同步處理的。但是如果通過多個SQLitedatabase物件之間無法進行執行緒同步。

使用Socket

Socket:套接字,是網路通訊中的概念。分為流式套接字和使用者資料包套接字,分別對應網路的傳輸控制層中的TCP和UDP。

TCP:面向連線的協議,提供穩定的雙向通訊功能。TCP連線的建立需要經過"三次握手"才能完成,為了提供穩定的資料傳輸功能,其本身提供了超時重傳機制,因此具有超高的穩定性。

UDP: 無連線的,提供不穩定的單向通訊功能,當然UDP也可以實現雙向通訊功能。在效能上,UDP具有更好的效率,其確定就是不能保證資料一定能正確傳輸,尤其在網路擁塞的情況下。

TCPServerService

/**
 * sockect實現程式間通訊
 */
public class TCPServerService extends Service {
    private boolean mIsServiceDestoryed = false;
    private String[] mDefinderMessages = new String[]{
            "你好啊,哈哈",
            "請問你叫什麼名字呀?",
            "今天北京天氣不錯呀,shy",
            "你知道嗎,我可是可以和多個人同時聊天的哦",
            "給你講個笑話吧,據說愛笑的人運氣不會太差,不知道真假"
    };


    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

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

    @Override
    public void onDestroy() {
        mIsServiceDestoryed = true;
        super.onDestroy();
    }

    private class TcpServer implements Runnable{

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket();
            } catch (IOException e) {
                System.err.println("establish tcp server failed,port:868");
                e.printStackTrace();
                return;
            }
            while (!mIsServiceDestoryed){
                //接收客戶端的請求
                try {
                    final Socket client = serverSocket.accept();
                    System.out.println("accept");
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                respondseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void respondseClient(Socket client) throws IOException{
        //用於接收客戶端訊息
        BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        //使用者向客戶端傳送訊息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));
        out.println("歡迎來到聊天室");
        while (!mIsServiceDestoryed){
            String str = in.readLine();
            System.out.println("msg form client:"+str);
            if (str == null){
                //客戶端斷開連線
                return;
            }
            int i = new Random().nextInt(mDefinderMessages.length);
            String msg = mDefinderMessages[i];
            out.println(msg);
            System.out.println("send :"+str);
        }
        System.out.println("client quit");
        MyUtils.close(out);
        MyUtils.close(in);
    }
}
複製程式碼

TCPClientActivity

public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView text;
    private EditText edit;
    private Button send;
    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG:
                    text.setText(text.getText() + (String) (msg.obj));
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    send.setEnabled(true);
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcpclient);
        text = (TextView) this.findViewById(R.id.text);
        edit = (EditText) this.findViewById(R.id.edit);
        send = (Button) this.findViewById(R.id.send);
        send.setOnClickListener(this);
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();

    }

    /**
     * 關閉連線
     */
    @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v == send) {
            final String msg = edit.getText().toString();
            if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
                mPrintWriter.println(msg);
                edit.setText("");
                String time = formatDateTime(System.currentTimeMillis());
                final String showedMsg = "self " + time + ":" + msg + "\n";
                text.setText(text.getText() + showedMsg);
            }
        }

    }

    /**
     * 格式化時間
     *
     * @param l
     * @return
     */
    private String formatDateTime(long l) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(l));
    }

    /**
     * 連線Server
     */
    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {//重連
            try {
                socket = new Socket("localhost",8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);//1000毫秒重連
                e.printStackTrace();
            }
            try {
                //接收伺服器端的訊息
//                if (socket == null || socket.getInputStream() == null)return;
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//                if (br == null)return;
                while (!TCPClientActivity.this.isFinishing()){
                    String msg = br.readLine();
                    System.out.println("receive :"+msg);
                    if (msg != null){
                        String time = formatDateTime(System.currentTimeMillis());
                        final String showedMsg = "server "+time+":"+msg+"\n";
                        mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget();
                    }
                }
                System.out.println("quit.....");
                MyUtils.close(mPrintWriter);
                MyUtils.close(br);
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
複製程式碼

選擇合適的IPC方式

根據下圖選擇合適的IPC方式:

IPC.png

相關文章