Android多进程(一)—— 开启多进程

2023-11-19

Android多进程:一般情况下,一个应用程序就是一个进程,进程名就是应用程序的包名。进程是系统分配资源的基本单位,每个进程都有自己独立的资源和内存空间。

1 Android开启多进程的原因

  • 单进程分配的内存不够,需要更多的内存。 早期的Android系统只为一个单进程的应用分配了16MB的可用内存,随着手机硬件的提升和Android系统的改进,虽然可分配的内存越来越多,但仍然可以通过开启多进程来获取更多内存来处理自己的APP业务。
  • 进程之间相互监视,如果有进程被杀或者崩溃,另外的进程可以重新启动它
  • 一个进程退出了,另外的进程仍然可以工作,比如说推送服务,只要负责推送消息的进程没有退出,仍然能推送消息

2 开启多进程

AndroidManifest.xml中配置android:process

  • 第一种:如android:process = ":remote",以:开始,后面的字符串是可以随意指定的。如果包名是com.cah.androidtest,所以实际进程名是com.cah.androidtest:remote这种设置形式表示该进程为当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中
  • 第二种:如android:process = "com.cah.androidtest.remote",以小写字母开头,表示运行在一个以这个名字命名的全局进程中,其他应用的组件可以和它跑在同一进程中(使用SharedUID,且签名一致),从而减少资源的占用。

首先在Activity中启动一个服务:

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);

    Intent myServiceIntent = new Intent(MainActivity.this, MyService.class);
    startService(myServiceIntent);
  }
}

public class MyService extends Service {

  private static final String TAG = "MyService";

  @Override
  public void onCreate() {
    Log.e(TAG, "onCreate ");
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(TAG, "onStartCommand: ");
    return START_STICKY;
  }

  @Override
  public void onDestroy() {
    Log.e(TAG, "onDestroy: ");
  }

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

然后在AndroidManifest.xml中配置android:process就可以了:

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

    <application
        android:name=".MyApplication"
        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.AndroidTest">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".MyService"
            android:process=":remote" />
    </application>

</manifest>

查看进程:
查看进程

3 Android IPC通信中的UIDPID识别

3.1 UID

Android上,一个UID标识一个应用程序。应用程序在安装时被分配UID,应用程序在设备上存续期间,UID保持不变。在Linux中的UID是用户的ID,由于Android系统设计之初是单用户系统,UID被赋予新的使命,数据共享。 不同程序如果要相互访问,只能是UID相同才可以,这使得数据共享具有一定的安全性。(不同的程序,还需要拥有相同的签名)

Android系统在Android 4.2开始加入多用户的支持。通常,第一个在系统中注册的用户将默认成为系统管理员。不同用户的设置各不相同,并且不同用户安装的应用以及应用数据也不相同。但是系统中和硬件相关的设置则是共用的,例如,网络设置等。

用户切换后前面用户运行的后台进程还可以继续运行。这样,进行用户切换时无须中断一些后台进行的耗时操作。

3.2 PID

PID即进程ID,一个应用里可以有多个PID。在Android系统中一般不会把已经kill掉的进程ID重新分配给新的进程,新的进程号,一般比之前所有的进程号都要大。

进程com.cah.androidtest

com.cah.androidtest
进程com.cah.androidtest:remote

com.cah.androidtest:remote

进程com.jiandan.jianeryou

com.jiandan.jianeryou

3.3 sharedUserId

Android中每个应用都有唯一的一个UID该应用程序下的资源仅对应用自身可见,如果想要其他应用程序可见,就要使用sharedUserId,这样就可以使两个应用程序公用一个UID

以下是两个单独的应用程序:com.cah.androidtestcom.cah.kotlintext

com.cah.kotlintest

class MainActivity : AppCompatActivity() {

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

        val sp = getSharedPreferences("user", 0)
        sp.edit().putString("name", "Eileen").apply()
    }
}

