解决android应用被强杀或应用被回收导致的空指针问题

Posted lvzishen123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决android应用被强杀或应用被回收导致的空指针问题相关的知识,希望对你有一定的参考价值。

1.问题是如何发生的,会在什么情况下发生此类问题?

当用户运用手机清理助手或后台回收我们的应用造成我们应用程序进程被杀死的时候就有可能出现这种空指针的问题,下面举个例子我们一起来看看这种情况是如何发生的。

如图所示我们新建一个程序Demo,程序中有三个Activity,分别为SplashActivity MainActivity InfoActivity,下面我们简称这三个Activity为A B C.这三个Activity也是模拟我们平时项目的进入流程,SplashActivity也就是我们的欢迎页面,当此Activity显示完后我们让它finish掉。MainActivity是我们的主页面,通常这个页面启动模式都是设置成SINGLETASK的,不明白为什么设置成这个模式的同学可以去百度一下Activity的启动模式,而InfoActivity则是我们的二级页面。
这里写图片描述

如上图我们自定义一个application类,大家都知道这个类是在整个程序启动时最先被加载的,同时我们在这个里边定义一个集合list。
看到这里我们可能会疑惑了,为什么要定义这么一个集合呢??
答:这个集合是为了方便我们模拟出空指针异常出现而准备的,当然这么写只是方便模拟,大家在日常开发中有可能会因为其他原因而出现此类问题。
接下来我们看看这个集合的初始化。如图:可以看到这个集合是在mainactivity中被初始化的时候new出来的,同时我们给赋一个值。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyApplication.list=new ArrayList<>();
        MyApplication.list.add("我从MAINACTIVITY来");
        Button button= (Button) findViewById(R.id.activity_main_btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,InfoActivity.class));
            }
        });
    }
}

当我们点击按钮时跳到了InfoActivity中,再让我们看看InfoActivity中是如何处理的。

public class InfoActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_info);
        TextView textView= (TextView) findViewById(R.id.text);
        textView.setText(MyApplication.list.get(0));
    }
}

很简单只是将控件赋值了。

好,让我们看看整个流程。
这里写图片描述

哈哈,很简单吧!!接下来让我们模拟一下异常的发生!我们将应用放到InfoActivity页面,同时摁下home键将程序放到后台,然后用android studio强制结束我们的进程,再点开应用我们一起来看看会发生什么。
这里写图片描述
当我们再点开应用的时候便会发现!咦!发生异常了!!
这里写图片描述
让我们来分析一下为什么会出现这种情况,显而易见是因为给textview赋值时集合为空而导致的,而我们的集合是在当MainActivity中new并赋值的。当我们的进程被强杀或者被回收的时候,Android系统虽然让你的进程没有了,但是此进程中Activity中栈的信息还是存在的,也就是说此时当你点开此应用的时候程序中的Activity栈还是存在着B→C这两个Activity的,只不过Activity中的数据都没有了,需要重新创建新的Activity数据,也就是重新走生命周期了,当我们的InfoActivity重新走到14行时,因为MainActivity中的数据没有初始化(只有当我们回退键退到MainActivity才会走生命周期),而这个集合是在MainActivity中被初始化的,所以这个集合肯定为空。这也就导致了空指针异常的发生。

2.解决问题
其实思路很简单,既然我们的进程都被回收了,再打开的时候直接让应用强制走启动流程就可以了,也就是说让我们的应用重新走A→B→C这个流程就可以了!
第一步:我们需要新建一个单例类,android中单例类是与我们整个应用的生命周期挂钩的,也就是说当我们应用进程没了以后单例类也没有了,同时单例类不会因为被GC回收掉,所以不用担心出现被回收的问题,如下:

/**
 * Created by lvzishen on 2016/5/5.
 *
 * APP状态跟踪器常量码
 */
public class AppStatusConstant {
    public static final int STATUS_FORCE_KILLED=-1; //应用放在后台被强杀了
    public static final int STATUS_KICK_OUT=1;//TOKEN失效或者被踢下线
    public static final int STATUS_NORMAL=2;  //APP正常态
//    public static final int STATUS_LOGOUT=2;//用户注销登录
//    public static final int STATUS_OFFLINE=3;//未登录状态
//    public static final int STATUS_ONLINE=4;//登录状态
    //intent到MainActivity 区分跳转目的
    public static final String KEY_HOME_ACTION="key_home_action";//返回到主页面
    public static final int ACTION_BACK_TO_HOME=6; //默认值
    public static final int ACTION_RESTART_APP=9;//被强杀
    public static final int ACTION_KICK_OUT=10;//被踢出
}
public class AppStatusManager {

    public int appStatus= AppStatusConstant.STATUS_FORCE_KILLED;        //APP状态 初始值为没启动 不在前台状态

    public static AppStatusManager appStatusManager;

    private AppStatusManager() {

    }


    public static AppStatusManager getInstance() {
        if (appStatusManager == null) {
            appStatusManager = new AppStatusManager();
        }
        return appStatusManager;
    }

    public int getAppStatus() {
        return appStatus;
    }

    public void setAppStatus(int appStatus) {
        this.appStatus = appStatus;
    }
}

如上所示,我们新建了一个App状态常量类用于方便统一参数,同时新建了一个AppStatus单例类,这个单例类中定义了一个int类型的参数,参数默认值为-1,也就是默认为进程关闭的状态,而这个值是什么时候被改变的呢,很简单在SpalshActivity的onCreate方法被改变就可以啦!在SpalshActivity中改成正常态2即可。

