关键词:强杀 / home 键 / static 导致的 NullPointerException / BaseActivity
背景:Android 编程中我们经常会使用到 static 变量,static 变量属于类本身,所有实例调用的静态变量的值都是一样的,如果在某一个类里改变了一个静态变量的值,其它所有的实例在调用这个值的时候也全都会发生了变化。static 在虚拟机中单独占用内存,在不同的包和类中都能使用,很方便。但是当应用被强杀后,若应用较长时间处于后台,会导致 NullPointerException 的异常产生。
解释:因为按下 Android 的 home 键,如果位于后台较长时间,或者由于内存不足应用被强杀,应用依然会保持 activity 的栈信息(activity 栈没有被清空,比如说 A -> B -> C -> D 这个栈还保存了,只是 ABCD 这几个 activity 实例没有了。所以回到 App 时,显示的还是 D 页面),当我们选择 “最近打开的应用” 回到前台的时候,该 activity 会重新执行 onCreate() 进行初始化操作(也包括 application 的初始化),如果操作中包含了对其他类的静态变量的引用,而应用被强杀后该静态变量的实例已被虚拟机回收,这样便引发了空指针。
那么问题来了,我们理应重新走应用的流程,如何改善这种情况而避免这种异常的发生呢,既然 App 都被强杀了,干嘛不重新走第一次启动的流程呢,别让 App 回到 D 而是再启动 A,这样所有的变量都是按正常的流程去初始化,也就不会空指针了。需要判断是否被强杀,如果是,就强制重新走应用的开始流程。通过在有心课堂的学习,进行了这个过程的模仿并梳理了思路。
参考:应用被强杀了怎么办 http://notes.stay4it.com/2016/02/26/how-to-handle-app-force-killed/
流程梳理 #
自定义了一个 CustomApplication,用来初始化全局变量
public class CustomApplication extends Application {
public static ArrayList<String> mTestNullPointer;
// 我们把 -1 模拟表示被强杀
public static int mAppStatus = -1;
@Override
public void onCreate() {
super.onCreate();
}
}
做了一个父类 BaseActivity ,让每一个 Activity 都继承自 BaseActivity,各 Activity 要么会执行 protectApp() 方法,要么会执行 setupData() 方法,前者是由于强杀,后者是正常情况下的初始化操作。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 判断如果被强杀,就回到 HomeActivity 中去,否则可以初始化
if (CustomApplication.mAppStatus == -1) {
// 重走应用流程
protectApp();
} else {
setupData();
}
}
// 在这里做初始化操作
protected void setupData() {
}
// 使用 protected 让子类可以重写该方法
protected void protectApp() {
// 重新走应用的流程是一个正确的做法,因为应用被强杀了还保存 Activity 的栈信息是不合理的
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra("action", "force_kill");
startActivity(intent);
}
}
下面是模拟强杀并且进行优化处理的做法流程
图中各 Activity 对应的代码如下
1、WelcomeActivity
public class WelcomeActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 把状态变为 0 能使父类不会走 protectApp()
CustomApplication.mAppStatus = 0;
super.onCreate(savedInstanceState);
}
@Override
protected void setupData() {
setContentView(R.layout.activity_welcome);
handler.sendEmptyMessageDelayed(0, 1000);
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
finish();
}
};
}
2、LoginActivity
public class LoginActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
}
public void login(View view) {
startActivity(new Intent(this, HomeActivity.class));
}
}
3、HomeActivity
/**
* HomeActivity 的启动模式为 "singleTask"
*/
public class HomeActivity extends BaseActivity implements View.OnClickListener {
private Button mHomeProfileBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// 初始化操作
@Override
protected void setupData() {
setContentView(R.layout.activity_home);
mHomeProfileBtn = $(R.id.id_mHomeProfileBtn);
mHomeProfileBtn.setOnClickListener(this);
CustomApplication.mTestNullPointer = new ArrayList<>();
CustomApplication.mTestNullPointer.add("profile");
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getStringExtra("action");
if ("force_kill".equals(action)) {
// 在 ProfileActivity 被强杀了就重新走应用的流程
protectApp();
}
}
@Override
protected void protectApp() {
// 回到 WelcomeActivity
startActivity(new Intent(this, WelcomeActivity.class));
finish();
}
@Override
public void onClick(View view) {
startActivity(new Intent(this, ProfileActivity.class));
}
@SuppressWarnings("unchecked")
private <T> T $(int resId) {
return (T) findViewById(resId);
}
}
4、ProfileActivity
public class ProfileActivity extends BaseActivity {
private TextView mProfileLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 让所有继承于 BaseActivity 的子类的 onCreate() 都直接交给父类 BaseActivity 去操作,
// 让父类进行一系列的先判断,不让子类随随便便地进行初始化
}
@Override
protected void setupData() {
setContentView(R.layout.activity_profile);
mProfileLabel = $(R.id.id_mProfileLabel);
mProfileLabel.setText(CustomApplication.mTestNullPointer.toString());
}
@SuppressWarnings("unchecked")
private <TT> TT $(int resId) {
return (TT) findViewById(resId);
}
}
End.
Note by HF.
Learn from 有心课堂