Android长时间后台运行,内存被回收再次打开导致APP崩溃(APP在后台被系统回收后,需要重新启动)

问题:

因为手机内存不足的时候,会回收一些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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值