之前我们了解了 :
-----------------------------------Java——多线程浅析.
-----------------------------------Android——Handler详解
-----------------------------------Android——HandlerThread浅析
-----------------------------------Java——ThreadPool线程池

让我们继续看看Android多进程:

1. 概述

默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不应改变这一点。但是,如果您发现需要控制某个组件所属的进程,则可在清单文件(AndroidMenifest)中执行此操作。

主要依靠android:process属性

  • 各类组件元素activity、service、receiver和 provider的清单文件条目均支持 android:process 属性,此属性可指定该组件应在哪个进程中运行。

  • 可以设置android:process属性,使每个组件均在各自的进程中运行,或者使某些组件共享一个进程,而其他组件则不共享

  • 也可设置 android:process,以便不同应用的组件在同一进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。

  • 此外,application 元素还支持 android:process 属性,用来设置适用于所有组件的默认值。

    • application类继承自 ContextWarpper 类,代表应用程序(即 Android App)的类,也属于Android中的一个系统组件
    • 每个Android App运行时,会首先自动创建Application 类并实例化 Application 对象,且只有一个。即 Application类 是单例模式(singleton)类
    • 也可通过 继承 Application 类自定义Application 类和实例
    • 即不同的组件(如Activity、Service)都可获得Application对象且都是同一个对象
    • Application 对象的生命周期是整个程序中最长的,即等于Android App的生命周期

当内存不足,而其他更急于为用户提供服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某个进程。正因如此,系统会销毁在被终止进程中运行的应用组件。当这些组件需再次运行时,系统将为其重启进程。

2. 开启多进程模式

正常情况下,在android中多进程是指一个应用中存在多个进程的情况。在Android中使用多进程只有一种方法,就是在AndroidMenifest中指定android:process属性。如下所示:

<activity android:name=".FirstActivity"android:process=":remote"/>
<activity android:name=".SecondActivity"android:process="com.zchhh.LaunchMode:remote"/>

其中FirstActivity和SecondActivity分别指定了process属性,并且它们的属性值不同,这意味着当前又增加了两个新进程。

  • 假设当前包名为com.zchhh.LaunchMode,当FirstActivity启动时,系统会为它单独创建一个进程,进程名为"com.zchhh.LaunchMode:remote";
  • 当SecondActivity启动时,系统也会为它单独创建一个进程,进程名com.zchhh.LaunchMode.remote。

我们注意到FirstActivity和SecondActivity的android:process属性分别为:remote和com.zchhh.LaunchMode.remote,它们有什么区别呢?

首先:

  • :指要在当前进程名前面加上包名,这是一种简写方法
  • 对于FirstActivity来说它完整的进程名为com.zchhh.LaunchMode:remote
  • 对于SecondActivity中的声明方式,是一种完整的声明方式,不会附加包名

其次: 私有进程和全局进程

  • 进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程当中
  • 不以“:”开头的进程属于全局进程,其他应用通过shareUID方式可以和它跑在同一个进程中
  • Android会为每个应用分配唯一的一个UID,具有相同UID的应用才能共享数据。

【注】:

  • (1)私有进程:android:process=":remote",以冒号开头,冒号后面的字符串原则上是可以随意指定的。如果我们的包名为“com.biyou.multiprocess”,则实际的进程名 为“com.biyou.multiprocess:remote”。这种设置形式表示该进程为当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
  • (2)全局进程:进程名称不以“:”开头的进程都可以叫全局进程,如android:process=“com.secondProcess”,以小写字母开头,表示运行在一个以这个名字命名的全局进程中,其他应用通过设置相同的ShareUID可以和它跑在同一个进程。
  • (3)两个应用通过ShareUID运行在同一个进程中是有要求的,就是需要两个应用有相同的UID并且签名也要相同。

3. 多进程模式的运行机制

多进程运行起来并不单单指android:process属性即可,例如当我们创建SecondActivity时,它会运行在一个单独的进程中,android会为每一个应用或者进程分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致在不同虚拟机中访问同一个类的对象会产生多个副本。

所有运行在不同进程中的四大组件,当它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。正常情况下,四大组件也要通过一些中间层来共享数据,但是像这种简单地通过指定进程名来开启多线程都会无法正确运行。一般地,使用多进程会造成如下几方面的影响:

