这可能是最全的Android:Process (进程)讲解了

来源:互联网 发布:java连接池原理 编辑:程序博客网 时间:2024/05/22 08:15

官方是这样描述的:

Tools for managing OS processes.

管理操作系统进程的工具类。

下面就来详细介绍下关于Process的点滴:

概述

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

各类组件元素的清单文件条目activity、service、receiver 和 provider均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。

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

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

决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。(引自官方文档)

多进程的应用场景

推荐看:
http://blog.spinytech.com/2016/11/17/android_multiple_process_usage_scenario/
对多进程的应用场景写的非常详细。

1.在新进程中开启服务;
2.多模块应用。
多模块应用:
比如我做的应用大而全,里面肯定会有很多模块,假如有地图模块、大图浏览、自定义WebView等等(这些都是吃内存大户),还会有一些诸如下载服务,监控服务等等,一个成熟的应用一定是多模块化的。

首先多进程开发能为应用解决了OOM问题,Android对内存的限制是针对于进程的,这个阈值可以是48M、24M、16M等,视机型而定,所以,当我们需要加载大图之类的操作,可以在新的进程中去执行,避免主进程OOM。

多进程不光解决OOM问题,还能更有效、合理的利用内存。我们可以在适当的时候生成新的进程,在不需要的时候及时杀掉,合理分配,提升用户体验。减少系统被杀掉的风险。

多进程还能带来一个好处就是,单一进程崩溃并不影响整体应用的使用。例如我在图片浏览进程打开了一个过大的图片,java heap 申请内存失败,但是不影响我主进程的使用,而且,还能通过监控进程,将这个错误上报给系统,告知他在什么机型、环境下、产生了什么样的Bug,提升用户体验。

再一个好处就是,当我们的应用开发越来越大,模块越来越多,团队规模也越来越大,协作开发也是个很麻烦的事情。项目解耦,模块化,是这阶段的目标。通过模块解耦,开辟新的进程,独立的JVM,来达到数据解耦目的。模块之间互不干预,团队并行开发,责任分工也明确。

开启进程的方法

1.我们通常会使用修改清单文件的android:process来达到多进程的目的。如果android:process的value值以冒号开头的话,那么该进程就是私有进程,如果是以其他字符开头,那么就是公有进程,这样拥有相同 ShareUID 的不同应用可以跑在同一进程里。
2.通过JNI利用C/C++,调用fork()方法来生成子进程,一般开发者会利用这种方法来做一些daemon(守护进程)进程,来实现防杀保活等效果。

ps:ShareUID :
ShareUserId,在Android里面每个app都有一个唯一的linux user ID,则这样权限就被设置成该应用程序的文件只对该用户可见,只对该应用程序自身可见,而我们可以使他们对其他的应用程序可见,这会使我们用到SharedUserId,也就是让两个apk使用相同的userID,这样它们就可以看到对方的文件。为了节省资源,具有相同ID的apk也可以在相同的linux进程中进行(注意,并不是一定要在一个进程里面运行),共享一个虚拟机。
ShareUserId的作用,数据共享、调用其他程序资源。

进程生命周期与优先级

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程):

1.前台进程:(foreground process)
用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

2.可见进程:
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

3.服务进程
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

4.后台进程:
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。

5.空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

进程间通信

Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。 这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。

要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

组件中的Process

前面已经提到了,我们可以在任意组件和application中定义process属性,我们先看看官方文档怎么说:

android:process
The name of the process where the service is to run. Normally, all components of an application run in the default process created for the application. It has the same name as the application package. The element’s process attribute can set a different default for all components. But component can override the default with its own process attribute, allowing you to spread your application across multiple processes.
If the name assigned to this attribute begins with a colon (‘:’), a new process, private to the application, is created when it’s needed and the service runs in that process. If the process name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage.

我英文才过四级,翻译的不好请多指教:
中文:这个进程的名字就是正在运行的服务所在的进程,通常来说,所有组件和应用在默认的进程中运行,也就是应用包名,在application中应用此属性,将会为所有的组件开启一个不同的进程,但是组件能够覆盖application中的进程,允许你应用在跨进程通信。
如果process属性以:开头(:simon),那么将在需要的时候和服务需要运行在另外一个进程的时候开启一个属于此应用的私有进程,如果以小写字母开头(com.simon),(不能以数字开头,并且要符合命名规范,必须要有.否则将会出现这种错误: Invalid process name simon in package com.wind.check: must have at least one ‘.’)服务将在以这个名字命名的全局进程中,如果这是被允许的话。这个将允许组件在不同的应用中共享同一个进程,减少资源占用。

最后再讲一个process的性能优化

由多进程引起的application实例化多次

设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个进程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。
我们去看老罗的blog:Android系统在新进程中启动自定义服务过程(startService)的原理分析

我们可以看到step17:

这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

public final class ActivityThread {      ......      private final void handleCreateService(CreateServiceData data) {          // If we are getting ready to gc after going to the background, well          // we are back active so skip it.          unscheduleGcIdler();          LoadedApk packageInfo = getPackageInfoNoCheck(              data.info.applicationInfo);          Service service = null;          try {              java.lang.ClassLoader cl = packageInfo.getClassLoader();              service = (Service) cl.loadClass(data.info.name).newInstance();          } catch (Exception e) {              if (!mInstrumentation.onException(service, e)) {                  throw new RuntimeException(                      "Unable to instantiate service " + data.info.name                      + ": " + e.toString(), e);              }          }          try {              if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);              ContextImpl context = new ContextImpl();              context.init(packageInfo, null, this);              Application app = packageInfo.makeApplication(false, mInstrumentation);              context.setOuterContext(service);              service.attach(context, this, data.info.name, data.token, app,                  ActivityManagerNative.getDefault());              service.onCreate();              mServices.put(data.token, service);              try {                  ActivityManagerNative.getDefault().serviceDoneExecuting(                      data.token, 0, 0, 0);              } catch (RemoteException e) {                  // nothing to do.              }          } catch (Exception e) {              if (!mInstrumentation.onException(service, e)) {                  throw new RuntimeException(                      "Unable to create service " + data.info.name                      + ": " + e.toString(), e);              }          }      }      ......  }  

我们可以看到这段代码:
Application app = packageInfo.makeApplication(false, mInstrumentation);
可以知道在这里创建了Application。

需要注意的是:
1.不管是以:开头的还是以字母开头的进程,也就是无论是全局的进程还是私有的进程,只要是新建了一个进程,都会调用onCreate()方法,另外只要进程不被杀死,就不会再调用onCreate()方法了,亲测。

下面让我们来看看启动多个进程调用application的onCreate()方法:

这里写图片描述

这种多少会给应用带来影响的,下面给出解决方案:

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

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

下面贴出处理后的结果:

这里写图片描述

只调用了一次application的onCreate方法。

Process方法和属性

推荐大家去官方文档看:
https://developer.android.com/reference/android/os/Process.html

最后如果有什么不足的地方还请大家指正。

给出官方文档传送门:
https://developer.android.com/about/versions/nougat/index.html?q=process&hl=zh-cn

2 0