1.Android中的多进程模式:
在android中最有特色的进程通讯方式就是Binder了,通过Binder可以轻松的实现进程间通讯,除了Binder,还有Socket,通过Socket也可以实现任意两个进程之间的通讯,当然同一个设备的两个进程通过Socket也是可以通讯的。通过四大组件的addroid:process属性,就可以开启多线程模式,但是却会有很多其他问题,下面会说到。
1.1开启多线程模式
在Android 中使用多进程只有一个方法,那就是在AndroidMenifest中指定android:process属性,除此之外没有任何其他方法,也就是说我们无法给一个线程或一个实体类指定其运行时的进程。其实还有一个非常规的方法,那就是通过JNI在native层去fork一个新的进程。但是不用作考虑这种特殊情况。
下面演示如何使用:
<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity" android:process=":processB"/>
<activity android:name=".ThirdActivity" android:process="jian.com.ipctest.remote"/>
</application>
上面的SecondActivity和ThirdActivity指定了process属性。并且他们属性不同,这意味着当前应用又增加了两个新进程。
我们尝试从MainActivity调到SecondActivity中,然后到ThirdActivity。
在命令行中输入:shell ps |grep 你的包名可得:
可以看到我们成功的启用了多个进程。
在SecondActivity和ThirdActivity中的android:process属性一个有带包名一个没带。":"的含义是在当前的进程名称加上当前的包名,这个简单的写法。它是属于当前进程的私有进程。其他应用不可以和他跑在同一个进程中,而进程名不“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和他跑在同一个进程中。
1.2多进程模式运行机制
1.2.1多进程中的问题
新建一个UserManager。
public class UserManager {
public static int mUserId = 1;
}
然后在上面的MainActivity中将其赋值为2,并打印,然后跳到SecondActivity中再次打印,正常情况下两个都应该是2才对。我们看结果:
并没有像我们在以前在用一个进程中使用的那样。所以多进程并不是在android:process中设置属性那么简单。
那么为什么会出现这种情况呢?原因是SecondActivity运行在了一个独立的进程中。android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机。不同的虚拟机在内存中有不同的地址空间,这样的话,不同的虚拟机访问同一个类的对象的时候就会产生多份副本。也就是说。这两个进程都存在一个UserManger类,并且互补干扰,在同一个进程中修改mUserId只会影响当前进程,对其他进程没有任何的影响的。这就是为什么SecondActivity中的mUserId依然还是1的原因。
使用多进程的问题:
- 静态成员和单例模式完全失效。
- 线程同步机制完全失效
- SharePreference的可靠性降低
- Application会多次创建
2Andorid中IPC方式
2.1Bundle
Activity ,Service,Receiver 都是支持Intent 中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不o同广东进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity,Service或Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
能被传数据类型必须要是基本类型或实现了Parcelable接口的对象,实现了Serializable接口的对象。
下面作为测试的例子。因为方便直接在一个model中使用。设置不同的进程可以在AndroidManifest.xml中使用andorid :process=""标签
如:
<activity android:name=".MainActivity" android:process=":processA">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity android:name=".ActivityB" android:process=":processB"/>
MainActivity在进程A中,ActivityB在进程B中
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,ActivityB.class);
Bundle bundle = new Bundle();
bundle.putInt("id",10);
intent.putExtra(ActivityB.BUNDLE_TAG,bundle);
startActivity(intent);
}
});
}
}
ActivityB.java
public class ActivityB extends AppCompatActivity {
public static final String BUNDLE_TAG = "BUNDLE";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activityb);
Intent intent =getIntent();
Bundle bundle = intent.getBundleExtra(BUNDLE_TAG);
int id = bundle.getInt("id");
Toast.makeText(ActivityB.this,"id="+id,Toast.LENGTH_LONG).show();
}
}
运行结果
可以看到是可以成功的传输过去,和在同一进程没啥分别。
2.2使用文件共享
在Andorid中,使用并发读/写文件可以没有限制的进行,甚至两个线程同时对同一个文件进行写操作都是可以的,尽管这可能出现问题。通过文件交换数据很好用。除了可以交换一些文本信息。还可以序列化一个对象文件系统中的同事从另一个进程中恢复这个对象,下面就展示这个使用方法。
我们新建一个User类:
public class User implements Serializable
{
private int id;
private String info;
private boolean isLogin;
public User(int id, String info, boolean isLogin) {
this.id = id;
this.info = info;
this.isLogin = isLogin;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", info='" + info + '\'' +
", isLogin=" + isLogin +
'}';
}
}
在Activity中执行以下方法
private void persistToFile(){
PATH =getFilesDir().toString();
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1,"hello",false);
File dir = new File(PATH,"User");
if(!dir.exists()){
dir.mkdirs();
}
File cacheFile = new File(PATH,CACHPATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cacheFile));
objectOutputStream.writeObject(user);
Log.d("MainActivity---User","persist user:"+user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
在SecondActivity中执行以下方法:
private void recoverFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cacheFile = new File(getFilesDir().toString(),CACHPATH);
if(cacheFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(cacheFile));
user = (User) objectInputStream.readObject();
Log.d("SecondActivity---User", "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
,然后查看运行的结果:
我们可以看到上面成功的恢复了之前保存的User对象中的内容,说是内容是因为得到的对象只是在内容上和序列化前的是一样的,但本质上是两个不同的对象。
tip:文件共享的方式适合用于数据同步要求不高的进程之间进行通讯。并且要妥善处理并发读/写的问题。
关于SharedPreferences是个特例,众所周知,SharedPreferences是Android中提供的轻量级的存储方案,底层用XML文件来存储键值对。本质来说sharePreferences也属于文件的一种,但是由于系统对他的读/写有一定的缓存策略,即使在内存中有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠了。当面对文件并发的读/写会有很大的可能丢失数据,因此不建议在多进程通讯中使用SharePreferences。
2.3使用Messenger
Messenger是一个轻量级的IPC方案,它的底层实现是AIDL我们来看一下Messenger的构造方法就知道了.明显的可以看到有AIDL的痕迹在这里。
public Messenger(Handler target){
mTarget =target.getIMessenger();
}
public Messenger(IBinder target){
mTarget = IMessenger.Stub.asInterface(target);
}
由于它每一次处理一个请求,因此在服务端不需要考虑多线程的同步的问题。因为服务端不存在并发执行的情形。实现一个Messenger的步骤分为服务端和客服端。
服务端进程
首先,我们需要在服务器端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中班会这个Messenger对象即可。客户端进程
客户端进程首先要先绑定服务端的Service,绑定成功后用服务端返回的IBind对象创建一个Messenger,通过这个Messenger就可以向服务器发送消息了,发送消息类型为Messenger对象。如果需要服务端能回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个messenger对象通过replyTo参数传递给服务端,服务端通过这个参数就可以回应客服端。下面的例子说明了这个。
创建MessengerService.java
用MessengerHandler来处理客户端发送的消息,并返回一个消息给客户端。Messenger的作用是将客户端发送的消息传递给MessengerHandler处理
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.i(TAG,"接收到消息从客户端:"+msg.getData().getString("msg"));
//回复消息给客户端
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,2);
Bundle bundle = new Bundle();
bundle.putString("reply","我收到了你的消息了,稍后给你信息");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mMessenger.getBinder();
}
}
然后注册service,让它独立运行在一个进程中。
<service
android:name=".service.MessengerService"
android:process=":remote" />
客户端的实现
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService =new Messenger(iBinder);
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","hello jian,I am client");
msg.setData(data);
//这个将需要接收服务端回复的Messenger通过Message的replyTo参数传递给服务端
msg.replyTo = mGetReplyMessager;
try{
//通过Messenger发送消息
mService.send(msg);
}catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
private Messenger mGetReplyMessager = new Messenger(new MessengerHandler());
// 处理服务端返回的消息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 2:
Log.i(TAG,"接收服务端的消息:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//绑定Service
Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
}
预期的结果是点击按钮,服务端接收到消息,然后客户端接收到服务端返回的消息
上面可以看出,在Messenger中进行数据传递必须将数据放入Message中,而Message都实现了Parcelable接口,因此可以跨进程传输。这里用到Bundle,bundle可以支持大量的数据类型。有一个很关键的是,如果需要接收服务端返回的参数,那么就需要设置客户端Message的replyTo参数,也就是一个Handler。(可以理解错传递了一个对象国服务端,然后服务端调用这个对象里面的方法)
Messenger工作原理
未完待续
纪录于andorid开发艺术探索。