内容来自《Android开发艺术探索》一书。
1、Android 中开启多进程 和 多进程导致的问题
开启:
- 一个应用对应至少一个进程,两个应用间通信就属于进程间通信。
- 一个应用可以有多个进程,一个应用使用多进程的唯一方式:给四大组件指定android:process属性。
<activity android:name=".Main3Activity"
android:process="com.lying.ms_ipc.remote3"/>
<activity android:name=".Main2Activity"
android:process=":remote2"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Main2Activity 和 Main3Activity 使用了两种命名方式。Main2Activity使用的 : 表示前面加上包名,Main3Activity使用的是全路径名称。
基于不同的命名方式,区别如下:以 : 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。不是以 : 开头的,属于全局进程,其他应用可以通过ShareUID方式和它跑在同一个进程。
注:系统会为每个应用分配一个唯一的UID,具有相同的UID才能共享数据。-
例:启动应用,首先会启动MainActivity,然后启动Main2Activity,然后启动Main3Activity。通过命令查看已启动的进程。
查看命令:adb shell ps 或者 adb shell ps | findstr 包名
注:Windows中使用 findstr,Linux中使用 grep。
E:\AndroidProject\MS_IPC>adb shell ps | findstr com.lying.ms_ipc
u0_a140 16567 1778 1870128 116828 0 0 S com.lying.ms_ipc
u0_a140 16614 1778 1867788 109388 0 0 S com.lying.ms_ipc:remote2
u0_a140 16656 1778 1893812 112740 0 0 S com.lying.ms_ipc.remote3
问题:
由于系统会为每个应用或者说为每个进程 分配 一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,即:在不同的虚拟机中访问同一个类的对象会产生多个副本。导致以下问题:
-
静态成员和单例模式完全失效。
-
创建一个类,内有一个静态成员
public class UserManager { public static int userId = 1; }
-
在MainActivity的onCreate中修改 userId 的值为 666,然后启动 Main2Activity,在Main2Activity中打印userId。
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); UserManager.userId = 666; } }
public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); Log.i(TAG, "onCreate: UserManager userId = " + UserManager.userId); } }
-
查看打印值,发现Main2Activity中拿到的不是修改后的值,而是修改前的。
I/Main2Activity: onCreate: UserManager userId = 1
-
-
线程同步机制完全失效。
- 原因:不是一块内存了,无论锁对象还是全局类都不能保证线程同步,不同进程锁的不是同一个对象。
-
SP的可靠性下降。
- 原因:SP不支持两个进程同时写。
-
Application会多次创建。
原因:一个组件跑在新的进程中,系统要分配独立的虚拟机,这个过程其实就是启动一个应用过程。
-
举例:自定义一个Application
public class MyApplication extends Application { private static final String TAG = "MyApplication"; @RequiresApi(api = Build.VERSION_CODES.P) @Override public void onCreate() { super.onCreate(); String processName = Application.getProcessName(); Log.i(TAG, "onCreate: MyApplication OnCreate processName = " + processName); } }
在AndroidManifest文件中的 application 指定 android:name=".MyApplication"
-
启动MainActivity,再启动Main2Activity
I/MyApplication: onCreate: MyApplication OnCreate processName = com.lying.ms_ipc I/MyApplication: onCreate: MyApplication OnCreate processName = com.lying.ms_ipc:remote2
看日志,Application的onCreate执行了两次。
2、序列化
序列化和反序列化概念:将实体转换为二进制流,然后在另一个地方将二进制流再反序列化成实体对象。序列化前 和 反序列化后的对象属于两个不同的对象,只是其中的内容相同,因为内容相同所以我们 IPC 可以使用序列化方式来进行数据的传递。
两种序列化方式的选择:Serializable开销大,Parcelable效率高。但Parcelable主要用在内存方面,序列化到存储设备或网络传输使用Serializable会好点。
2.1、通过 Serializable 接口进行序列化
serializable 是 java 提供的序列化接口。
(1)需要支持序列化的类 实现 Serializable 接口。
-
(2)在类中指明 serialVersionUID
private static final long serialVersionUID = 8736492374937894L
-
(3)序列化过程
User user = new User(0, "jake", ture); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt")); out.writeObject(user); out.close();
-
(4)反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt")); User newUser = (User)in.readObject(); in.close();
注意:serialVersionUID 是在序列化和反序列化中判断是不是正确的当前类。若不指明,默认为哈希值,当类结构有变化时,哈希值会改变,这时就会导致反序列化失败。所以最好指明 serialVersionUID;
2.2、通过 Parcelable 接口进行序列化
Parcelable 是Android 中的序列化方式
(1)需要序列化的类实现 Parcelable 接口
(2)实现 writeToParcel
(3)实现CREATOR
-
举例如下:
public class Book implements Parcelable { public int bookId; public String bookName; public Book(int bookId, String bookName){ this.bookId = bookId; this.bookName = bookName; } /** * @return 当前对象的内容描述。含有文件描述符返回 1,否则返回 0.几乎所有情况都是返回0. */ @Override public int describeContents() { return 0; } /** * 序列化 */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } /** * 反序列化 */ public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel parcel){ bookId = parcel.readInt(); bookName = parcel.readString(); } @NonNull @Override public String toString() { return "bookId = " + bookId + ", bookName = " + bookName; } }
注意:在举例中,Book中都是基本类型和String,如果其中还有个可序列化的对象 Library library;那么在 Book(Parcel parcel) 方法中需要增加这个
//是可序列化对象时,需要传入一个当前线程的上下文类加载器
library = parcel.readParcelable(Thread.currentThread().getContextClassLoader());
3、Binder
Binder是Android跨进程通信的一种方式。
工作原理如下图: