Android開發藝術探索筆記之 Android 中的 IPC 方式之文件共享和Message

Android 中的 IPC 方式
  • 通過Intent 中附加 extras 來傳遞信息
  • 通過共享文件的方式來共享數據
  • 通過Binder 方式來跨進程通信
  • 通過ContentProvider 跨進程通信
  • 通過 Socket 網絡通信實現數據傳遞

使用Bundle
四大組件中的三大組件(Activity、Service、Receiver)都是支持在 Intent 中傳遞 Bundle數據的,由於Bundle實現了 Parcelable 接口,故它可以方便的在不同進程間傳輸。

Bundle 傳輸的數據條件:必須能夠被序列化,如基本類型、實現了Parcellable、Serializable 接口的對象以及一些Android 支持的特殊對象

場景:
1、我們在一個進程中啓動了另一個進程的 Activity、Service、Receiver,就可以在Bundle 中附加我們需要傳輸給遠程進程的信息,並通過Intent 發送出去,Intent 可以攜帶基本數據類型和Bundle類型的數據

2、比如A 進程在進行一個計算,計算完成後它要啓動B 進程的一個組件並把計算結果傳遞給 B進程,但是這個計算結果不支持放入Bundle 中,因此無法通過 Intent 來傳遞,如採取其他 IPC 方式則會略顯複雜,可以考慮採用如下方式:
我們通過 Intent 啓動進程 B的一個 Service 組件(如 IntentService),讓Service 在後臺進行計算,計算完畢後再啓動 B進程中真正要啓動的目標組件,由於Service 也在B 進程中,故目標組件可直接獲取計算結果,這樣就輕鬆解決了跨進程的問題。
這種方式的核心思想在於將原本需要在A 進程中的計算任務轉移到 B進程的後臺 Service 中去執行,這樣成功的避免了進程間通信問題,且只用了很小的代價

使用文件共享
共享文件也是一種不錯的進程間通信方式,兩個進程通過 讀/寫 同一個文件來交換數據,比如 A 進程把數據寫入文件,B 進程通過讀取這個文件來獲取數據,通過文件交換數據很好使用,處理可以交換一些文本信息外,還可以序列化一個對象到文件系統中的同時從另一個進程中恢復這個對象

問題:怎麼防止併發操作用一個共享文件?即同時讀寫同一個文件數據?
但是由於 Android 系統基於 Linux,使得其併發讀/寫文件可以沒有限制的進行,甚至兩個線程同時對同一個文件進行寫操作都是允許的,

示例代碼:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.yhadmin.aidldemo"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--未指定 process 屬性,則默認運行在默認進程中,進程名爲包名-->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- : 的含義是指在當前的進程名前附加上當前的包名,以 : 開頭的進程屬於當前應用的私有進程,其他應用的
            組件不可以和它跑在同一個進程中,不以 : 開頭的進程屬於全局進程,其他應用通過 ShareUID 方式可以和
            它跑在同一進程中
        -->
        <activity
            android:name=".SecondActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:process=":remote">
        </activity>
        <!--聲明瞭完整的進程名,不會附加包名信息-->
        <activity android:name=".ThirdActivity"
                  android:configChanges="screenLayout"
                  android:label="@string/app_name"
                  android:process="com.example.yhadmin.aidldemo.remote">
        </activity>
    </application>

</manifest>

MainActivity

