查看原文
其他

Android技能树 — 多进程相关小结

2018-04-13 青蛙要fly 鸿洋

本文作者


作者:青蛙要fly

链接:

https://www.jianshu.com/p/15dfdcc3d8b7

本文由作者授权发布。


1前言


最近过完年了,打算把自己的Android知识都整理一下。


Android技能书系列:

Android技能树 — 动画小结、 View小结、 Activity小结、View事件体系小结、 Android存储路径及IO操作小结、多进程相关小结。


本篇为多进程小结,其他文章欢迎直接看博主文章~~


其他不多说,先上脑图:


需要点击放大查看


2进程与线程



有时候面试别人的时候,我会问说什么是多进程,怎么开启多进程,他们会说new 一个Thread。所以很多人会把多进程和多线程弄错。我就简单说明下:一般来说我们启动一个APP,一般就是一个进程,然后这个APP里面有很多线程,最熟悉的就是我们平常的主线程(UI)线程。所以进程是包含线程的。


当然我这讲的就比较通俗了:


可以看下其他类似的文章介绍:


Android--进程与线程

https://www.cnblogs.com/plokmju/p/android_ProcessOrThread.html


3开启多进程



其实开启多进程很简单。只需要在AndroidManifest.xml的四大组件中添加android:process即可。这时候就会运行在你定义好的名字的进程中了。


多进程开启后的问题


简单来说就是同步会有问题。我们刚才说了一般来说启动一个APP,就创建了一个进程,然后所有的东西都在这个进程里面。这时候你对某个Activity定义了android:process。他就运行在另外一个进程了。这时候Application也会重新创建一次,在这个新的进程中。这个Activity也会在这个新的进程中。


而且我们建立的一些实体类对象也是不同进程里面各自产生自己的副本对象。互不关联。


所以我们知道了一些线程同步。单例模式都无效了,因为对象是各个进程中有副本,同步

锁的锁对象都不是同一个对象。当然线程同步机制就失效了。


其中SharePreferences本身是一个文件,所以不受多进程的影响,但是因为SharePreferences不支持多个进程同时执行写操作,所以有可能会导致出现数据丢失等问题。甚至是并发读和写也可能有问题。但是如果你只是一个进程写,一个进程读,而且不是同时,那就问题不大了。


4进程间通信


既然说了多进程,如果我们现在就是二个进程进行通信怎么办。在讲如何通信之前,我们可以先看下相关的基础,那就是序列化及反序列化。


我们看序列化有哪些:



我们可以看到,序列化一般主要是二个,那就是Serialzable和Parcelable。

具体的时候都很简单。下面写大致提下这二个的使用。


Serialzable


User.java  (要传递的实体类)


public class User implements Serializable {
   private static final long serialVersionUID = 512345678910L;
   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;
   }
}


我们只要直接将我们的类实现Serializable接口即可。很简单。这里我提一下serialVersionUID。因为我们平时写都不会写这个。也是正常使用。


但是比如我把这个User对象通过ObjectOutputStream序列化后写到了本地文件,但是这时候我们把我们的User对象里面的属性改了,比如增加了一项:public boolean haha;然后再通过ObjectInputStream去读取出来就会抛异常。


因为反序列化会和序列化时候的serialVersionUID进行比较,如果不同,直接不进行反序列化了,就抛出异常。但是我们不手动写这个值,它会根据当前这个类结构去生成的hash值为值。所以当我们把这个类结构更改后,再去反序列化就报错了。


Parcelable

public class User implements Parcelable {
   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;
   }
   protected User(Parcel in) {
       userId = in.readInt();
       userName = in.readString();
       isMale = in.readInt() == 1;
   }
   public static final Creator<User> CREATOR = new Creator<User>() {
       @Override
       public User createFromParcel(Parcel in) {
           return new User(in);
       }
       @Override
       public User[] newArray(int size) {
           return new User[size];
       }
   };
   @Override
   public int describeContents() {
       return 0;
   }
   @Override
   public void writeToParcel(Parcel dest, int flags) {
       dest.writeInt(userId);
       dest.writeString(userName);
       dest.writeInt(isMale ? 1 : 0);
   }
}


你写的类实现了Parcelable后AS会自动提示,你就按照他的提示生成相应的代码即可。这里我们只要注意这么几个地方:


1.我们在序列化前,总要先把这个类实例化成对象,然后把相应的内容赋值进去是吧,所以上面代码中,我写了个构造函数:

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


这样我们写代码的时候就new User(10,"dyp",true)(当然你也可以写setXXX方法去设置)


2.我们要序列化了,我们把我们的这个类里面的属性值都写进Parcel中,就好比我们是拿了个本子,一行行的记下内容,然后等会一行行的取出来。所以我们看到了。我们是按照顺序先记录下来,所以等会还原的时候也要按顺序取出来相应的值。所以顺序很重要。

@Override
public void writeToParcel(Parcel dest, int flags) {
  //记下userId,因为是Int类型,所以用writeInt
  dest.writeInt(userId);
  //记下userName,因为是String类型,所以用writeString
  dest.writeString(userName);
  /*记下isMale ,因为是Boolean类型,
    但是没有writeBoolean,只有writeBooleanArray,
    所以我们用writeInt()来记录,1是true,0是false。
    额外说下writeBooleanArray内部其实还是用writeInt来记录的。
  */

  dest.writeInt(isMale ? 1 : 0);
}


3.我们最后传到了其他的进程,肯定是要从Parcel里面把我们的对象给还原出来,肯定是先new 一个User对象,然后把各种我们前面第二步保存好的值给它重新赋值。