com.cah.androidtest

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    try {
      Context ct = this.createPackageContext("com.cah.kotlintest", Context.CONTEXT_IGNORE_SECURITY);
      SharedPreferences sp = ct.getSharedPreferences("user", MODE_PRIVATE);
      String name = sp.getString("name", "not get name");
      Log.d("kotlin", "share preference-->" + name); 
      boolean isCommit = sp.edit().putInt("age", 10).commit();
      Log.d("kotlin", "share preference-->" + isCommit);
    } catch (PackageManager.NameNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

// Failed to ensure /data/user/0/com.cah.kotlintest/shared_prefs: mkdir failed: EACCES (Permission denied)
// kotlin: share preference-->not get name
// SharedPreferencesImpl: Couldn't create directory for SharedPreferences file /data/user/0/com.cah.kotlintest/shared_prefs/user.xml
// kotlin: share preference-->false

为两个应用程序的AndroidManifest.xml添加sharedUserId

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cah.kotlintest"
    android:sharedUserId="com.cah.share">
    ...
</manifest>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cah.androidtest"
    android:sharedUserId="com.cah.share">
    ...
</manifest>

此时运行程序会有The application could not be installed: INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,这是由于使用了sharedUserId后,不同的签名造成的。卸载程序,重新安装。

运行:

// kotlin: share preference-->Eileen
// kotlin: share preference-->true

查看程序:

sharedUserId查看进程
sharedUserId查看UID_1
sharedUserId查看UID_2

4 查看Android应用程序内存

为了维持多任务的功能环境,Android为每个进程设置了最大内存(在设备出厂的时候就确定了)。表示堆分配的初始大小,超过这个值就会OOM

可以通过代码获得每个进程可用的最大内存:

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int heapGrowthLimit = am.getMemoryClass(); 

以下是源码:

/**
* Return the approximate per-application memory class of the current
* device.  This gives you an idea of how hard a memory limit you should
* impose on your application to let the overall system work best.  The
* returned value is in megabytes; the baseline Android memory class is
* 16 (which happens to be the Java heap limit of those devices); some
* devices with more memory may return 24 or even higher numbers.
*/
public int getMemoryClass() {
  return staticGetMemoryClass();
}

/** @hide */
@UnsupportedAppUsage
static public int staticGetMemoryClass() {
  // Really brain dead right now -- just take this from the configured
  // vm heap size, and assume it is in megabytes and thus ends with "m".
  String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
  if (vmHeapSize != null && !"".equals(vmHeapSize)) {
    return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
  }
  // 如果不存在dalvik.vm.heapgrowthlimit,则以dalvik.vm.heapsize为准
  return staticGetLargeMemoryClass();
}

/**
* Return the approximate per-application memory class of the current
* device when an application is running with a large heap.  This is the
* space available for memory-intensive applications; most applications
* should not need this amount of memory, and should instead stay with the
* {@link #getMemoryClass()} limit.  The returned value is in megabytes.
* This may be the same size as {@link #getMemoryClass()} on memory
* constrained devices, or it may be significantly larger on devices with
* a large amount of available RAM.
*
* <p>This is the size of the application's Dalvik heap if it has
* specified <code>android:largeHeap="true"</code> in its manifest.
*/
public int getLargeMemoryClass() {
  return staticGetLargeMemoryClass();
}

/** @hide */
static public int staticGetLargeMemoryClass() {
  // Really brain dead right now -- just take this from the configured
  // vm heap size, and assume it is in megabytes and thus ends with "m".
  String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
  return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
}

dalvik.vm.heapgrowthlimit,单个进程可用的最大内存,主要针对的是这个值;dalvik.vm.heapsize,表示不受控情况下的极限堆, 如果要使用这个极限堆,需要在AndroidMainfest中指定:

<application
      android:name=".MyApplication"
      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:largeHeap="true"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/Theme.AndroidTest">
     ....
</application>

或:

$adb shell getprop dalvik.vm.heapgrowthlimit

$adb shell getprop dalvik.vm.heapsize

$adb shell getprop dalvik.vm.heapstartsize

查看堆内存

能够获取极限堆的本意是为了一小部分消耗大量内存的应用,最好不要轻易使用,因为额外的内存会影响系统整体的用户体验,并且会使得GC的运行时间更长。另外,一些要求严格的设备dalvik.vm.heapsizedalvik.vm.heapgrowthlimit可能是一样的。

查看内存使用情况:

堆内存
Native Heap:给Native层分配的内存;Dalvik HeapJava对象在堆中分配的内存

Heap内存有3列,Heap Size—可用最大内存;Heap Alloc—已经分配的内存;Heap Free—剩余可用内存。Heap Size = Heap Alloc + Heap Free。如果Heap Free变得很小,就可能发生OOM

5 开启多进程引出的问题

5.1 使Application运行多次
public class MyApplication extends Application {

  private static final String TAG = "CAH";

  @Override
  public void onCreate() {
    super.onCreate();
    int pid = android.os.Process.myPid();
    Log.e(TAG, "Application.onCreate ==== pid: " + pid);
    String processNameString = "";
    ActivityManager mActivityManager =
      (ActivityManager) this.getSystemService(getApplicationContext().ACTIVITY_SERVICE);
    for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
      Log.e(TAG, "onCreate: appProcess.pid = " + appProcess.pid);
      if (appProcess.pid == pid) {
        processNameString = appProcess.processName;
      }
    }

    if ("com.cah.androidtest".equals(processNameString)) {
      Log.e(TAG, "onCreate: processName = " + processNameString + " ===work");
    } else {
      Log.e(TAG, "onCreate: processName = " + processNameString + " ===work");
    }

  }
}