public class MainActivity
        extends AppCompatActivity
{

    private static final String TAG = "MainActivity";
    private IBookManager.Stub      mBookManager;
    private IBinder.DeathRecipient mDeathRecipient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button secondBtn = findViewById(R.id.second);
        Button third     = findViewById(R.id.third);
        secondBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SecondActivity.acrtionStart(MainActivity.this);
            }
        });
        third.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ThirdActivity.acrtionStart(MainActivity.this);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run() {
                User               user               = new User(1, "Hello world", false);
                File               file               = new File(getExternalCacheDir().getPath(),"test.text");
                ObjectOutputStream objectOutputStream = null;
                try {
                    objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
                    objectOutputStream.writeObject(user);
                    Log.d(TAG, "PERSIST USER:" + user);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (objectOutputStream!=null)
                        objectOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

SecondActivity

public class SecondActivity
        extends AppCompatActivity
{

    private static final String TAG = "MainActivity";
    public static void acrtionStart(Context context){
        context.startActivity(new Intent(context,SecondActivity.class));
    }
    private User mUser;

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

    private void recoverFromFile() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                File file = new File(getExternalCacheDir().getPath(),"test.text");
                ObjectInputStream objectInputStream=null;
                try {
                    objectInputStream = new ObjectInputStream(new FileInputStream(
                            file));
                    mUser = (User) objectInputStream.readObject();
                    Log.d(TAG,"recover user:"+mUser);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        if (objectInputStream!=null)
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

Log 日誌


從上可看出在 SecondActivity 中成功的從文件中恢復了之前存儲的 User 對象內容,這裏之所以說是內容,是因爲反序列化後得到的對象只是內容上和序列化的對象一樣,它們本質上是兩個對象

通過文件共享這種方式共享數據對文件格式是沒有要求的,比如可以使文本文件,也可以是XML 文件,只要讀/寫雙方約定數據格式即可。

缺點:併發讀/寫問題,如果併發讀/寫,那麼我們讀出的內容有可能不是最新的,如果併發寫就更嚴重了,因此我們要儘量避免這種情況的發生或者考慮使用線程同步來限制多個線程的寫操作

適用場景:對數據同步要求不高的進程之間通信,並且要妥善處理併發讀/寫的問題,使用線程同步鎖

SharedPreferences:Android 提供的輕量級存儲方案
通過鍵值對的方式村粗數據,底層採用XML 文件存儲鍵值對,每個應用的 SharedPreferences 文件都可以在當前包所在的data 目錄下查看到,一般它的目錄位於 /data/data/package name(包名)/shared_prefs目錄下,從本質上說 SharedPreferences  也是文件存儲的一種,但是由於系統對他的讀/寫有一定的緩存策略,即載內存中會有一份 SharedPreferences  文件的緩存,因此在多進程模式下,系統對它的讀/寫就變得不可靠,當面對高併發的讀/寫訪問,SharedPreferences 有很大機率丟失數據,因此不建議在進程間通信中使用 SharedPreferences 

使用Messenger(信使)
通過它可以再不同進程中傳遞對象,在Message 中放入我們需要傳遞的數據,就可以輕鬆實現數據的進程間傳遞,是一種輕量級的 IPC 方案,它的底層是AIDL,如下是 Messenger 的構造函數:

    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    //關聯的 Handler 會接收到發送的消息
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     * 
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
Messenger 的使用方法很簡單,它對AIDL 做了封裝,是的我們可以更簡單的進行進程間通信,同時由於他一次處理一個請求,因此在服務端我們不用考慮線程同步問題,因爲服務端中不存在併發執行的情形。

實現一個Messenger 有如下幾個步驟,分爲服務端和客戶端:
1、服務端進程:
  •  在服務端創建一個 Service 來處理客戶端的連接請求,同時創建一個 Handler 並通過它來創建一個 Messenger 對象,然後在 Service 的 onBind 方法中返回這個Messenger 對象底層 Binder 即可
2、客戶端進程:
  • 綁定服務端的 Service,綁定成功後用服務端返回的 IBinder 對象創建一個 Messenger, 通過這個Messenger 就可以向服務端發送消息了,發消息類型爲Message 對象,如果需要服務端能夠迴應客戶端,就和服務的一樣,還需要創建一個 Handler 並創建一個新的 Messenger 對象,並把這個 Messenger 對象通過 Message 的 replyTo 參數傳遞給服務端,服務端通過這個 replyTo 參數就可以迴應客戶端了

示例代碼:
public class MainActivity
        extends AppCompatActivity
{

    private static final String TAG = "MainActivity";

    private Messenger mService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //創建 Messenger 對象
            mService = new Messenger(service);
            //創建消息載體 Message 對象
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            //跨進程通信數據載體
            Bundle data = new Bundle();
            data.putString("msg","hello, this is client.");
            //msg 裝載 Bundle 數據
            msg.setData(data);
            try {
                //Messenger 發送消息
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button secondBtn = findViewById(R.id.second);
        Button third     = findViewById(R.id.third);
        secondBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MessengerService.class);
                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
            
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}
主要作用是綁定遠程的 MessengerService,綁定成功後,根據服務端返回的 binder 對象創建 Messenger 對象並使用此對象向服務端發送消息,即綁定成功後,獲取到了和服務端通信的Messenger 對象

MessengerService
public class MessengerService
        extends Service
{
    private static final String TAG = "MessengerService";
    //構建 MessengerHandler 處理客戶端發送的消息,並從消息中去除客戶端發送來的文本信息
    private static class MessengerHandler
            extends Handler
    {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.i(TAG,
                          "receive msg from Client:" + msg.getData()
                                                          .get("msg"));
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }
    //通過 MessengerHandler 構建 Messenger 對象,關聯 MessengerHandler
    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        //返回 Messenger 的 Binder 對象
       return mMessenger.getBinder();
    }
}
這裏 Messenger 的作用是將客戶端發送的消息傳遞給 MessengerHandler 處理

這裏是客戶端發送消息給服務端,故在服務端創建Handler 和 Messenger ,
manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.yhadmin.aidldemo"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!-- 未指定 process 屬性,則默認運行在默認進程中,進程名爲包名 -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!--
             : 的含義是指在當前的進程名前附加上當前的包名,以 : 開頭的進程屬於當前應用的私有進程,其他應用的
            組件不可以和它跑在同一個進程中,不以 : 開頭的進程屬於全局進程,其他應用通過 ShareUID 方式可以和
            它跑在同一進程中
        -->
        <activity
            android:name=".SecondActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:process=":remote">
        </activity>
        <!-- 聲明瞭完整的進程名,不會附加包名信息 -->
        <activity
            android:name=".ThirdActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:process="com.example.yhadmin.aidldemo.remote">
        </activity>
     <!-- 進程名爲包名:MessengerService -->
        <service
            android:name=".MessengerService"
            android:process=":MessengerService"
            android:enabled="true"
            android:exported="true">
        </service>
    </application>

</manifest>

log 日誌:

由上可以看出,在Messenger 中進行數據傳遞必須將數據放入Message 中,而Messenger 和 Message 都實現了Parcelable 接口,因此可以跨進程傳輸,即Message 中所支持的數據類型就是 Messenger 所支持的傳輸類型,實際上通過Messenger 來傳輸 Message,Message 中能使用的載體只有 what、arg1、arg2、Bundle以及 replyTo。Message 的另一個字段 object 在同一個進程中是很實用的,但在進程間通信時,在2.2之前 object字段不支持跨進程傳輸,在2.2之後也僅僅是系統提供的實現了 Parcelable 接口的對象才能通過它來傳輸,這意味着我們自定義的 Parcelable對象是無法通過 object 字段來傳輸的,但可用Bundle 來傳輸,Bundle可以支持大量的數據類型

實現服務端迴應客戶端效果:
客戶端:
public class MainActivity
        extends AppCompatActivity
{

    private static final String TAG = "MainActivity";

    private Messenger mService;
    private static class MessengerHander extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVICE://接收服務端發送的消息
                    Log.i(TAG,
                          "receive msg from Client:" + msg.getData()
                                                          .get("reply"));
                     break;
                default:
                    super.handleMessage(msg);
                     break;
            }
        }
    }
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHander());
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * Create a Messenger from a raw IBinder, which had previously been
             * retrieved with {@link #getBinder}.
             *
             * @param target The IBinder this Messenger should communicate with.
             */
            //從服務端獲取 Binder 對象,然後創建 Messenger 對象,屬於服務端?
            mService = new Messenger(service);
            //創建消息載體 Message 對象
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            //跨進程通信數據載體
            Bundle data = new Bundle();
            data.putString("msg","hello, this is client.");
            //msg 裝載 Bundle 數據
            msg.setData(data);
            //將這個信使存儲在msg的replyTo中,給服務端發消息用
            msg.replyTo = mGetReplyMessenger;
            try {
                //Messenger 發送消息
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button secondBtn = findViewById(R.id.second);
        Button third     = findViewById(R.id.third);
        secondBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MessengerService.class);
                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });

        third.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ThirdActivity.acrtionStart(MainActivity.this);
            }
        });

    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}