public static final Creator<User> CREATOR = new Creator<User>() {
   @Override
   public User createFromParcel(Parcel in)
{
       //这里是不是我们先进行了new一个对象,同时把Parcel对象传入。
       return new User(in);
   }
   @Override
   public User[] newArray(int size)
{
       return new User[size];
   }
};


protected User(Parcel in) {
   //然后我们再生成这个对象的同时,再把这个对象的属性都赋值好,切记要按照上面写入的顺序来读取出来赋值。
    userId = in.readInt();
    userName = in.readString();
    isMale = in.readInt() == 1;
}


Binder


序列化的相关的基础讲了。我们来看Binder ,其实吧,Binder我也不知道怎么讲,直接贴别的大佬的相关文章了。


图文详解 Android Binder跨进程通信的原理

https://www.jianshu.com/p/4ee3fd07da14


5进程间通信方式示例



所以我们可以一个个具体来看实现进程间通信方式。


使用Bundle



其实这个我们平时用的很多,

我写个Demo大家就知道了。


MainActivity.java

User user = new User(10,"dongyaoping",true);
Intent intent = new Intent(MainActivity.this,MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable("data",user);
bundle.putInt("int",10);
bundle.putString("string","haha");
intent.putExtras(bundle);
startService(intent);


MyService.java (记得在AndroidManifest.xml中设置android:process属性,让它在另外一个进程)

public class MyService extends Service{
   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       User user = (User) intent.getExtras().getSerializable("data");
       Log.v("dyp","user:"+user.toString());
       return super.onStartCommand(intent, flags, startId);
   }
}


所以我们看到Bundle可以put进去很多东西,因为Bundle本身实现了Parcelable


public final class Bundle
extends BaseBundle
implements Cloneable, Parcelable
{}


使用文件共享



其实这个就更简单了。我们只需要把一个要传的数据写到一个文件,然后在另外一个进程中去读取这个文件就可以了。


一个进程中取写入:


User user = new User(100,"dyp",true);
try {
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path));
    out.writeObject(user);
    out.close();
} catch (Exception e) {
    e.printStackTrace();
}


另外一个进程中取读取:

try {
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path));
    User user = (User ) inputStream.readObject();                  
} catch (Exception e) {
    e.printStackTrace();
}


使用Socket



直接贴上大佬的文章:

Android:这是一份很详细的Socket使用攻略

https://www.jianshu.com/p/089fb79e308b


使用Binder


  1. AIDL

  2. Messenger

  3. ContentProvider



我们主要讲下AIDL,因为Messenger是AIDL的封装,使用起来也更方便。AIDL会了。Messenger也就会使用。ContentProvider的教程就更多了。


说实话平时四大组件中ContentProvider使用的频率很少很少。所以我也不具体写了,网上的教程也很多。


贴上大佬的ContentProvider教程:


  • Android:关于ContentProvider的知识都在这里了!https://www.jianshu.com/p/ea8bc4aaf057


贴上另外大佬的 Messenger的教程:


  • Android 进阶10:进程通信之 Messenger 使用与解析

    http://blog.csdn.net/u011240877/article/details/72836178


我们主要来看AIDL的实现:



具体的细节大家可以看脑图,我就不细说了。


我们可以看到在客户端跨进程访问服务端的时候,我们分了五步。


第一步:创建AIDL文件。



这里我们要注意一点。我们在AS中创建AIDL,直接就可以右键 -->  New --> AIDL即可。这时候会在这个目录下面。



这时候我们会看到这样的界面。

interface IMyAidlInterface {
   String getInfor(String s);
   String getName(char name);
   //传递对象。
   String getBook(in Book book);
}


如果我们要传递一个Book对象,这时候这个Book.java应该是在java包里面,所以我们同时还要再aidl文件夹中创建一个跟这个对象同名的aidl文件。所以变成了这样:



不过一定要切记,整个Book.java和Book.aidl的包名要一样。不然会提示找不到Book这个类。


第二步:声明一个 IBinder 接口实例(或者基于 AIDL 生成)。


然后我们build下之后,AS就会根据我们写的AIDL自动生成一个IBinder文件。(当然如果你第一步不写AIDL,完全自己写一个IBinder文件也是可以的。)



第三步:实现 ServiceConnection


final ServiceConnection connection = new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
     }
     @Override
     public void onServiceDisconnected(ComponentName name) {
     }
};


第四步:调用 Context.bindService(),以传入您的 ServiceConnection 实现。

Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);


第五步:onServiceConnected() 实现中实现相关操作


在你的 onServiceConnected() 实现中,你将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。


final ServiceConnection connection = new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         Log.v("dyp", "已经连接上了");
         IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
         try {
             String haha = iMyAidlInterface.getInfor("hello,我是activity");
             Log.v("dyp", "接受到Service发过来的字符串:" + haha);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
     }
     @Override
     public void onServiceDisconnected(ComponentName name) {
          Log.v("dyp","断开了连接");
     }
};


服务端第一步:实例化 YourInterfaceName.Stub对象


private IBinder binder = new IMyAidlInterface.Stub() {
       @Override
       public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
       }
       @Override
       public String getInfor(String s) throws RemoteException {
           Log.v("dyp","接收到Activity的字符串:"+s);
           return "service传过去的字符串";
       }
       @Override
       public String getBook(Book book) throws RemoteException {
           return null;
       }
       @Override
       public String getName(char name) throws RemoteException {
           return null;
       }
};


服务端第二步:onBind方法中返回上面生成的对象


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


各种通信方式比较


   

该图来源于网络。


今天插入个招聘,百度App Android研发,想要换工作的可以给我发简历(pdf格式),623565791@qq.com,具体要求可以参考(PS:上次发招聘,二群的loader已经加入百度啦):

http://www.wanandroid.com/blog/show/2082



如果你想要跟大家分享你的文章,欢迎投稿~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存