// Application.onCreate ==== pid: 20747
// onCreate: appProcess.pid = 20747
// onCreate: processName = com.cah.androidtest ===work

// Application.onCreate ==== pid: 20792
// onCreate: appProcess.pid = 20747
// onCreate: appProcess.pid = 20792
// onCreate: processName = com.cah.androidtest:remote ===work

开启的两个进程都会执行onCreate方法。所以在Application中进行初始化操作以及数据的传递操作,都是不合适的,解决的方法就是得到每个进程的名称,如果进程的名称和应用的进程名相同则进行相应的操作,如果不相同则进行其他进程的操作。

5.2 静态成员失效
public class MainActivity extends AppCompatActivity {

    private final static String TAG = "MainActivity";
    public static boolean processFlag = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        processFlag = true;

        Intent service = new Intent(this, MyService.class);
        startService(service);
    }
}

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
        Log.e(TAG, "onCreate: " + MainActivity.processFlag); // false
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        throw null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

设置了android:process属性之后,产生了两个隔离的内存空间,在一个进程的内存空间里修改一个值并不会影响到另外的内存空间。

5.3 文件共享问题

多进程情况下会出现两个进程在同一时刻访问同一文件的情况,这可能会造成资源的竞争。在多线程的情况下,有锁机制控制资源的共享,但是多进程比较难,虽然有文件锁、排队等机制,但是在Android中很难实现。解决方法就是多进程的时候不要同时访问一个文件,选择一个进程进行操作,其他的进程调用操作进程实现。

5.4 断点调试问题

调试就是跟踪程序运行过程中的堆栈消息,由于每个进程都有自己独立的内存空间和各自的对战,无法实现在不同的进程间调试。调试时可以先去掉android:process标签,这样可以保证调试状态下在同一进程中,堆栈信息时连贯的,调试完后,在恢复。

参考

https://www.cnblogs.com/weizhxa/p/8457987.html
https://www.cnblogs.com/helloTerry1987/p/13109971.html
https://blog.csdn.net/weixin_41987588/article/details/82694758
https://blog.csdn.net/c_z_w/article/details/85336283
https://blog.csdn.net/weixin_33762130/article/details/93196657?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-4&spm=1001.2101.3001.4242
https://blog.csdn.net/skyxiaojt/article/details/80002913?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-5&spm=1001.2101.3001.4242
https://www.cnblogs.com/mythou/p/3258715.html
https://www.cnblogs.com/helloTerry1987/p/13109971.html
https://xuexuan.blog.csdn.net/article/details/52328928

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android多进程(一)—— 开启多进程 的相关文章

