關鍵詞:強殺 / 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 有心課堂