爲了接收服務端的回覆,客戶端需要準備一個接收消息的 Messenger 和 Handler
當客戶端發送消息時,需要把接收服務端回覆的Messenger 通過 Message 的 replyTo 參數傳遞給服務端
服務端:
public class MessengerService
        extends Service
{
    private static final String TAG = "MessengerService";
    //構建 MessengerHandler 處理客戶端發送的消息,並從消息中去除客戶端發送來的文本信息
    private static class MessengerHandler
            extends Handler
    {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.i(TAG,
                          "receive msg from Client:" + msg.getData()
                                                          .get("msg"));
                    //獲取 msg.replyTo 中的 Messenger,即客戶端發送過來的Messenger
                    Messenger client = msg.replyTo;

                    Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","嗯,你的消息我已經收到,稍後會回覆你。");
                    relpyMessage.setData(bundle);

                    try {
                        client.send(relpyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }
    //通過 MessengerHandler 構建 Messenger 對象,關聯 MessengerHandler
    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        /**
         * Retrieve the IBinder that this Messenger is using to communicate with
         * its associated Handler.
         *
         * @return Returns the IBinder backing this Messenger.
         */
        //返回 Messenger 的 Binder 對象
       return mMessenger.getBinder();
    }
}
log 日誌:
03-07 10:34:19.875 25823-25823/? I/MessengerService: receive msg from Client:hello, this is client.
03-07 10:34:19.875 25723-25723/com.example.yhadmin.aidldemo I/MainActivity: receive msg from Client:嗯,你的消息我已經收到,稍後會回覆你。   
個人理解總結:
    即服務端想要接收和處理客戶端的消息,需要準備一個 Messenger 和一個Handler,Messenger 通過IBinder 函數傳遞給客戶端用來發送消息,關聯的Handler 用來接收並處理消息
    客戶端想要接收和處理服務端的消息,也需要準備一個 Messenger 和一個Handler,並將這個Messenger 通過 msg.replyTo 方法攜帶給服務端,用於服務端發送消息

就像打電話,Messenger 就是電話號碼,先把我的電話號碼給你,然後你用我的電話號碼給我打電話?


源碼分析:
通過Handler 構建 Messenger
private Messenger mGetReplyMessenger = new Messenger(new MessengerHander());
通過關聯的Handler 獲取 IMessenger 對象
private final IMessenger mTarget;

    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

Handler 中的 getIMessenger 方法

  final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;//返回Messenger 對象,
        }
    }

    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();//存儲當前進程的 Uid
            Handler.this.sendMessage(msg);//通過Handler 發送消息
        }
    }
