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>

查看进程:
查看进程

这里选择 remote 是主观的,也可以命名其他的名字。冒号 “:” 代替当前应用的包名,所以 MyService 运行在名为 “com.cah.androidtest:remote” 的进程中。也可以设置 android:process=“com.cah.androidtest.remote”,这样 MyService 运行在名为 com.cah.androidtest.remote 的进程中。

这两种命名方式也是有区别的,如果被设置的进程名是以一个冒号开头的,则这个新的进程对于这个应用来说是私有的,其他应用的组件不能和新的进程运行在同一进程中,当新的进程被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以小写字母开头的,则这个服务将运行在一个以这个名字命名的全局进程中,当然前提是它有相应的权限,将允许不同应用中的组件运行在同一进程中,从而减少资源的占用。

3 Android IPC 通信中的 UID 和 PID 识别

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.androidtest 和 com.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.heapsize 和dalvik.vm.heapgrowthlimit 可能是一样的。

查看内存使用情况:

堆内存

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

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

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值