注意:

  • (1) 静态成员和单例模式完全失效。

  • (2) 线程同步机制完全消失:因为不是同一个内存,那么无论是锁对象还是全局类都无法保证线程同步,因为不同进程锁的不是同一个对象。

  • (3) SharePreference可靠性下降:SharePreference不支持两个进程同时执行写操作,因为会导致数据丢失,因为SharedPreferences底层是通过读写XML文件实现的,并发写显然会出现问题,甚至读/写多可能出问题。

  • (4) Application会多次创建:系统在创建进程的同时分配独立的虚拟机(即代表会多次创建Application),当一个组件运行在一个新的进程中,由于 系统要创建新的进程同时分配独立的虚拟机,因此就是一个启动应用的过程。既然重新启动则会创建新的Application。

  • (5)sharepreference数据可能会有误差(sharepreference不允许两个线程同时执行操作,其底层是通过XML文件读写完成)

IPC机制的介绍看这篇博文:Android——进程间通信方式.

4.进程生命周期与优先级

在大多数情况下,每个 Android 应用都在各自的 Linux 进程中运行。当需要运行应用的一些代码时,系统会为应用创建此进程,并使其保持运行,直到不再需要它且系统需要回收其内存以供其他应用使用。

应用进程的生命周期并不由应用本身直接控制,而是由系统综合多种因素来确定的,比如系统所知道的正在运行的应用部分、这些内容对用户的重要程度,以及系统中可用的总内存量。这是 Android 非常独特的一个基本功能。

应用开发者必须了解不同的应用组件(特别是 Activity、Service 和 BroadcastReceiver)对应用进程的生命周期有何影响。这些组件使用不当会导致系统在应用进程正执行重要任务时将它终止。

进程生命周期错误的一个常见示例:

  • 当 BroadcastReceiver 在其 BroadcastReceiver.onReceive() 方法中接收到一个 Intent 时,它会启动一个线程,然后从该函数返回
  • 一旦返回,则系统会认为 BroadcastReceiver 不再处于活动状态,因此不再需要其托管进程(除非其中有其他应用组件处于活动状态)
  • 因此,系统可能会随时终止进程以回收内存,这样会终止在进程中运行的衍生线程
  • 要解决这个问题,通常可以从 BroadcastReceiver 调度 JobService,这样系统就知道进程中还有处于活动状态的任务正在进行中。

为了确定在内存不足时应该终止哪些进程,Android 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):

看这篇博文:Android——进程优先级.

5.注意

再强调一遍使用方法:

Android中开启多进程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性,例如:

<serviceandroid:name=".MyService"android:process=":remote">
</service><activityandroid:name=".MyActivity"android:process="com.shh.ipctest.remote2">
</activity>
  • :remote:以:开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.shh.ipctest:remote,同时以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
  • com.shh.ipctest.remote2:这是完整的命名方式,为全局进程,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。

几个注意的点:

5.1 Application的多次重建

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.processtest"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="19" /><applicationandroid:name="com.example.processtest.MyApplication"android:icon="@drawable/ic_launcher"android:label="@string/app_name"><activityandroid:name=".ProcessTestActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><serviceandroid:name=".ProcessTestService"android:process=":remote"></service></application></manifest>

定义两个类:ProcessTestActivity和ProcessTestService,只是在Activity的onCreate方法中直接启动了该Service,同时,我们自定义了自己的Application类:

MyApplication :

public class MyApplication extends Application {public static final String TAG = "viclee";@Overridepublic void onCreate() {super.onCreate();int pid = android.os.Process.myPid();Log.d(TAG, "MyApplication onCreate");Log.d(TAG, "MyApplication pid is " + pid);}
}

ProcessTestActivity :

public class ProcessTestActivity extends Activity {public final static String TAG = "viclee";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_process_test);Log.i(TAG, "ProcessTestActivity onCreate");this.startService(new Intent(this, ProcessTestService.class));}
}

ProcessTestService :

public class ProcessTestService extends Service {public static final String TAG = "viclee";@Overridepublic void onCreate() {Log.i(TAG, "ProcessTestService onCreate");}@Overridepublic IBinder onBind(Intent arg0) {return null;}}

执行上面这段代码,查看打印信息:

MyApplication onCreate
MyApplication pid is 6371
ProcessTestActivity onCreate
MyApplication onCreate
MyApplication pid is 6399
ProcessTestService onCreate

MyApplication的onCreate方法调用了两次,分别是在启动ProcessTestActivity和ProcessTestService的时候,且pid也不相同

  • Application的onCreate方法中做一些全局的初始化操作,它被初始化多次是完全没有必要的
  • 出现这种情况,是由于即使是通过指定process属性启动新进程的情况下,系统也会新建一个独立的虚拟机,自然需要重新初始化一遍Application

怎么来解决这个问题呢?

思路1: 通过在自定义的Application中通过进程名来区分当前是哪个进程,然后单独进行相应的逻辑处理。

