Android 跨进程通信大总结

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/111553746
本文出自【赵彦军的博客】

1、Android进程

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.zhaoyanjun"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }
}    

applicationId 为:com.zhaoyanjun,那项目运行起来,所有的 activityservice 默认运行在 com.zhaoyanjun 进程.

工具类:

package com.zhaoyanjun;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author yanjun.zhao
 * @time 2020/12/16 11:01 AM
 * @desc
 */
public class Process {

    /**
     * 获取当前进程名
     * @return
     */
    public static String getCurrentProcessName() {
        FileInputStream in = null;
        try {
            String fn = "/proc/self/cmdline";
            in = new FileInputStream(fn);
            byte[] buffer = new byte[256];
            int len = 0;
            int b;
            while ((b = in.read()) > 0 && len < buffer.length) {
                buffer[len++] = (byte) b;
            }
            if (len > 0) {
                String s = new String(buffer, 0, len, "UTF-8");
                return s;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

2、修改Android默认进程

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhaoyanjun">

    <application
        android:process=":appProcess"
        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/Theme.RecyclerViewDemo">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>
</manifest>

在 application 标签下添加 android:process 属性可以修改Android默认的进程名字。进程的名字为:com.zhaoyanjun:appProcess , 这个修改是全局的,所有 activity 都默认运行在这个进程中。

3、指定activity、Service进程

activity 指定进程

<activity
    android:process=":remote"
    android:name=".MainActivity2">
</activity>

在这里插入图片描述
service 指定进程

<service
    android:process=":remote_service"
    android:name=".LibraryService">
</service>

在这里插入图片描述

4、activity进程间通信

MainActivity有一个 button 按钮,点击跳转到 MainActivity2

//MainActivity代码
findViewById<Button>(R.id.bt1).setOnClickListener {
      var intent = Intent(this, MainActivity2::class.java)
      intent.putExtra("key", "value1")
      startActivity(intent)
}

MainActivity2 指定进程

<activity
    android:process=":remote"
    android:name=".MainActivity2">
</activity>

MainActivity2 逻辑

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main2)

    var value = intent.getStringExtra("key")
    Log.d("zhaoyanjun", "MainActivity2 进程: ${Process.getCurrentProcessName()} 获取值: $value")
}

日志:

D/zhaoyanjun: MainActivity2 进程: com.zhaoyanjun:remote 获取值: value1

5、AIDL 简介

AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:

1、AIDL文件以 .aidl 为后缀名

2、 AIDL支持的数据类型分为如下几种:

  • 八种基本数据类型:bytecharshortintlongfloatdoubleboolean
    StringCharSequence
  • 实现了Parcelable接口的数据类型
  • List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
  • Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象

3、AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值。

3、定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。

  • 其中 in 表示数据只能由客户端流向服务端,
  • out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。定向Tag具体的使用差别后边会有介绍

4、明确导包。在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下

6、activity、service 进程间通信 AIDL

创建 IBookAidlInterface.aidl文件 在这里插入图片描述
创建 IBookAidlInterface 完成后,会在 main 目录下,创建 aidl 文件夹,并且会创建同名的包名,如下:
在这里插入图片描述
我们把默认的 basicTypes 方法删除,修改如下

interface IBookAidlInterface {
    String getTitle();
    void setTitle(String title);
}

下面创建 Service , 并指定进程名字:

 <service
     android:name=".MyService"
     android:enabled="true"
     android:exported="true"
     android:process=":remote" />

MyService 如下:

class MyService : Service() {

    private var binder: Binder = object : IBookAidlInterface.Stub() {

        override fun getTitle(): String {
            return "aidl->title"
        }

        override fun setTitle(title: String?) {
            Log.d("aidl", "service-setTitle:$title")
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    override fun onCreate() {
        super.onCreate()
    }
}

MainActivity 绑定Service

class MainActivity : AppCompatActivity() {

    private var iBookAidlInterface: IBookAidlInterface? = null

    private var serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {

        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            iBookAidlInterface = IBookAidlInterface.Stub.asInterface(service)
            var name = iBookAidlInterface?.title
            Log.d("aidl", "getTitle: $name")
            iBookAidlInterface?.title = "我是一个title"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bindService()
    }

    /**
     * 绑定服务
     */
    private fun bindService() {
        var intent = Intent(this, MyService::class.java)
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(serviceConnection)
    }
}

运行项目,日志如下:
在这里插入图片描述
在这里插入图片描述
可以看到,子进程 Service 和 主进程的 Activity 已经可以通信了。
github 地址:https://github.com/zyj1609wz/AndroidMultiProgress

7、AIDL 传递复杂对象

首先创建 Book 类,并且实现 Parcelable 接口。这里要注意,一定要实现 Parcelable 接口,因为只有实现 Parcelable 的复杂对象才能在 AIDL 中传递。

public class Book implements Parcelable {

    public String title;

    Book() {

    }

    protected Book(Parcel in) {
        title = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(title);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    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];
        }
    };
}

同时在 aidl 目录中定义 Book.aidl 文件,并且声明 parcelable 属性。Book.aidl文件如下:

package com.yanjun.progress;

parcelable Book;

下面我们修改 IBookAidlInterface.aidl 的方法,如下:

interface IBookAidlInterface {
     String getTitle();
     void setTitle(String title);
     Book getBook();
     void setBook(in Book book);
}

需要注意的是 setBook方法,一定要声明 Book 对象是 in属性,表明是 book 对象是从客户端流向服务端。

最后在 Service 里面实现接口就行了,如下:

class MyService : Service() {

    private var binder: Binder = object : IBookAidlInterface.Stub() {

        override fun getTitle(): String {
            return "aidl->title"
        }

        override fun setTitle(title: String?) {
            Log.d("aidl", "service-setTitle:$title")
        }

        override fun getBook(): Book {
            var book = Book()
            book.title = "书的名字"
            return book
        }

        override fun setBook(book: Book?) {

        }
    }

    override fun onBind(intent: Intent): IBinder ?{
        return binder
    }

    override fun onCreate() {
        super.onCreate()
    }
}

demo github 地址:https://github.com/zyj1609wz/AndroidMultiProgress

8、SharedPreferences 跨进程

SharedPreferences要实现跨进程通信,要遵循文件名字相同的规则。而且是同一个应用,两个进程间。
SharedPreferences之所以能实现跨进程,是因为SharedPreferences 数据存在SD卡的磁盘中,两个进程共用一个文件。

注意事项:

  • 虽然 SharedPreferences 可以实现跨进程,但是 Google 官方不建议使用,因为 Google 认为多个进程读同一个文件都是不安全的,Android 不保证该模式总是能正确的工作,建议使用 ContentProvider 替代多进程之间文件的共享。

额外话题 SharedPreferences 优化:

  • commit 是把内容同步提交到硬盘的,返回 boolean 表明修改是否提交成功
  • apply先立即把修改提交到内存,然后开启一个异步的线程提交到硬盘,并且如果提交失败,你不会收到任何通知,apply没有返回值 。
  • 使用了apply方式异步写sp的时候,每次apply()调用都会新增一个finisher。在有些系统生命周期事件发生的时候都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程,造成 ANR
  • 当一个文件较大时,首次读取可能会较慢,每次写入也会较慢
  • 在不同模块使用多个文件存储的情况下,那么未被使用到的模块,不会被读取进内存
  • 当存储的valuejson或者html形式时,由于特殊符号较多,会占用更多的字符

如何解决 ANR :

  • 如果处理 SP的时候,使用commit方法替代apply,在保证调用时线程正确处理,并保证同一文件不使用多个线程写入的情况下,不会出现该ANR
  • 自查是否有一下子修改多个key-valueapply多次的情况,如有,可合并为put多次但apply一次

9、广播跨进程

Context.sendBroadcast()
发送的是普通广播,所有订阅者都有机会获得并进行处理

10、ContentProvider跨进程

什么是ContentProvider ?

即 内容提供者,是 Android 四大组件之一

ContentProvider什么作用?

进程间 进行数据交互 & 共享,即跨进程通信

在这里插入图片描述

ContentProvider原理

ContentProvider的底层原理 = Android中的Binder机制

ContentProvider优秀文章链接

本来是想总结一下的,但是我发现了一篇写的很好的文章:
建议把文章和评论都看看,很有收获。

Android:关于ContentProvider的知识都在这里了!
Android中使用Contentprovider导致进程被杀死

11、Socket跨进程通信

优点:

  • 功能强大,可通过网络传输字节流,支持一对多实时并发通信

缺点:

  • 实现细节步骤稍繁琐,不支持直接的RPC

适用场景:

  • 网络间的数据交换

12、文件共享跨进程

优点

  • 简单易用

缺点:

  • 不适用高并发场景,并且无法做到进程间即时通信

适用场景:

  • 适用于无关发的情况下,交换简单的数据,对实时性要求不高的场景。

13、Bundle跨进程

优点

  • 简单易用

缺点:

  • 只能传输Bundle支持的数据类型

适用场景:

  • 四大组件间的进程间通信

14、MMKV

MMKV是国内微信团队开源的高性能 key-value组件,良好的支持Android跨进程通信,具体使用方法就自己看文档吧,这里不赘述了。

github: https://github.com/Tencent/MMKV/
中文文档:MMKV中文文档

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值