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();
    }



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