public class MyApplication extends Application {public static final String TAG = "viclee";@Overridepublic void onCreate() {super.onCreate();int pid = android.os.Process.myPid();Log.d(TAG, "MyApplication onCreate");Log.d(TAG, "MyApplication pid is " + pid);ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();if (runningApps != null && !runningApps.isEmpty()) {for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {if (procInfo.pid == pid) {if (procInfo.processName.equals("com.example.processtest")) {Log.d(TAG, "process name is " + procInfo.processName);} else if (procInfo.processName.equals("com.example.processtest:remote")) {Log.d(TAG, "process name is " + procInfo.processName);}}}}}
}

运行之后,查看Log信息,

MyApplication onCreate
MyApplication pid is 23918
process name is com.example.processtest
ProcessTestActivity onCreate
MyApplication onCreate
MyApplication pid is 6399
process name is com.example.processtest:remote
ProcessTestService onCreate

不同的进程执行了不同的代码逻辑,可以通过这种方式来区分不同的进程需要完成的初始化工作

思路2: 判断是否为主进程,只有主进程的时候才执行下面的操作

String processName = this.getProcessName();//判断进程名,保证只有主进程运行
if (!TextUtils.isEmpty(processName) &&processName.equals(this.getPackageName())) {//在这里进行主进程初始化逻辑操作                          Log.i(">>>>>>","oncreate");
}//获取进程名的方法,这个方法效率是比较好的:public static String getProcessName() {try {File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));String processName = mBufferedReader.readLine().trim();mBufferedReader.close();return processName;} catch (Exception e) {e.printStackTrace();return null;}}

5.2 静态成员的失效

将之前定义的Activity和Service的代码进行简单的修改,代码如下:
ProcessTestActivity :

public class ProcessTestActivity extends Activity {public final static String TAG = "viclee";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_process_test);Log.i(TAG, "ProcessTestActivity onCreate");this.startService(new Intent(this, ProcessTestService.class));}
}

新ProcessTestActivity :

public class ProcessTestActivity extends Activity {public final static String TAG = "viclee";public static boolean processFlag = false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_process_test);processFlag = true;Log.i(TAG, "ProcessTestActivity onCreate");this.startService(new Intent(this, ProcessTestService.class));}
}

ProcessTestService :

public class ProcessTestService extends Service {public static final String TAG = "viclee";@Overridepublic void onCreate() {Log.i(TAG, "ProcessTestService onCreate");}@Overridepublic IBinder onBind(Intent arg0) {return null;}}

新ProcessTestService :

public class ProcessTestService extends Service {public static final String TAG = "viclee";@Overridepublic void onCreate() {Log.i(TAG, "ProcessTestService onCreate");Log.i(TAG, "ProcessTestActivity.processFlag is " + ProcessTestActivity.processFlag);}@Overridepublic IBinder onBind(Intent arg0) {return null;}}

重新执行代码,打印Log

MyApplication onCreate
MyApplication pid is 18580
process name is com.example.processtest
ProcessTestActivity onCreate
MyApplication onCreate
MyApplication pid is 6399
process name is com.example.processtest:remote
ProcessTestService onCreate
ProcessTestActivity.processFlag is flase

在Activity中定义了一个标志processFlag并在onCreate中修改了它的值为true,然后启动Service,但是在Service中读到这个值却为false

照正常的逻辑,静态变量是可以在应用的所有地方共享的,但是设置了process属性后,产生了两个隔离的内存空间,一个内存空间里值的修改并不会影响到另外一个内存空间。

5.3 文件共享问题

多进程情况下会出现两个进程在同一时刻访问同一个数据库文件的情况。这就可能造成
 - 资源的竞争访问,导致诸如数据库损坏、数据丢失等。在多线程的情况下我们有锁机制控制资源的共享
 但是在多进程中比较难,虽然有文件锁、排队等机制,但是在Android里很难实现。解决办法就是多进程的时候不并发访问同一个文件,比如子进程涉及到操作数据库,就可以考虑调用主进程进行数据库的操作。

IPC机制的介绍看这篇博文:Android——进程间通信方式.

5.4 断点调试问题

多进程情况下,可以通过【Attach to Process】来绑定辅助进程进行调试

  • 但这样有一个问题就是,无法对辅助进程的onCreate方法进行调试
  • 只有在进程启动后才可以绑定进程进行调试,而辅助进程启动时执行onCreate方法只是一瞬间的事情
  • 等手动绑定完进程后,onCreate方法一般都已经执行完毕了,所以没法对onCreate方法进行调试

