Android——多进程

之前我们了解了 :
-----------------------------------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属性,例如:

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

<activity
    android: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-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
 
    <application
        android:name="com.example.processtest.MyApplication"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android: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>
        
        <service
            android: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";
	@Override
	public 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";
 
	@Override
	protected 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";
 
	@Override
	public void onCreate() {
		Log.i(TAG, "ProcessTestService onCreate");
	}
 
	@Override
	public 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";
 
	@Override
	public 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";
 
	@Override
	protected 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;
 
	@Override
	protected 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";
 
	@Override
	public void onCreate() {
		Log.i(TAG, "ProcessTestService onCreate");
	}
 
	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
 
}

新ProcessTestService :

public class ProcessTestService extends Service {
	public static final String TAG = "viclee";
 
	@Override
	public void onCreate() {
		Log.i(TAG, "ProcessTestService onCreate");
		Log.i(TAG, "ProcessTestActivity.processFlag is " + ProcessTestActivity.processFlag);
	}
 
	@Override
	public 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()
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yawn__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值