随机推荐

  • 剑指 Offer 18. 删除链表的节点 -- 双指针

    0 题目描述 leetcode原题链接 剑指 Offer 18 删除链表的节点 1 双指针解法 删除值为 val 的节点分需为两步 定位节点 修改引用 定位节点 遍历链表 直到 head val val 时跳出 即可定位目标节点 修改引用
  • 荐读

    本文转自 链闻 ChainNews 作者 Karen 虽然区块链技术诞生至今不过短短十余年 但是东西方天然的文化差异在加密世界中同样留下了一些痕迹 当社交媒体上关于 东方区块链 只关心币价 利益和投机而只有 西方区块链 才关心底层技术的革新
  • “元宇宙”既是机遇也是挑战

    一次性看懂元宇宙 是开疆拓土还是新一轮割韭菜 众所周知 随着AI VR AR MR XR 区块链 云计算 物联网 数字孪生 量子技术 5G技术等新兴技术的快速发展 推动刚刚过去的2021年成为元宇宙元年 先是3月Robolox上市 开启了元
  • 基于Dpabi的功能连接

    1 预处理 这里预处理用Gretna软件进行 共分为以下几步 1 DICOM转NIfTI格式 2 去除前10个时间点 Remove first 10 times points 由于机器刚启动 被试刚躺进去也还需适应环境 导致刚开始扫描的数据
  • 分享一个开源免费、功能强大的视频播放器库

    99 的前端开发者都关注了这个公众号 点击上方 前端开发博客 关注并 设为星标 回复加群 自助秒进前端群 最近在开发一个前端项目 用到播放视频的功能 所以就查了下有什么前端的视频播放器库可以使用 今天来分享一下给大家 这个库的名字叫做 Pl
  • Zabbix之自定义监控MySQL主从状态和延迟

    zabbix之自定义监控MySQL主从状态和延迟 文章目录 监控MySQL主从状态 1 安装Mariadb配置主从 2 配置监控脚本 3 web界面添加监控项 4 添加触发器 5 触发验证 监控MySQL主从延迟 配置监控脚本 web界面添
  • 机器学习-knn近邻分类算法

    算法原理 本质是通过距离 欧式距离 判断两个样本是否相似 如果距离够近就认为他们足够相似属于同一类别 算法优缺点 主要参数k 标记数据周围几个数作为参考对象 需要根据数据来决定 k值越大 模型偏差大 对噪声数据不敏感 可能造成欠拟合 k值越
  • 若依文件下载

    若依文件下载 都看我的 2021 4 17 找了一圈 每一个写的简单的 还得自己完成 提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 前台代码 二 加入js代码 总结 前言 提示 若依框架的文件下载 提
  • css 让内容可滑动,css实现隐藏滚动条并可以滚动内容

    代码预览 行走在光阴里的人 谁不对初见怀揣一份美好向往和期待 谁不对初见心存一份眷恋和不舍 假如人生是一场途经 初见一定是人生路上最美的绽放 人生在世 不管你是青丝如云 还是白发如霜 当你念及 人生若只如初见 时 你的嘴角一定会不由自主地泛
  • ChatGTP套壳网站总结更新

    总结一批ChatGTP套壳网站供大家学习参考 前10个网站经过测试可用 所以套壳网站 就是使用ChatGPT提供的API与ChatGPT系统连接 使用自己的网站来实现交互展示 效果与在ChatpGPT网站上聊天是一样的 ChatGTP套壳网
  • HTC-VIVE手柄使用代码

    using UnityEngine using System Collections 检测手柄功能的脚本 这个脚本挂到手柄上 controler right 和controler left 上 public class ButtonTouc
  • java包装类&简单认识泛型

    1 包装类 在 Java 中 由于基本类型不是继承自 Object 为了在泛型代码中可以支持基本类型 Java 给每个基本类型都对应了一个包装 类型 类中比如由属性 方法 使用比较方便 1 1 基本数据类型和对应的包装类 1 2 装箱和拆箱
  • vtk使用之Mapper和actor的关联

    参考博客 VTK的Mapper Dezeming的博客 CSDN博客 vtk mapper VTK 图形进阶 vtkPolyData数据生成与显示 简 单的博客 CSDN博客 vtkpolydata 类vtkMapper及其派生类 把输入的
  • 引入微信支付Java SDK WxPayAPI_JAVA.zip

    最近需要接入微信支付 百度了很多博客 关键第一步导入微信支付提供的官方sdk就卡住了 那些博客上也没说怎么导入 以前没整过sdk 一下懵了 后来发现WxPayAPI JAVA zip解压出来的文件是个maven项目 然后直接IDEA打开这个
  • Java基础知识总结(三)

    java的代码块分类 局部代码块 比较简单 在局部位置 方法定义中 定义的 作用 限定某个变量的生命周期 构造代码块 在类的成员位置 作用 在执行构造方法之前 如果存在构造代码块 优先执行构造代码块 可以将构造方法中共性内容 放在构造代码中
  • [渗透]CVE-2020-1938/CNVD-2020-10487:Apache Tomcat AJP连接器远程执行代码漏洞

    受影响版本 Apache Tomcat 6 Apache Tomcat 7x lt 7 0 100 Apache Tomcat 8x lt 8 5 51 Apache Tomcat 9x lt 9 0 31 未受影响版本 Tomcat 7
  • 设计模式(十)装饰器模式

    装饰器模式是一种非常有用的结构型模式 它允许我们在不改变类的结果的情况下 为类添加新的功能 我们来举例说明一下 首先添加一组形状 它们都实现了形状接口 public interface Shape String getShape class
  • QT使用emit时发生内存泄露

    1 场景 在QT里面使用多线程进行编程时 子线程执行的函数里面使用了emit发生了内存泄露 2 主要原因 在使用子线程时 线程使用了join 来等待子线程完成 这样使用emit也不会发送信号 因为join 是阻塞的 必须等待当前线程完成 3
  • iOS逆向工程之App脱壳

    本篇博客以微信为例 给微信脱壳 砸壳 在iOS逆向工程中是经常做的一件事情 因为从AppStore直接下载安装的App是加壳的 其实就是经过加密的 这个 砸壳 的过程就是一个解密的过程 未砸壳的App是无法在Class dump Hoppe
  • Android多进程(一)—— 开启多进程

    Android多进程 一般情况下 一个应用程序就是一个进程 进程名就是应用程序的包名 进程是系统分配资源的基本单位 每个进程都有自己独立的资源和内存空间 1 Android开启多进程的原因 单进程分配的内存不够 需要更多的内存 早期的And