解决方法:

  • 在onCreate方法的首行加入以下代码即可
  • Debug.waitForDebugger方法会让手机进程保持阻塞状态,直到连上调试器后,才会继续执行后续的代码
Debug.waitForDebugger()

Android——多进程相关推荐

  1. Android多进程实现,一个APP多个进程

    相关文章: Android IPC机制(一)开启多进程(文章1:刘望舒大神的文章,1.3w阅读量,20赞) Android IPC机制(二)用Messenger进行进程间通信(文章2:刘望舒大神的文章 ...

  2. Android 多进程编程 15问15答!

    ps:阅读本文 需要对android 多进程编程有一定了解. 1.Android中总共有几种方式进行IPC? 答:一共有两种,一种是binder 还有一种是socket.Binder 大家用的比较多. ...

  3. Android IPC机制之IPC概念、Android 多进程和相关基础知识

    1.IPC 的基本概念 1.1 IPC的使用场景 IPC 即 Inter-Process Communication 进程间通信,IPC用于多进程,而Android的多进程情况一般有: 1.一个应用需 ...

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

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

  5. Android 多进程的利弊分析

    多进程的使用方法: Android多进程概念:一般情况下,一个应用程序就是一个进程,这个进程名称就是应用程序包名.我们知道进程是系统分配资源和调度的基本单位,所以每个进程都有自己独立的资源和内存空间, ...

  6. [Android]多进程知识点详解

    作者:Android开发_Hua 链接:https://www.jianshu.com/p/e0f833151f66 多进程知识点汇总: 一:了解多进程 二:项目中多进程的实现 三:多进程的优缺点与使 ...

  7. Android 多进程调试技巧

    之前开发多进程的应用,想要调试Application 里面的代码,一直调试不了代码. 因为是多进程,Application 会执行两次. 原因: 比如我们有两个进程,一个叫进程a, 一个叫进程b. 因 ...

  8. Android 多进程开发

    前言 正常情况下,一个apk启动后只会运行在一个进程中,其进程名为AndroidManifest.xml文件中指定的应用包名,所有的基本组件都会在这个进程中运行.但是如果需要将某些组件(如Servic ...

  9. android 程序 共享文件,026 Android多进程-文件共享

    一.前言 前面几节已经讲了,基本数据及可序列化数据在进程间是如何传送的,也就是Messenger把装有Bundle的Message发送到别的进程,这样就完成进程间的通信了. 那么,还有其他的方式实现进 ...

最新文章

  1. tar自动打包指定文件夹中的文件到指定目录
  2. mysql dba系统学习(16)mysql的mysqldump备份 mysql dba系统学习(17)mysql的备份和恢复的完整实践
  3. 部署DNS服务和管理DNS
  4. mysql忘记密码的处理方法
  5. 广外计算机考研专业课,【广外考研论坛】 21广外各专业考研问题全解答!纯干货!...
  6. spring框架mvc框架_Spring的MVC测试框架入门–第1部分
  7. 抽象类继承多个抽象类_多重继承?抽象类?C++的内存布局并不复杂
  8. 【javascript】对原型对象、原型链的理解
  9. Orcale的存储过程
  10. 蓝牙学习笔记(二)——低功耗蓝牙(BLE)的体系结构
  11. 文件上传漏洞及其绕过
  12. MCSE 與 MCSA 升級 Windows Server 2008 之路
  13. php流量统计代码_用php编写的简单的网站流量统计程序
  14. Civil 3d-快速转换C3D图形为CAD图形
  15. Respon.WriteFile 下载文件
  16. 思科和H3C交换机单端口环路解决方案
  17. 海尔微型计算机云悦t3G276ia,没了海尔云悦miniA 迷你主机界尽失半壁江山
  18. 解决usr/bin/ld: cannot find -lxxx问题
  19. 网络安全需要看什么书?(网安工程师)?
  20. “美亚杯”第三届中国电子数据取证大赛答案解析(个人赛)

热门文章

  1. TM1638驱动数码管的一点建议,附程序
  2. TM1638快速开发教程(基于正点原子mini板stm32f103rc)
  3. 通达信标记符号_通达信指标中赋值符号“:”、“=”、“:=”区别?
  4. 网络技术这十个术语你知道吗?
  5. Django之强大后台xadmin的使用(一)
  6. android 布局 字体大小,移动端页面布局及字体大小该如何设置
  7. [Music]30首经典萨克斯名曲
  8. 优链时代荣获“2022 数字技术应用创新奖”|2022世界数字经济大会
  9. 如何进行直播代码编写,怎样生成直播代码
  10. Unity 多机器的视频不卡帧同步