public class SplashActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AppStatusManager.getInstance().setAppStatus(AppStatusConstant.STATUS_NORMAL); //进入应用初始化设置成未登录状态
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        handler.sendEmptyMessageDelayed(0, 3000);

    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            startActivity(new Intent(SplashActivity.this, MainActivity.class));
            finish();
        }
    };
}

当我们的停留在某个页面的时候应用被回收统一跳转到MainActivity即可,因为MainActivity为SINGLETASK启动模式,所以此时Activity栈中就只剩MainActivity一个Activity,在MainActivity中finish掉自己的同时在跳转SplashActivity即可完成,这样也就强制APP重新走了启动流程,为了统一管理,我们新建一个BaseActivity,其他Activty继承这个BaseActivity,改造后的代码如下:

/**
 * Created by lvzishen on 2016/5/5.
 */
public abstract class BaseActivity extends AppCompatActivity {
    /**
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        switch (AppStatusManager.getInstance().getAppStatus()) {
            /**
             * 应用被强杀
             */
            case AppStatusConstant.STATUS_FORCE_KILLED:
                //跳到主页,主页lauchmode SINGLETASK
                protectApp();
                break;
            /**
             * 用户被踢或者TOKEN失效
             */
            case AppStatusConstant.STATUS_KICK_OUT:
                //弹出对话框,点击之后跳到主页,清除用户信息,运行退出登录逻辑
//                Intent intent=new Intent(this,MainActivity.class);
//                startActivity(intent);
                break;
            case AppStatusConstant.STATUS_NORMAL:
                setUpContentView();
                setUpView();
                setUpData(savedInstanceState);
                break;
        }


    }

    /**
     * 加载布局
     */
    protected abstract void setUpContentView();

    /**
     * view初始化
     */
    protected abstract void setUpView();

    /**
     * 加载数据
     */
    protected abstract void setUpData(Bundle savedInstanceState);

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

    @Override
    public void onBackPressed() {
        // 返回默认结束当前页面
        finish();
    }


}
public class SplashActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AppStatusManager.getInstance().setAppStatus(AppStatusConstant.STATUS_NORMAL); //进入应用初始化设置成未登录状态
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void setUpContentView() {
        setContentView(R.layout.activity_splash);
    }

    @Override
    protected void setUpView() {

    }

    @Override
    protected void setUpData(Bundle savedInstanceState) {
        handler.sendEmptyMessageDelayed(0, 3000);
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            startActivity(new Intent(SplashActivity.this, MainActivity.class));
            finish();
        }
    };
}
public class MainActivity extends BaseActivity {
    Button button;
    @Override
    protected void setUpContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void setUpView() {
        button= (Button) findViewById(R.id.activity_main_btn);
    }

    @Override
    protected void setUpData(Bundle savedInstanceState) {
        MyApplication.list=new ArrayList<>();
        MyApplication.list.add("我从MAINACTIVITY来");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, InfoActivity.class));
            }
        });
    }


    @Override
    protected void protectApp() {
     Toast.makeText(getApplicationContext(),"应用被回收重启走流程",Toast.LENGTH_LONG).show();
        startActivity(new Intent(this, SplashActivity.class));
        finish();
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        int action = intent.getIntExtra(AppStatusConstant.KEY_HOME_ACTION, AppStatusConstant.ACTION_BACK_TO_HOME);
        switch (action) {
            case AppStatusConstant.ACTION_RESTART_APP:
                protectApp();
                break;
            case AppStatusConstant.ACTION_KICK_OUT:
                break;
            case AppStatusConstant.ACTION_BACK_TO_HOME:
                break;
        }
    }

}
public class InfoActivity extends BaseActivity {
    TextView textView;

    @Override
    protected void setUpContentView() {
        setContentView(R.layout.activity_info);
    }

    @Override
    protected void setUpView() {
        TextView textView= (TextView) findViewById(R.id.text);
    }

    @Override
    protected void setUpData(Bundle savedInstanceState) {
        textView.setText(MyApplication.list.get(0));
    }
}

**如上代码所示,我们在SpalshActivity中将状态改变为正常态,同时在BaseActivity中判断APP状态,只要进程被回收AppStatusManager则会被重新创建,int Status因为没有走SplashActivity所以还是为默认值-1,在BaseActivity oncreate方法中判断int status后则会走protectApp()这个方法(正常态不会走),在MainActivity中重写这个方法跳转到SpalshActivity同时finish自己即可完成整个流程!我们用这个思路也可以处理诸如单点登录下线等问题!让我们看看改造以后的效果
这里写图片描述

我们将页面停留在MainActivity中同时强制关闭进程,可以看到再打开时重新走了应用流程,同时吐司出现,说明应用是走了MainActivity的protectApp方法的。好了,就讲到这里,说的不对的地方请多提,谢谢阅读!!**
源码下载地址: https://github.com/lvzishen/AppTracker

以上是关于解决android应用被强杀或应用被回收导致的空指针问题的主要内容,如果未能解决你的问题,请参考以下文章

应用被强杀了怎么办

应用被强杀了怎么办

Android 常见内存泄漏的解决方式

常见的内存泄漏原因及解决方法

浅谈BaseActivity写法,促使我们更高效开发

2023年Android黑科技保活方案,应用永生,拒绝强制杀死 最高适配Android 13 小米 华为 Oppo vivo 等最新机型 拒绝强杀 开机自启动 附demo apk 附研究资料