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
通信中的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:remote
:
进程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
查看程序:
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