學習筆記| (二)IPC機制

一.多進程

1.創建多進程:
  • 在android 中通過 android:process爲四大組件創建新的進程
  • 在特殊情況下也可以通過JNI去native層fork一個新的進程,但是一般不會這麼做
  • android:process = ":remote" 是私有進程(其他應用的組件不會和他跑在同一個進程中)
  • android:process = "com.test.remote"是全局進程(其他應用通過ShareUID和它跑在同一個進程中)
2.Share UID:
  • android 系統會爲每一個應用都分配一個UID(鑰匙)
  • 只有UID相同的才能共享數據(房間)
  • 注意:要想在同一個進程中運行,除了UID要一樣,簽名也得一樣;這種情況下的兩個應用,不光能訪問私有數據(比如data目錄),還能共享內存數據
3.多進程的運行機制
  • 系統會爲每個應用分配一個虛擬機,或者說,會爲每一個進程分配一個虛擬機
  • 同一個應用內不同進程直接就像是複製品一樣,每個進程內的東西是完全一樣的,一個進程內變量或其他東西的改變,不影響其他進程,他們都有自己的內存空間
4.多進程的影響:
  • 靜態成員變量和單例模式無效(原因就是3中所說的,每一個進程都有自己的內存空間了)
  • SharePreference無效(SharePreference不允許多個進程同時進行讀寫操作)
  • 線程同步機制無效(因爲不同進程鎖的都不是同一個對象)
  • 會創建多個Application(創建一個新的進程,就要創建一個新的虛擬機,就相當於打開了一個新的應用,會重新創建一個application。可以這麼理解,在同一個進程中的應用,用同一個虛擬機和同一個Application)

二.IPC機制

1.Serizable接口
  • 使用:
    • 寫一個類實現Serializable接口
    • 自己寫一個serialVersionUID或者是系統自動生成,自己寫的話可以保證序列化和反序列化正確
  • 不參與序列化的有:
    • 對於靜態的成員變量
    • transient關鍵字標記的不參與
String fname = Environment.getExternalStorageDirectory() + "/test.txt";
        //序列化
        User user = new User(1,"lili",true);
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(fname));
            outputStream.writeObject(user);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("user","存入失敗:"+e.getMessage());
        }
        //反序列化
        try {
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fname));
            User user = (User) inputStream.readObject();
            Log.e("user","讀取結果:"+user.toString());
            inputStream.close();
            textEdit.setText(user.toString());
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("user","讀取失敗:"+e.getMessage());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            Log.e("user","讀取失敗222:"+e.getMessage());
        }
2.Parceable接口
Parcelable方法.png
  • 寫一個類實現Parcelable接口
  • parceable方法說明.png
  • 在序列化對象中有一個是實現了Parcelable的類,序列化時需要:
    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(book,0);
    }
    //反序列化 
    protected User(Parcel in) {
       //需要上傳當前上下文的線程
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }
3.這兩個接口的區別
區別 Serializable Parcelable
平臺 java的序列化接口 android的序列化接口
原理 將一個對象轉成可存儲或可傳輸的狀態 將一個對象分解成若干個支持傳輸的類型
使用場景 將序列化對象存儲到設備上或者是用於網絡傳輸(推薦) 內存序列化
優缺點 用到了大量的I/O操作(ObjectOutputStream/ObjectInoutStream),使用簡單,但是消耗過大 高效,但是使用複雜
4.Binder
4.1 理解
  • Binder是android的一個類,實現了IBinder接口
  • 從IPC角度:是Android中一種跨進程通信方式,可以理解爲一種虛擬的物理設備,它的驅動是/dev/binder
  • 從Framework層,是ServiceManager連接各種Manager和相應ManagerService的橋樑
  • 從應用層,是連接客戶端和服務端的橋樑,bindService的時候,服務端會返回一個服務端的Binder對象給客戶端,客戶端可以通過這個Binder可以獲取服務端提供的服務和數據
4.2 創建aidl

①創建Book.java實現Parceable接口
②創建Book.aidl(先取別的名字,之後再重命名)

// Book.aidl
package ly.com.artres.aidl;
parcelable Book;

③創建IBookManager.aidl

// IBookManager.aidl
package ly.com.artres.aidl;
//手動導包
import ly.com.artres.aidl.Book;

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

