app 被强杀后应用崩溃的解决思路

重点内容刚写博客,不是很清楚怎么写,大家见谅,有不清楚的直接qq我,1092417123,欢迎大家一起交流技术

https://github.com/aixiaozi/Fast_Fragment
代码在提交的防止应用被强杀里面,对应用被强杀的情况做了一些封装,防止应用崩溃

1. 应用被强杀的原因

导致应用被强杀的原因有很多,比如:按了 home 键、系统内存不足等等

此外,Android 6.0新特性中,在电源管理中新加了两个特性,导致挂在后台的应用更容易被回收,这就要求我们要注意状态的保存和恢复

应用待机 (App standby)

检测:不充电,且没有直接或间接启动该应用
退出:应用激活,或设备充电时

休眠(Doze)

检测:不充电,且设备静止且灭屏一段时间
特性:让系统进入一个休眠状态,周期性的进入一个窗口恢复正常工作,然后进入更长时间的休眠状态

2. 应用被强杀导致空指针的原因分析

eg:我们在一个类中设置一个 static 变量, HomeActivity 中初始化这个 static 变量,在其它页面(ProfileActivity)调用,这时候应用被强杀,再点击应用直接回到 ProfileActivity,此时由于 这个 static 尚未 初始化,所以会导致空指针,应用崩溃。

Android 机制中 app 被强杀后,app中所有的变量都被清空了,但是唯有Activity的栈信息依然保存着

被强杀后不会重新走WelcomeActivity,而是直接返回ProfileActivity,重新走ProfileActivity 的 onCreate()流程,只是这些Activity重新初始化,包括Application,所以此时引用的变量可能为空,就导致了空指针

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);
        mProfileLabel = (TextView) findViewById(R.id.mProfileLabel);
        mProfileLabel.setText(CustomApplication.mTestNullPointers.toString());
    }

解决方法一:单例(如果为空,就new)

这里写图片描述

当对象为空,就 new 出来,再去拿对象里的东西,可以解决但不建议这么做

3. 解决办法 – 重走应用流程

为什么会出现空指针? -> 应用被强杀 -> 直接让它走应用的流程

if(isForceKilled){
    startActivity(new Intent(this, WelcomeActivity.class));
}

在ProfileActivity 中判断是否被强杀,如果是强杀,重新走应用的流程(实际代码中在 BaseActivity 中去判断),BaseActivity 相关代码如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        switch (AppStatusTracker.getInstance().getAppStatus()) {
            case ConstantValues.STATUS_FORCE_KILLED:
                protectApp();
                break;
            case ConstantValues.STATUS_KICK_OUT:
                kickOut();
                break;
            case ConstantValues.STATUS_LOGOUT:
            case ConstantValues.STATUS_OFFLINE:
            case ConstantValues.STATUS_ONLINE:
                setUpContentView();
                setUpView();
                setUpData(savedInstanceState);
                break;
        }
    }

    protected void protectApp() {
        Intent intent = new Intent(this, HomeActivity.class);
        intent.putExtra(ConstantValues.KEY_HOME_ACTION, ConstantValues.ACTION_RESTART_APP);
        startActivity(intent);
    }

由此,跳转到 HomeworActivity,再在 HomeActivity 里 再进行判断 是否被强杀,HomeAcitivity 相关代码如下(onNewIntent 是因为HomeAcitivty 启动模式为 singleTask):

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        int action = intent.getIntExtra(ConstantValues.KEY_HOME_ACTION, ConstantValues.ACTION_BACK_TO_HOME);
        switch (action) {
            case ConstantValues.ACTION_KICK_OUT:
                break;
            case ConstantValues.ACTION_LOGOUT:
                break;
            case ConstantValues.ACTION_RESTART_APP:
                protectApp();
                break;
            case ConstantValues.ACTION_BACK_TO_HOME:
                break;
        }
    }

    @Override
    protected void protectApp() {
        startActivity(new Intent(this, WelcomeActivity.class));
        finish();
    }

注意:此时重走应用流程,比如说 跳转到 WelcomeActivity 界面,再点返回会回到ProfileActivity,而此时我们需要的是直接退出应用程序

这里写图片描述

对应用的 栈信息分析:首先是 WelcomeActivity,然后自己 finish,然后是 LoginActivity,成功之后 到 HomeActivity,然后点返回键应该回到 桌面。

HomeActivity 启动模式设置为 singleTask,如果现在在ProfileActivity,现在要回到 HomeActivity ,singleTask 就会 直接用栈底的 HomeActivity ,ProfileActivity 和 XxxActivity 会被直接 remove 掉,从栈里清除,

4. 整体思路

应用被强杀,会在BaseAcitivity 中判断,如果是,跳转至HomeActivity,在HomeActivity(singleTask ) 的 onNewIntent 中进行标注,再跳转至 welcomeActivty,整个栈信息就清空了,开始重走整个应用流程

5. demo(粗糙)

在ProfileActivity 中,

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (CustomApplication.mAppStatus == -1) {//被强杀
            Intent intent = new Intent(this, HomeActivity.class);
            intent.putExtra("action", "force_kill");
            startActivity(intent);
        } else {
            setUpData();
        }
    }

在HomeActivity中,多一个onNewIntent

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String action = intent.getStringExtra("action");
        if ("force_kill".equals(action)) {
            protectApp();
        }
    }

    @Override
    protected void protectApp() {
        startActivity(new Intent(this, WelcomeActivity.class));
        finish();
    }

错误示例

这里写图片描述

注意:保留的只有栈信息,实例是没有的,此时还要走 onCreat(),如果在 onCreate() 中调用 static 变量 还是有可能导致空指针,办法:可以在这里也判断一下是否被强杀就可以了

6.判断应用是否是被强杀

可以这样想:

初始值是 -1 ,代表着 isForceKilled(),它就是被强杀掉的,状态可以在 WelcomeActivity 中 onCreate() 中对这个状态进行修改

在CustomApplication中定义一个常量

public static int mAppStatus = -1;

当应用走到 WelcomeActivity 进行修改


public class WelcomeActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        CustomApplication.mAppStatus = 0;
        super.onCreate(savedInstanceState);
    }
    ...
}

在BaseActivity 中进行判断:

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (CustomApplication.mAppStatus == -1) {
            protectApp();
        } else {
            setUpData();
        }
    }
    ...
 }

7. 用到的代码

public class ConstantValues {
    public static final int STATUS_FORCE_KILLED = -1;//被强杀
    public static final int STATUS_LOGOUT = 0;//注销
    public static final int STATUS_OFFLINE = 1;//未登录
    public static final int STATUS_ONLINE = 2;//登陆
    public static final int STATUS_KICK_OUT = 3;//token失效,或账号在另一台设备登陆,被挤下线

    public static final String KEY_HOME_ACTION = "key_home_action";
    public static final int ACTION_BACK_TO_HOME = 0;
    public static final int ACTION_RESTART_APP = 1;
    public static final int ACTION_LOGOUT = 2;
    public static final int ACTION_KICK_OUT = 3;
}
public class AppStatusTracker implements Application.ActivityLifecycleCallbacks{

    private boolean isForground;
    private int activeCount;//计数器,所有的Activity都执行了stop方法,那么count=0,app在后台执行

    private static final long MAX_INTERVAL = 5 * 60* 1000;
    private long timestamp;//在后台呆的时间够长,再切换到前台的时间差,超过最大值显示图形解锁
    private boolean isScreenOff;//图形解锁的特殊需求,屏幕锁住之后再亮起需要图形解锁

    private static AppStatusTracker tracker;
    private Application application;
    private DaemonReceiver receiver;
    private int mAppStatus = ConstantValues.STATUS_FORCE_KILLED;

    private AppStatusTracker(Application application) {
        this.application = application;
        application.registerActivityLifecycleCallbacks(this);//必须注册才能接受到该方法的回调
    }

    public static void init(Application application) {
        tracker = new AppStatusTracker(application);
    }

    public static AppStatusTracker getInstance() {
        return tracker;
    }

