解惑 -- 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中的初始化代码,不建议使用静态变量来存值,如果一定要使用,为了保险起见,应该在使用的之前做一下判断。


最后温馨提示:使用静态变量需特别注意内存泄漏,因为大部分内存泄漏的问题都是由于静态变量持有需销毁对象的引用而未及时释放所造成的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值