interface IBookManager {
    //獲取所有圖書列表
    List<Book> getBookList();

    //添加圖書:AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out、inout,in表示輸入型參數,
    //out輸出型參數,inout表示輸入輸出型參數,AIDL中只支持方法,不支持聲明靜態常量,是不同於一般接口的
    void addBook(in Book book);
}

④Build-->Make project之後在

build/generate/source/aidl中可以看到編譯後的IBookManager .java
4.3 手寫aidl

所有通過Binder傳輸的接口都要繼承IInterface接口

aidl內部實現.png

binder機制.png

/**
 * 手寫aidl實現
 * 所有通過Binder傳輸的接口都要繼承IInterface接口
 */
public interface BookManager extends IInterface{
    //1.1創建文件描述類
    static final String DESCRIPTION = "ly.com.artres.aidl.BookManager";

    //1.2創建兩個方法的標識
    static final int TRANSACT_GETBOOKLIST = IBinder.FIRST_CALL_TRANSACTION + 0;
    static final int TRANSACT_ADDBOOK = IBinder.FIRST_CALL_TRANSACTION + 1;


    //1.3創建客戶端要調用的幾個方法
    List<Book> getBookList() throws RemoteException;
    void addBook(Book book) throws RemoteException;

    //1.4創建內部類Stub,實現外部接口,實際上是一個Binder
    public class BookManageImpl extends Binder implements BookManager{

        //2.1要有一個構造函數和外部接口進行綁定
        public BookManageImpl(){
            this.attachInterface(this,DESCRIPTION);
        }

        //2.2 將服務端的Binder轉化爲客戶端需要的接口(BookManager)
        public static BookManager asInterface(Binder binder){
            if (binder == null){
                //客戶端與服務端連接失敗
                return null;
            }

            //根據描述,取出當前進程的接口
            IInterface iin = binder.queryLocalInterface(DESCRIPTION);
            if (iin != null && iin instanceof BookManager){
                //相同進程,取出的接口和客戶端的接口相同,則返回服務端binder
                return (BookManager) binder;
            }

            //不同進程
            return new BookManageImpl.Proxy(binder);
        }

        //2.3 返回當前的binder對象
        @Override
        public IBinder asBinder() {
            return this;
        }

        /**
         * 2.4 客戶端發起RPC(遠程過程調用)請求--->運行在服務端的Binder線程池中,所以所有的Binder請求都要是同步的
         * @param code : 請求的方法標識
         * @param data : 請求參數
         * @param reply : 請求的返回結果
         * @param flags : 參數flags只有0和FLAG_ONEWAY兩種,默認的跨進程操作是同步的,所以transact()方法的執行會阻塞,
         *              調用以同步的形式傳遞到遠程的transact(),等待遠端的transact()返回後繼續執行——最好理解的方式就是把
         *              兩端的transact()看作一個方法,Binder機制的目標也就是這樣。指定FLAG_ONEWAY時,表示Client的transact()
         *              是單向調用,執行後立即返回,無需等待Server端transact()返回
         * @return 返回false,則客戶端請求失敗
         * @throws RemoteException
         */
        @Override
        public boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            //判斷請求的哪個方法
            switch (code){
                case TRANSACT_GETBOOKLIST:
                    //獲取圖書列表
                    //有請求參數,讀取請求參數(沒有)
                    data.enforceInterface(DESCRIPTION);

                    //調用請求方法
                    List<Book> list = this.getBookList();

                    //請求完成後,需要返回值,則寫到reply中
                    reply.writeNoException();
                    reply.writeTypedList(list);

                    return true;

                case TRANSACT_ADDBOOK:
                    //添加圖書
                    //有請求參數,讀取請求參數(有)
                    data.enforceInterface(DESCRIPTION);

                    Book _arg0;
                    if (0 != data.readInt()){
                        //將之前我們所存入的數據按照順序進行獲取,反序列化
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    }else {
                        _arg0 = null;
                    }

                    //調用方法
                    this.addBook(_arg0);

                    //請求完成後,需要返回值,則寫到reply中(沒有返回值)
                    reply.writeNoException();

                    return true;

                case INTERFACE_TRANSACTION:
                    reply.writeString(DESCRIPTION);
                    return true;

                default:
                    break;
            }

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

        //2.4創建代理類
        private static class Proxy implements BookManager{
            IBinder mRemote;

            /**
             * 服務端的binder
             * @param binder
             */
            Proxy(IBinder binder){
                this.mRemote = binder;
            }

            /**
             * 運行在客戶端
             * @return
             * @throws RemoteException
             */
            @Override
            public List<Book> getBookList() throws RemoteException {
                Parcel _data = Parcel.obtain();//創建輸入型對象
                Parcel _reply = Parcel.obtain();//創建輸出型對象
                List<Book> _result = null;//創建結果對象

                try {
                    //寫入請求參數(爲空)
                    _data.writeInterfaceToken(DESCRIPTION);

                    //發起RPC請求
                    mRemote.transact(TRANSACT_GETBOOKLIST,_data,_reply,0);

                    //返回結果(list)
                    _reply.readException();
                    _result = _reply.createTypedArrayList(Book.CREATOR);
                }catch (Exception e){
                    e.getMessage();
                }finally {
                    //回收
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            /**
             * 運行在客戶端
             * @param book
             * @throws RemoteException
             */
            @Override
            public void addBook(Book book) throws RemoteException {
                Parcel _data = Parcel.obtain();//創建輸入型對象
                Parcel _reply = Parcel.obtain();//創建輸出型對象

                try {
                    //寫入請求參數
                    _data.writeInterfaceToken(DESCRIPTION);
                    if (book != null){
                        //序列化
                        _data.writeInt(1);
                        book.writeToParcel(_data,0);
                    }else {
                        _data.writeInt(0);
                    }

                    //發起RPC請求
                    mRemote.transact(TRANSACT_ADDBOOK,_data,_reply,0);

                    //返回結果(爲空)
                    _reply.readException();
                }catch (Exception e){
                    e.getMessage();
                }finally {
                    //回收
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public IBinder asBinder() {
                return mRemote;
            }

            public String getInterfaceDescription(){
                return DESCRIPTION;
            }
        }

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

        @Override
        public void addBook(Book book) throws RemoteException {

        }
    }

}

4.4 兩個重要方法
  • unlinkToDeath()
private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if (mManage == null){
                //斷開之前的連接
                mManage.asBinder().unlinkToDeath(mRecipient,0);
                mManage = null;
            }
        }
    };
  • linkToDeath():
binder.linkToDeath(mRecipient,0);

三、Android中的IPC方式:

  • Bundle
  • ContentProvider
  • AIDL
  • Messenger
  • 文件共享
  • Socket
3.1 Bundle
  • Bundle實現了Parcelable接口,可以通過Intent傳遞
  • Bundle可以放基本數據類型,實現了Parcelale或Serializable接口的類,還有特殊數據類型
       Intent intent = new Intent(this,BActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("key1","你在幹啥");
        intent.putExtra("intentkey",bundle);
        startActivity(intent);
  • 特殊使用場景:
    進程A中的計算結果要在進程B中展示,且這個計算結果不支持Bundle傳輸,可以通過Intent啓動B的一個Service組件,然後B在後臺進行計算,計算完成後在B中顯示
3.2 Messenger(信使)
  • 是一種輕量級的ipc方案
  • 底層是AIDL
  • 串行的方式處理消息,客戶端發一個消息,服務端處理一個,當客戶端發了大量的消息的時候,服務端也只能一個一個處理,效率低,不適合用Messenger處理。
  • Messenger主要是用來傳遞消息,當要調用服務端的方法時就不適用了。
    messenger流程圖.jpg

原理分析:
①client和server綁定後
②client在onServiceConnected中通過IBinder生成一個Messenger發送Message消息
③server接收到client發來的消息後,會在Handler中進行處理
④server回覆消息給client,也是通過Messenger,他發送的消息需要客戶端處理,所以這個Messenger得是client的,怎麼創建客戶端的Messenger呢?只需要在②中,發送消息的時候,通過msg.replyTo = xx,就可以把client的messenger傳過來了

  • 注意:
    在跨進程中不能傳遞自定義的Parceable對象
3.3 AIDL
  • (1)支持的數據類型:

    aidl支持的數據類型.png

    自定義的Parcelable對象和AIDL都要手動import

  • (2)解註冊跨進程的接口時:

@Override
    protected void onDestroy() {
        if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()){
            //註銷
            Log.e("testaidl","onDestroy unregister listener:"+mNewBookListener);
            try {
                mRemoteManager.unregisterListener(mNewBookListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        unbindService(mConnection);
        super.onDestroy();
    }

這樣寫的時候會解註冊失敗,因爲這時候的manager和service端的都不是同一個了,aidl不能傳輸對象,這裏的manager相當於兩個新對象。
要刪除這個跨進程的接口,要用到RemoteCallBackListener;
使用方法:

 //用於刪除跨進程的接口
private RemoteCallbackList<IOnNewBookArrivedLisneer> mListennerist = new RemoteCallbackList<>();

@Override
public void registerListener(IOnNewBookArrivedLisneer listener) throws RemoteException { 
            mListennerist.register(listener);
        }

@Override
public void unregisterListener(IOnNewBookArrivedLisneer listener) throws RemoteException {
            mListennerist.unregister(listener);
        }

注意:
在使用RemoteCallbackList時要注意:

      //獲取元素個數
       int N = mListennerist.beginBroadcast();
        for (int i=0;i<N;i++){
            IOnNewBookArrivedLisneer lisneer = mListennerist.getBroadcastItem(i);
            if (lisneer != null){
                try {
                    lisneer.onNewBookArrived(book);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

        //要和beginBroadcast配套使用
        mListennerist.finishBroadcast();

(3)在UI線程中調用遠程端的耗時方法(互調):

  • 服務端中的方法都運行在Binder線程池中,客戶端調用服務端中的耗時方法時,會造成ANR,所以要在客戶端開啓一個子線程才能調用服務端的方法;
  • 服務端的調用客戶端Binder線程池中的某一個耗時方法時,也要開始一個線程才行;

(4)Binder意外死亡的時候:

  • 可以在onServiceDisconnected中重新連接
  • 可以在DeathRecipient 中斷開之前的連接,再重新綁定
IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
        }
    };
  • 他們二者的區別:
    onServiceDisconnected是運行在UI線程中,而DeathRecipient 是運行在客戶端的Binder線程池中的

(5)權限驗證:
聲明權限

<!--權限驗證,驗證通過才能連接服務器-->
<permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/>

使用權限:

<uses-permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE"/>
  • 可以在onBinder()中驗證權限
@Override
    public IBinder onBind(Intent intent) {
        //權限驗證
        int permission = checkCallingOrSelfPermission("ly.com.artres.aidl.ACCESS_BOOK_SERVICE");
        if (permission == PackageManager.PERMISSION_DENIED){
            return null;
        }
        return mBinder;
    }
  • 可以在onTransact()中通過權限驗證或者Uid,Pid進行驗證

  • 總結:創建一個service和AIDL接口,創建一個類實現AIDL接口中的Stub類並實現裏面的抽象方法,在service的onBind()中返回這個類的對象,然後在客戶端就可以綁定服務端Service,繼而訪問遠程接口

3.4 ContentProvider
  • 底層是Binder
  • 寫一個類繼承ContentProvider
    要實現6個方法:
    ①boolen onCreate():創建,做初始化工作,運行在UI線程中;
    ②String getType(Uri uri):根據uri獲取對應的MIME(媒體)類型:文字/圖片/視頻,在主線程;
    ③Uri insert(@NonNull Uri uri, @Nullable ContentValues values):插入數據,在binder線程池中運行;
    ④int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs):刪除數據,在binder線程池中運行;
    ⑤int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs):更新數據,在binder線程池中運行;
    ⑥Cursor query(...):查詢數據,在binder線程池中運行;

實現步驟:

  • 在manifest中定義這個provider
<provider
            android:name=".contentproviderdemo.BookProvider"
            android:authorities="ly.com.artres.provider"//唯一標識
            android:permission="ly.com.PROVIDER"//權限
            android:process=":process" />//這個可以不寫
  • 根據Uri-->得到UriCode--->再判斷是哪一張表
  • 將uri和uricode進行綁定
   //創建Uri和UriCode
    private static String AUTHORITY = "ly.com.artres.provider";
    private static Uri BOOK_URI = Uri.parse("content://"+AUTHORITY+"/book");
    private static Uri USER_URI = Uri.parse("content://"+AUTHORITY+"/user");
    public static final int BOOK_CODE = 0;
    private static final int USER_CODE = 1;

   //創建urimatch
    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI(AUTHORITY,"book",BOOK_CODE);
        uriMatcher.addURI(AUTHORITY,"user",USER_CODE);
    }

     /**
     * 根據Uri得到Uricode,再判斷是哪一張表
     * @param uri
     * @return
     */
    private String getTableName(Uri uri){
        String tableName = null;
        switch (uriMatcher.match(uri)){
            case BOOK_CODE:
                tableName = DBOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_CODE:
                tableName = DBOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }


   @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e("testcp","insert():"+Thread.currentThread().getName());

        String tableName = getTableName(uri);
        if (tableName == null){
            throw new IllegalArgumentException("insert unsupport uri:"+uri);
        }

        mDb.insert(tableName,null,values);
        mContext.getContentResolver().notifyChange(uri,null);
        return null;
    }

SQLite數據庫:

  • 底部實現了線程同步,如果provider不使用sqlite數據庫,要注意在CRUD中使用線程同步;
  • 自定義一個類繼承SQLiteOpenHelper,必須要有一個構造方法;
  • SQLiteOpenHelper用於創建升級數據庫;
public class DBOpenHelper extends SQLiteOpenHelper {
    public static final String DB_NAME = "book.db";
    public static final String BOOK_TABLE_NAME = "booktb";
    public static final String USER_TABLE_NAME = "usertb";
    public static final int DB_VERSION = 1;

    private String CREATE_BOOK = "create table if not exists "+BOOK_TABLE_NAME+"(_id integer primary key,name text)";
    private String CREATE_USER = "create table if not exists "+USER_TABLE_NAME+"(_id integer primary key,name text)";


    /**
     * 必須要有一個構造函數
     * @param context
     */
    public DBOpenHelper(Context context) {
        super(context,DB_NAME,null,DB_VERSION);
    }


    @Override
    public void onCreate(SQLiteDatabase db) {
       //創建表
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_USER);
    }

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

    }
}

根據sqliteopenhelprt可以創建sqlitedatabase,然後在contentprovider的CRUD方法中調用sqlitedatabase的增刪改查的方法;

SQLiteDatabase mDb = new DBOpenHelper(mContext).getWritableDatabase();

在Activity中往provider中插入數據時:

        Uri uri = Uri.parse("content://ly.com.artres.provider/book");
        ContentValues values = new ContentValues();
        values.put("_id",4);
        values.put("name","語文");
        getContentResolver().insert(uri,values);

在Activity中從provider中查詢數據時:

        Uri uri = Uri.parse("content://ly.com.artres.provider/book");
        Cursor bookCursor = getContentResolver().query(uri,new String[]{"_id","name"},null,null,null);
        while (bookCursor.moveToNext()){
            int id = bookCursor.getInt(0);
            String name = bookCursor.getString(1);
            Log.e("testcp","query book結果:"+id+",name:"+name+"\n");
        }

        bookCursor.close();
3.5 文件共享
  • 實現Serializable接口
  • 通過ObjectOutputStream()和ObjectInputStream()實現
3.6 Socket(套接字)

1.分爲兩種:

  • 流式套接字-->對應TCP(面向連接,提供穩定的雙方通信功能,要建立三次握手,本身具有超時重連機制)
  • 用戶數據報套接字-->對應UDP(也能實現雙方通信功能,性能會更高,但是數據不一定能傳輸成功,)

2.可以傳輸任意字節流

3.使用:

  • 要有權限(不能在主線程訪問網絡
    <!-- socket要的權限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • 步驟:
    服務端:
①服務端創建Socket
ServerSocket serverSocket = new ServerSocket(8688);

②接收客戶端請求:
final Socket client = serverSocket.accept();

③接收客戶端請求
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String str = in.readLine();

④向客戶端發送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("歡迎來到聊天室");

客戶端:

①連接服務端
Socket socket = new Socket("localhost",8688);

②向服務端發送請求:
PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
mPrintWriter.println(“hello”);

③接收服務端的消息:
 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = in.readLine();

四、Binder連接池:

五、選擇合適的IPC方式:

選擇ipc方式.png
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章