Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 解決android應用被強殺或應用被回收導致的空指針問題

解決android應用被強殺或應用被回收導致的空指針問題

編輯:關於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中被初始化的,所以這個集合肯定為空。這也就導致了空指針異常的發生。<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4NCjxwPjIuveK+9s7KzOI8YnIgLz4NCjxzdHJvbmc+xuTKtcu8wre63LzytaWjrLzIyLvO0sPHtcS9+LPMtryxu7vYytXBy6Os1Nm08r+qtcTKsbry1rG908jD06bTw8e/1sbX38b0tq/B97PMvs2/ydLUwcujrNKyvs3Kx8u1yMPO0sPHtcTTptPD1tjQwtffQSZyYXJyO0ImcmFycjtD1eK49sH3s8y+zb/J0tTBy6OhPC9zdHJvbmc+PGJyIC8+DQq12tK7sr2jus7Sw8fQ6NKq0MK9qNK7uPa1pcD9wOCjrEFuZHJvaWTW0LWlwP3A4MrH0+vO0sPH1fu49tOm08O1xMn6w/zW3MbaudK5s7XEo6zSsr7NysfLtbWxztLDx9Om08O9+LPMw7vBy9LUuvO1pcD9wODSssO709DBy6OszazKsbWlwP3A4LK7u+HS8s6qsbtHQ7vYytW19KOsy/nS1LK708O1o9DEs/bP1rG7u9jK1bXEzsrM4qOsyOfPwqO6PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> /** * 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自己即可完成整個流程!我們用這個思路也可以處理諸如單點登錄下線等問題!讓我們看看改造以後的效果
這裡寫圖片描述閱讀!!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved