IPC进程间通信(基础)

内容来自《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

问题:

由于系统会为每个应用或者说为每个进程 分配 一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,即:在不同的虚拟机中访问同一个类的对象会产生多个副本。导致以下问题:

  1. 静态成员和单例模式完全失效。

    • 创建一个类,内有一个静态成员

      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
      
  2. 线程同步机制完全失效。

    • 原因:不是一块内存了,无论锁对象还是全局类都不能保证线程同步,不同进程锁的不是同一个对象。
  3. SP的可靠性下降。

    • 原因:SP不支持两个进程同时写。
  4. 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跨进程通信的一种方式。

工作原理如下图:

Binder工作机制.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容