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

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


版权声明:本文为xingyu19911016原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。