Android 中Application是单例,这个问题可能大家会毫不犹豫的回答正确
但是,如果APP中如果有集成一些第三方SDK的
并且在Application中加了打印的可能就会发现,APP启动的时候
怎么onCreate中的打印走了多次
不是说Application只会实例化一次的吗?
因为onCreate走了多次,说明创建了多个
那这个问题答案应该明朗了,在某种情况下,Application不唯一了
那这种情况是什么情况呢?
答案是:多进程
一般我们开发可能极少,除非一些特别的APP,可能我们都不会指定多进程
那为啥集成了第三方SDK会出现这种情况呢
是因为有些SDK指定了组件运行在特别的进程
那为啥第三方SDK会使用多进程?多进程带来的好处是什么?又有什么坏处呢?
进程
Android系统是底层是由Linux改造而来的
进程系统也是一致的,进程,就是程序的具体实现
当程序第一次启动,Android会启动一个Linux进程(具体由Zygote fork出来)和一个主线程
默认的情况下,所有组件都将运行在该进程内
同一个应用由系统分配一个独立的Linux账户,应用的产生的所有进程,都会是这同一个Linux账户
多进程Application会创建多个
很明显带来的问题就是Application的onCreate方法会执行多次
如果在onCreate方法中,做了初始化的操作,将会导致多次初始化操作
如果是启动的时候就执行的,将会导致启动时间延长
将组件指定在单独的进程
指定多进程是在AndroidManifest.xml里面配置
Android四大组件activity,service,provider, receiver
可以通过android:process属性来指定运行所在的进程
不指定默认就是运行在系统分配的进程
也可以修改默认进程 设置Application的android:process属性,来设置所有组件的默认进程
进程名分两种:
- 如果以冒号开头,比如":com.gaode.map"
这种情况下,是该APP私有的,进程名是APP包名+冒号+后面的名字
<activity android:name=".TestActivity"
android:process=":com.gaode.map"/>
- 如果小写字母以:开头 比如"com.baidu.map"
该组件将运行在以这个名字命名的进程中
这种方式就可以让不同应用中的组件可以共享一个进程
<activity android:name=".TestActivity"
android:process="com.baidu.map"/>
示例:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".TestActivity"
android:process="com.baidu.map"/>
Application 完整代码
public class BaseApplication extends Application {
private static final String APP_NAME = "com.qingguoguo.baseapp";
@Override
public void onCreate() {
super.onCreate();
Log.e("BaseApplication", "onCreate");
Log.e("BaseApplication", getProcessNameByPID(getApplicationContext(), android.os.Process.myPid()));
DBConfig.initGreenDao(this);
}
/**
* 判断是否是主进程
* @return
*/
public boolean isAppMainProcess() {
try {
int pid = android.os.Process.myPid();
String process = getProcessNameByPID(getApplicationContext(), pid);
return TextUtils.isEmpty(process) || APP_NAME.equalsIgnoreCase(process);
} catch (Exception e) {
return true;
}
}
/**
* 根据 pid 获取进程名
* @param context
* @param pid
* @return
*/
public String getProcessNameByPID(Context context, int pid) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (manager == null) {
return "";
}
for (android.app.ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
if (processInfo == null) {
continue;
}
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return "";
}
}
在ManinActivity中的点击 事件跳转到TestActivity,将会导致Application再次初始化
底层原理参考:http://gityuan.com/2016/03/26/app-process-create/
如何避免Application多次初始化
上面的代码已经给出了解决方法,就是判断当前进程是不是主进程
若不是可以跳过主进程的初始化
可以参考文章:https://blog.csdn.net/sz_chrome/article/details/72911392
多进程的好处
常驻后台任务应用
核心后台服务模块和其他UI模块进行分离,保证应用能更稳定的提供服务
从而提升用户体验
解决OOM问题
吃内存的大图,WebView等另开进程,避免主进程OOM
多模块开发
诸如下载服务,监控服务等等另开进程
多进程带来的问题
静态变量和单例模式完全失效
因为进程之间,内存是相互独立的,所以VM方法区的静态变量
也都是独立的,单例模式基于静态变量,所以单例也会失效
在两个不同进程访问一个相同类的静态变量,值未必相同
线程同步机制完全失效
Java的同步机制是VM来进行调度的,两个进程拥有两个不同的VM
所以,同步也会在多进程环境下失效,Synchronized,volatile关键字
等都是基于VM级别的同步,所以不要跨进程去使用线程同步,比如
主进程有个生产者,子进程的消费者是无法正常使用消费功能的,
只能通过跨进程通信,让主进程的消费者去消费,然后再回调
Application会多次创建
每个新进程在创建的时候,都会新建一个Application,所以多进程还
会导致Application多次创建的问题,onCreate方法会多次调用,一般
我们都会在onCreate里初始化操作,那么会多次初始化,最好也不要在
Application中设置过多的静态变量,导致内存增加
文件读写并发访问的问题
文件指的泛指所有需要并发访问的文件,例如:本地文件,数据库文件,
sharepreference等。由于Java中,文件锁、队列机制都是VM级别的,
所以不同进程访问同一个文件锁是不管用的。(通过C++可以实现多进程
文件锁机制,不过不在文本讨论范围内。)所以在实际开发过程中,还是
避免多进程同时访问统一文件,多利用Android中IPC的C/S思想,提供服务,
接口调用,避免直接去访问对方进程的文件或者数据库,提升设计美感,
同时也能提升代码的稳定性