解惑 -- static

static解惑,首先我們先來了解了解要解決的是啥惑,在瞭解之前,我們先來簡單過一下static的應用,static關鍵字可修飾內部類、方法和變量,被修飾的元素將會獨立存儲於內存中的靜態數據區,不隨對象的創建而產生。被static修飾的變量具有了全局的屬性,所以使用它便可以很方便在安卓中各種組件中進行數據的交互,然而,大量實例表明使用這種方式進行數據交互是不安全的,被static修飾的變量可能被回收變爲空,但是爲什麼在一些常量和單例的使用中又可以使用static進行修飾而不用擔心其值被回收呢?而這就是我們今天要解決的惑。


首先我們說說爲什麼用靜態變量來進行數據交互是不安全的,因爲在安卓中,activity遵循着嚴格的生命週期,因此當系統資源匱乏的時候,進程很可能被gc回收的,尤其是優先級較低的進程,即是假如你的應用在按了Home鍵後會在後臺運行,那麼優先級是較低的,系統隨時可能殺掉你的程序來獲得更多的內存,靜態變量也會被隨之銷燬,當重新打開程序,Android會在一個新的進程中重新建立一個新的Application實例,也會重新恢復之前的狀態,即是你之前打開了幾個Activity,系統都會重新幫你完全恢復好,這裏如果有一些數據希望保存和恢復的,可以用savedInstanceState來實現。但是因爲是重新開的進程,靜態變量也被重新初始化置null或者是其他的默認值,這時候如果再使用它,可想而知,程序則可能會直接crash。


接下來我們用一個實例來直觀地感受一下:

public class MainActivity extends Activity {
    public static String globalParam;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btnSave).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                globalParam = "yph";
            }
        });
        findViewById(R.id.btnUse).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("------------->",globalParam);
            }
        });
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d("------------->","onSaveInstanceState");
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d("------------->","onRestoreInstanceState");
    }
}

一個頁面兩個Button,一個Button保存數據到靜態變量globalParam中,另一個Button使用,即打印其值

首先打開此Activity,點一下保存,再點一下使用輸出log如下:


按Home健,讓應用運行在後臺,輸出如下:


模擬系統內存不足時候的殺進程,打開DDMS,找到相應進程點擊Stop Process殺掉進程:


重新打開App,輸出如下:


此時再點擊使用,程序直接crash,報空指針錯誤:


所以,很顯然的是,進程重新創建,靜態變量被重新置null了。同時可以證明,遇到這種情況我們可以使用onSaveInstanceState和onRestoreInstanceState來保存和恢復數據。



我們來看看靜態常量的寫法:

    public static final String SD_CARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
    public static final String USER_HEAD_IMG_PATH = SD_CARD_PATH  + "UserHeadImg.jpg";
    public static final String CAMERA_PATH = SD_CARD_PATH  + "CameraTmp.jpg";

    public static final String URL = "http://fcloud.com/api.php/";</span>

和靜態內部類餓漢式單例的寫法:

	publlic class Singleton {
    	private Singleton() {}
    	private static class SingletonLoader {
        	private static final Singleton INSTANCE = new Singleton();
   	 }
 
   	 public static Singleton getInstance() {
        	return SingletonLoader.INSTANCE;
   	 }
	}


爲什麼以上兩種寫法是安全的呢,爲什麼這裏的靜態變量就不會被回收呢?這就涉及到static元素與java類生命週期的關係了。
java類的生命週期如下:


加載連接(驗證,準備,解析)→初始化→卸載


簡要介紹一下這幾個步驟:
加載:當使用到某個類時(包括實例化類或者使用其靜態成員),JVM通過ClassLoader加載類至其中執行
驗證:驗證加載的類是否合法
準備:爲靜態變量分配內存和默認初值
解析:把常量池中的符號引用轉換爲直接引用(符號引用可以理解爲類名,方法名,變量名等寫代碼的時候所表示的形式,而直接引用則爲jvm中可以直接使用內存地址形式,這個步驟是把這兩個形式進行了變換)
初始化:初始化與類相關的靜態賦值語句和靜態語句
卸載:這裏的類被卸載等同於進程被回收


根據以上步驟,靜態變量是在classLoader通過以上裝載類的步驟(加載、連接和初始化)進行了創建與初始化,所以每次新開進程都會把靜態常量和靜態代碼塊進行初始化,然後才執行程序的入口函數,那上面時候纔會回收這些靜態變量呢,根據java類的生命週期,可以看到只有在類被卸載的時候,這些靜態值纔會被回收,而類被卸載等同於進程的結束,當進程重新開啓,這些定義好的靜態量又一次被重新分配內存,賦值,然後被使用。因此可以證明以上的靜態常量和單例寫法是安全的。至於用靜態變量來傳值是不安全的也是可以證明的,當我們在程序運行的時候給某個靜態變量賦了值,然後程序掛掉了,再回來這個變量被初始化了,即不是原來值了,這時候再進行使用則會出現問題了。明確一點就是,靜態變量的值不會在進程運行的時候被回收,只會在類被卸載的時候,既是進程死掉的時候被回收。

所以得出一個結論:靜態變量在初始化之後的賦值是不安全的,這裏的初始化包括了application,activity,fragment的onCreate中的初始化代碼,不建議使用靜態變量來存值,如果一定要使用,爲了保險起見,應該在使用的之前做一下判斷。


最後溫馨提示:使用靜態變量需特別注意內存泄漏,因爲大部分內存泄漏的問題都是由於靜態變量持有需銷燬對象的引用而未及時釋放所造成的。

發佈了33 篇原創文章 · 獲贊 36 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章