Messenger發送消息
//Messenger 發送消息
mService.send(msg);
在源碼中調用的是 MessengerImpl 的 send 方法,最終調用的是Handler 的 sendMessage(msg)方法
  /**
     * Send a Message to this Messenger's Handler.
     * 
     * @param message The Message to send.  Usually retrieved through
     * {@link Message#obtain() Message.obtain()}.
     * 
     * @throws RemoteException Throws DeadObjectException if the target
     * Handler no longer exists.
     */
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }

通過 IBinder 對象構建 Messenger 對象

//從服務端獲取 Binder 對象,然後創建 Messenger 對象,屬於服務端?
mService = new Messenger(service);

源碼:

   /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     * 
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
而這個IBinder 對象是通過綁定服務端時,回傳給客戶端的
 @Override
    public IBinder onBind(Intent intent) {
        /**
         * Retrieve the IBinder that this Messenger is using to communicate with
         * its associated Handler.
         *
         * @return Returns the IBinder backing this Messenger.
         */
        //返回 Messenger 的 Binder 對象
       return mMessenger.getBinder();
    }

查看源碼可知該IBinder 對象實質上就是通過 IMessenger獲取的

    /**
     * Retrieve the IBinder that this Messenger is using to communicate with
     * its associated Handler.
     * 
     * @return Returns the IBinder backing this Messenger.
     */
    public IBinder getBinder() {
        return mTarget.asBinder();
    }



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