问题:
因为手机内存不足的时候,会回收一些APP所占内存,如果被回收的APP中有全局变量,那么再次进入这个APP就会导致异常报错;
原因:
因为Application是全局变量,内存不足的时候会被回收,这个时候如果不是重启而是恢复之前页面,系统会重新new一个Application,所以没有回收之前保存在Application中的信息都将没有了,所以回收之前保存在Application中的信息都将没有了,这个时候被恢复的页面就可能因为需要Application中的信息报NullPointerException错;
这说明:系统回收APP后,再次进入此软件,Android只是恢复这个应用,并不是重启,他会创建一个新的Application对象并且启动上次用户离开时的activity以造成这个APP从来没有被kill掉的假象;
什么是应用程序?
Application和Activity,Service一样是android框架的一个系统组件,当android程序启动时系统会创建一个Application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如何创建自己的Application,也可以简单创建一个类继承Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的Application名字注入即可)。
android系统会为每个程序运行创建一个Application类对象且仅创建一个,所有Application可以说是单例(singleton)模式的一个类,且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局单例的,所以不同Activity,Service中获取的对象都是同一个对象。所以通过Application来进行一些数据传输,数据共享,数据缓存等操作。
解决办法:
看了大多数都是onSaveInstanceState和OnRestoreInstanceState来保存UI状态的,基本上就是在按下Home键或者其他情况的时候存储数据,然后再次打开APP的时候读取bundle的数据,因为一般项目都有多个Activity,这样做比较麻烦。
这里将将一个简单粗暴的方法:Activity加载布局之前判断当前程序是否被系统回收,如何是则重新启动APP或者重新初始化全局变量;
具体代码如下:
1.创建一个类保存APP是否被回收的两个静态变量;
public class AppStatusConstant {
public static final int STATUS_FORCE_KILLED = -1; //应用放在后台被强杀了
public static final int STATUS_NORMAL = 1; //APP正常态
}
2.创建AppStatus的管理类,并初始APPStatus(APP状态)的值为被系统回收状态;
public class AppStatusManager implements Application.ActivityLifecycleCallbacks {
//默认被初始化状态,被系统回收(强杀)状态
public int mAppStatus = AppStatusConstant.STATUS_FORCE_KILLED;;
public static AppStatusManager mAppStatusManager;
private Application application;
//是否前台
private boolean isForground;
//Activity运行个数
private int activeCount;
private AppStatusManager(Application application){
this.application = application;
application.registerActivityLifecycleCallbacks(this);
}
public static void init(Application application){
if(mAppStatusManager == null){
mAppStatusManager = new AppStatusManager(application);
}
}
public static AppStatusManager getInstance(){
return mAppStatusManager;
}
/**
* 获取APP状态
* @return
*/
public int getAppStatus(){
return mAppStatus;
}
/**
* 设置APP状态
* @param appStatus
*/
public void setAppStatus(int appStatus){
this.mAppStatus = appStatus;
}
/**
* 是否前台显示
* @return
*/
public boolean isForground(){
return isForground;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
activeCount++;
}
@Override
public void onActivityResumed(Activity activity) {
isForground = true;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
activeCount--;
if(activeCount == 0){
isForground = false;
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
3.创建一个BaseActivity继承AppCompatActivity,在onCreate方法中判断是否重启
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//校验APP状态
validateAppStatus();
}
private void validateAppStatus(){
int appStatus = AppStatusManager.getInstance().getAppStatus();
if(appStatus == AppStatusConstant.STATUS_FORCE_KILLED){
//异常退出
protectApp();
}else if(appStatus == AppStatusConstant.STATUS_NORMAL){
//不需要处理或者初始方法调用
}
}
private void protectApp(){
//第一种被回收,跳转到启动页面
Intent intent = new Intent(this, SplashActivity.class);
startActivity(intent);
finish();
return;
}
4.项目中的其他Activity继承BaseActivity
public class TestActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
5.最后就是启动页面了,在页面跳转前将APP状态调为正常
public class SplashActivity extends AppCompatActivity {
private ImageView sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.startup);
sp=(ImageView) findViewById(R.id.imageView);
sp.postDelayed(new Runnable() {
@Override
public void run() {
//页面跳转
Intent intent = new Intent(SplashActivity.this, AppMainActivity.class);
//app状态改为正常
AppStatusManager.getInstance().setAppStatus(AppStatus.STATUS_NORMAL);
startActivity(intent);
finish();
}
}, 2500);
}
}
6.MainActivity中manifest配置启动默认singletask;
<activity
android:name=".ui.activity.AppMainActivity"
android:exported="false"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan|stateHidden"
android:theme="@style/app_appcompat_theme_white">
</activity>
可能您还不明白为什么这么处理?为什么只设置APP的正常状态,就能起到重启的效果?
在手机内存不足时,会回收APP内存,同时会回收全局变量,这时STATUS_NORMAL和STATUS_FORCE_KILLED两个变量的值也会被回收,AppStatusManager中的mAppStatus变量也会被回收;再次恢复页面的时候这些全局变量都会重建,此时app的状态就变成被回收了,在恢复页面的时候判断不通过便会重新启动软件了;
补充说明:
1)MainActivity设置为singletask,当启动MainActivity时,会把MainActivity任务栈上的其他Activity销毁,内存回收,重启APP顺序,SplashActivity(初始化数据)->MainActivity(若任务栈上有其他Activity会被销毁),例如第一启动APP时任务栈时MainActivity(singletask模式)->BActivity->CActivity->....,内存不足重启APP时候,打开启动页SplashActivity(重启初始化数据及全局变量)->MainActivity(singletask模式)(->BActivity->CActivity->....其他将被销毁)
2)也可以采用清空任务栈,重启新的任务栈这种方式
将下面的代码
private void protectApp(){
//第一种被回收,跳转到启动页面
Intent intent = new Intent(this, SplashActivity.class);
startActivity(intent);
finish();
return;
}
替换为如下代码
/**
* 内存不足回收,重新启动APP
*/
public void restartApp(Context context){
ActivityTack.getInstance().exit();
Class aClass = null;
try {
aClass = Class.forName("cn.gome.hotpot.launch.SplashActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Intent intent = new Intent(context, aClass);
//清空任务栈,重启新的任务栈
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
3)不启动SplashActivity,监测内存回收异常启动,重新打开APP,在BaseActivity中的onCreate方法中优先初始化全局变量和用户信息,在当前Activity页面就可以重新加载数据;
例如第一次启动A->B->C->D,退出后台,内存回收收回APP所占内存,重新打开APP时候系统A->B->C->D的Activity顺序系统是记录,我们在可以在DActivity中重新初始化全局变量和用户信息;
private void protectApp(){
//设置默认状态
AppStatusManager.getInstance().setAppStatus(AppStatusConstant.STATUS_NORMAL);
//初始化全局变量,用户信息等
GlobalConfig.getInstance().initInfo(this);
}
实际上Activity和组件已经实现onSaveInstanceState方法,非必要可以不需要重写onSaveInstanceState,看实际情况,基础的信息会重新再Bundle拿到;此方法根据实际情况使用;
模拟:
terminal application
kill掉进程
1、 按Home按键退出你的程序;
2、
# 找到该APP的进程ID
adb shell ps
# 找到你APP的报名
adb shell ps | grep your.app.package
# 按照上述命令操作后,看起来是这样子的:
# USER PID PPID VSIZE RSS WCHAN PC NAME
# u0_a198 21997 160 827940 22064 ffffffff 00000000 S your.app.package
# 通过PID将你的APP杀掉
adb shell kill 21997
# APP现在被杀掉啦
不保留活动
android 系统在开发者选项中,有一个不保留活动的选项,它有以下三种作用。
保留app内存数据(全局的单例等),
保留activity堆栈数据,
再次开启app,会调用onCreate()重新创建栈顶的activity;
参考:
https://blog.csdn.net/na2609613672/article/details/90640951
https://xuexuan.blog.csdn.net/article/details/79191853
https://blog.csdn.net/qq_14876133/article/details/82801579
————————————————
版权声明:本文为CSDN博主「mayundoyouknow」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ahou2468/article/details/118943134