    public void setAppStatus(int status) {
        this.mAppStatus = status;
        if (status == ConstantValues.STATUS_ONLINE){
            if (receiver == null){
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intent.ACTION_SCREEN_OFF);
                receiver = new DaemonReceiver();
                application.registerReceiver(receiver,filter);
            }else if (receiver != null){
                application.unregisterReceiver(receiver);
                receiver = null;
            }
        }
    }

    public int getAppStatus() {
        return this.mAppStatus;
    }

    public boolean isForground() {
        return isForground;
    }

    private void onScreenOff(boolean isScreenOff) {
        this.isScreenOff = isScreenOff;
    }

    public boolean checkIfShowGesture(){
        if (mAppStatus == ConstantValues.STATUS_OFFLINE){
            if (isScreenOff){
                return false;
            }
            if (timestamp != 0l && System.currentTimeMillis() - timestamp > MAX_INTERVAL) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        L.e(activity.toString()+" onActivityStopped");
        activeCount --;
        if (activeCount == 0){
            isForground = false;//在后台执行
            timestamp = System.currentTimeMillis();
        }
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        L.e(activity.toString()+" onActivityCreated");
    }

    @Override
    public void onActivityStarted(Activity activity) {
        L.e(activity.toString()+" onActivityStarted");
        activeCount ++;
    }

    //界面要显示时调用此方法,onCreate -- onResume 之间不该写大量代码,
    // 防止初始化时间过长会导致黑屏,
    //一旦此方法回调,就可以确定当前应用是在前台执行
    @Override
    public void onActivityResumed(Activity activity) {
        L.e(activity.toString()+" onActivityResumed");
        //Activity 跳转过程中,onPause -> onResume 需要时间,这过程中算前台?后台?
        isForground = true;
        timestamp = 0l;
        isScreenOff = false;
    }

    @Override
    public void onActivityPaused(Activity activity) {
        L.e(activity.toString()+" onActivityPaused");
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        L.e(activity.toString()+" onActivityDestroyed");
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }

    private class DaemonReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            L.d("onReceive:" + action);
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                AppStatusTracker.getInstance().onScreenOff(true);
            }
        }
    }
}
public abstract class BaseActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener {
    protected Toolbar toolbar;
    protected TextView toolbar_title;
    public static final int MODE_BACK = 0;
    public static final int MODE_DRAWER = 1;
    public static final int MODE_NONE = 2;
    public static final int MODE_HOME = 3;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        switch (AppStatusTracker.getInstance().getAppStatus()) {
            case ConstantValues.STATUS_FORCE_KILLED:
                protectApp();
                break;
            case ConstantValues.STATUS_KICK_OUT:
                kickOut();
                break;
            case ConstantValues.STATUS_LOGOUT:
            case ConstantValues.STATUS_OFFLINE:
            case ConstantValues.STATUS_ONLINE:
                setUpContentView();
                setUpView();
                setUpData();
                break;
        }
    }

    protected abstract void setUpContentView();

    protected abstract void setUpView();

    protected abstract void setUpData();


    protected void protectApp() {
        Intent intent = new Intent(this, HomeActivity.class);
        intent.putExtra(ConstantValues.KEY_HOME_ACTION, ConstantValues.ACTION_RESTART_APP);
        startActivity(intent);
    }

    protected void kickOut() {
//        TODO show dialog to confirm
        Intent intent = new Intent(this, HomeActivity.class);
        intent.putExtra(ConstantValues.KEY_HOME_ACTION, ConstantValues.ACTION_KICK_OUT);
        startActivity(intent);
    }

    @Override
    public void setContentView(int layoutResID) {
        setContentView(layoutResID, -1, -1, MODE_BACK);
    }

    public void setContentView(int layoutResID, int titleResId) {
        setContentView(layoutResID, titleResId, -1, MODE_BACK);
    }

    public void setContentView(int layoutResID, int titleResId, int mode) {
        setContentView(layoutResID, titleResId, -1, mode);
    }

    public void setContentView(int layoutResID, int titleResId, int menuId, int mode) {
        super.setContentView(layoutResID);
        setUpToolbar(titleResId, menuId, mode);
    }

    protected void setUpToolbar(int titleResId, int menuId, int mode) {
        if (mode != MODE_NONE) {
            toolbar = (Toolbar) findViewById(R.id.toolbar);
            toolbar.setTitle("");
            toolbar_title = (TextView) findViewById(R.id.toolbar_title);

            if (mode == MODE_BACK) {
                toolbar.setNavigationIcon(R.drawable.ic_toolbar_back);
            }
            toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onNavigationBtnClicked();
                }
            });

            setUpTitle(titleResId);
            setUpMenu(menuId);
        }
    }

    protected void setUpMenu(int menuId) {
        if (toolbar != null){
            toolbar.getMenu().clear();
            if (menuId > 0) {
                toolbar.inflateMenu(menuId);
                toolbar.setOnMenuItemClickListener(this);
            }
        }
    }

    protected void setUpTitle(int titleResId) {
        if (titleResId > 0 && toolbar_title != null) {
            toolbar_title.setText(titleResId);
        }
    }

    protected void onNavigationBtnClicked() {
        finish();
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        return false;
    }

    @Override
    protected void onStart() {
        if (AppStatusTracker.getInstance().checkIfShowGesture()){
            L.d("need to show gesture");
        }
        super.onStart();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值