为不同的屏幕而设计
通常会用ScrollView 和 ListView 轻松搞定,虽然有时它们并不能完全覆盖全部屏幕,这两个控件会让你用最小的开发代价来保证你的软件在大多数屏幕上正常展示。
Dashboard style 的设计不需要scroll
使用文件夹
Android 的资源文件夹结构非常强大,在资源文件夹下你可以怎样做:
values-small
vaules-sw360dp
vaules-sw400dp
vaules-sw600dp
vaules-sw800dp
在 values-small 文件夹中存放了一个 bools.xml 文件, 文件中有如下几行代码:
if(getResources().getBoolean(R.bool.small_screen)){getSupportActionBar().hide();}
在小尺寸设备中boolean值将置为true ,对于大屏幕而言,ActionBar就置为了显示状态.。ActionBar因此来节省空间. 这段代码正是ActionBarSherlock 扩展库中的一部分。在values-sw360dp文件夹中,存放对应屏幕宽于360dp的资源文件。我不需要将 bools.xml 文件放入 values-sw400dp文件夹中, 因为操作系统会自动按相应路径搜索.
例如一个设备宽 600dp (600/160=3.75 英寸, 这就是我们通常所说的7片装) 操作系统会在values-sw600dp 和其包含的的文件夹中搜索 bools.xml 文件, 若没有找到则搜索 values-sw400dp 文件夹,再搜索 values-sw360dp 文件夹以此类推.
建议3:160dp = 1英寸。320 dp = 2英寸。dp = dip
你可以用这些目录结构技巧来应付所有资源类型
如:layout-sw360dp目录可以匹配目标宽是360dp的机器。如果你也要支持横竖屏布局切换的话,可以用如下目录:
layout-sw360dp-land
layout-sw360dp-port
别急,你有一半的用户是说阿拉伯语的?那就将布局名称改为下面的样子吧:
layout-sw360dp-land
layout-sw360dp-port
layout-sw360dp-land-ar
layout-sw360dp-port-ar
前两个可以适用于所有语言,-ar代表阿拉伯语。
让空白空间大于图像空间。让图像空间大于按钮的大小。
控件放大后是很丑陋的。一个100dip(0.63")大小的按钮是不想在平板上显示为原来两倍宽度200dip(1.25")的.原因是屏幕变大了,这不是说平板是给巨人用的。我们可以这样做,在按钮增加的空间和图片扩展的空间里添加空白。
用GraphicalLayout工具快速预览
GraphicalLayout是WYSIWG XML编辑器。在添加一些元素之后,可以在GraphicalLayout的下拉选择菜单里选择不同屏幕尺寸进行测试。
不要把所有的图片都缩放了
用布局文件来适应不同屏幕尺寸的方法只是成功的一半,布局里的元素(如:图片)也要能在高分辨率的屏幕下良好工作。在概念上比较简单的方式就是创建一套完整的图片目录并将它们与很多drawable目录匹配起来。
一般来说有drawble-ldpi, drawable-hdpi等目录就足够了
不要这样:
drawable-sw600dp-ldpi
drawable-sw600dp-mdpi
drawable-sw600dp-hdpi
drawable-sw600dp-xhdpi
drawable-sw600dp-xxhdpi
避免使用位图(jpg,png)
对于一些图标来说,用位图是个不错的选择,因为它们使用简单。但是如果可以避免使用位图,你可以节省很多空间。但用不同的方法也可以达到很好的结果。
建议用XML绘图
位图都可以用XML绘图来代替的。XML绘图不是万能的,但是它的方便性还是使我感到惊讶
用更多的XML绘图(如果必须,就用位图)。那我们怎样为天气信号构建一个超酷的图标-让灯泡动态的依据光的强度来进行自动填充,以及怎么点击指针后让其旋转呢?这里我们用位图和XML结合起来做个例子:
灯泡我们用PNG图:icon_magnitude_min(一个空的灯泡)和icon_magnitude_max(充满光的灯泡),然后我们动态的裁剪后者。
用9-patch
如果你想适应什么东西——例如拐角的圆弧或者颜色,创建9个小块要比创建位图更多被涉及,这就像回到了图像编辑器的时代。许多用9-patches获得的效果也可以通过XML获得。
通过覆盖onDraw()创建自定义views
有些事情XML并不十分在行,我们在OpenSignal和WeatherSignal中画过许多图像,为此有许多的库,但是我们要为自定义图像自己编写代码。这很有趣。或许你永远也不需要做这个,但为了使图像高度动态并自定义,这经常是唯一可行的办法。
不能使用XML的地方使用SVG
有时候覆盖onDraw()并勤勤恳恳的为自定义view编写代码画出需要的线条与弧线是过于技术化了。毕竟有一种矢量图像语言,它称作…Scalable Vector Graphics(可扩展矢量图形)。对SVG文件GZip压缩将它们变得更小它们就会处理的更快。SVG库并不是支持一切.在一些特定的alpha通道中似乎不能正常工作,你甚至不得不在代码中将它们剔除。
达到在android所有版本里表示展现一致的目标
为了确定你的app在所有的设备里看起来是一致的,你将需要自定义所有的东西。这其实没有你想象中那么难,只要你做到了,你将能更加好地把握到你的app的展示外观。
Selectors是创建buttons的利器
你将如何创建一个当按下去会改变的button呢?很简单:像下面那样在xml文件里定义背景。该xml文件将接收到button当前状态并且在外观上做出相应的改变。
尽量减少XML布局层次
更多的层次意味着系统将为解析你的代码付出更多的工作,这将会让图像渲染的更慢。
用Android Lint(Eclipse)
它将会得到程序的一些信息,并能提高程序的运行速度,或者它能让你得代码更加清爽。Android Lint可以得到错误信息。它可以给你的代码提供很详细的信息,并在你出错之前就可以给做出提示。
HierarchyViewer可以直观的看到你布局的层次
这个智能的工具可以显示布局中有多少层次,而且可以提示出那些可以让程序变慢。
用AsyncTasks
Anroid工程团队受够了人们经常在UI线程里面实现网络调用(译注:耗时操作,容易阻塞UI刷新),所以他们实现了一些可产生编译级错误信息的API。但是仍然在很多app中的一些工作会拖垮UI线程,我们要考虑到UI布局要快以及提高UI的响应性。
Proguard太好用了
ProGuard现在是默认启动着的. Proguard太好用了 (提高你app的速度和降低文件大小),但这也让StackTraces 非常难以处理。你将需要重新追踪你的StackTraces,因此你将需要继续保留在每次构建中创建的Proguard的映射文件。我把它们都放到以代码版本号命名的文件夹里。
为了显示StackTraces里的行数,你需要修改ProGuard的配置。确认你的proguard.cfg拥有下面这句话:
-keepattributes SourceFile,LineNumberTable
引用:http://mobile.51cto.com/ahot-410775.htm
优化内存管理机制问题
除了 优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
//设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理
bitmap 设置图片尺寸,避免 内存溢出
android 中用bitmap 时很容易内存溢出,报如下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget
● 主要是加上这段:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
● eg1:(通过Uri取图片)
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即为原来的四分之一
Bitmap bitmap = BitmapFactory.decodeStream(cr
.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。
● eg2:(通过路径取图片)
private ImageView preview;
private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg";
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap b = BitmapFactory.decodeFile(fileName, options);
preview.setImageBitmap(b);
filePath.setText(fileName);
优化Dalvik虚拟机的堆内存分配
对于Android平台来说,其托管层使用的Dalvik JavaVM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法: private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate时就可以调用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。
善用层级观察器(Hierarchy Viewer)
Android SDK tools目录下提供一个观察布局的工具,层级观察器(Hierarchy Viewer)。Hierarchy Viewer工具是一个非常好的布局优化工具,同时,你也可以通过它学习他人的布局。应该说是一个非常实用的工具。
Sqlite更优性能
Sqlite默认会为每个插入、更新操作创建一个事务,并且在每次插入、更新后立即提交。
这样如果连续插入100次数据实际是创建事务->执行语句->提交这个过程被重复执行了100次。如果我们显示的创建事务->执行100条语句->提交会使得这个创建事务和提交这个过程只做一次,通过这种一次性事务可以使得性能大幅提升。尤其当数据库位于sd卡时,时间上能节省两个数量级左右。
Sqlte显示使用事务,示例代码如下:
public void insertWithOneTransaction() {
SQLiteDatabase db = sqliteOpenHelper.getWritableDatabase(); // Begins a transaction db.beginTransaction();
try { // your sqls for (int i = 0; i < 100; i++) {
db.insert(yourTableName, null, value);
} // marks the current transaction as successful db.setTransactionSuccessful();
} catch (Exception e) { // process it e.printStackTrace();
} finally { // end a transaction db.endTransaction();
}
}
其中sqliteOpenHelper.getWritableDatabase()表示得到写表权限。
SQL语句的拼接使用StringBuilder代替String
这个就不多说了,简单的string相加会导致创建多个临时对象消耗性能。StringBuilder的空间预分配性能好得多。如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。
少用cursor.getColumnIndex
根据性能调优过程中的观察cursor.getColumnIndex的时间消耗跟cursor.getInt相差无几。可以在建表的时候用static变量记住某列的index,直接调用相应index而不是每次查询。
public static final String HTTP_RESPONSE_TABLE_ID = android.provider.BaseColumns._ID;
public static final String HTTP_RESPONSE_TABLE_RESPONSE = "response";
public List<Object> getData() {
……
cursor.getString(cursor.getColumnIndex(HTTP_RESPONSE_TABLE_RESPONSE));
……
}
优化为
public static final String HTTP_RESPONSE_TABLE_ID = android.provider.BaseColumns._ID; public static final String HTTP_RESPONSE_TABLE_RESPONSE = "response";
public static final int HTTP_RESPONSE_TABLE_ID_INDEX = 0;
public static final int HTTP_RESPONSE_TABLE_URL_INDEX = 1;
public List<Object> getData() {
……
cursor.getString(HTTP_RESPONSE_TABLE_RESPONSE_INDEX);
……
}
Sqlite可使用单线程池
Sqlite是常用于嵌入式开发中的关系型数据库,完全开源。
与Web常用的数据库Mysql、Oracle db、sql server不同,Sqlite是一个内嵌式的数据库,数据库服务器就在你的程序中,无需网络配置和管理,数据库服务器端和客户端运行在同一进程内,减少了网络访问的消耗,简化了数据库管理。不过Sqlite在并发、数据库大小、网络方面存在局限性,并且为表级锁,所以也没必要多线程操作。
Android中数据不多时表查询可能耗时不多,不会导致anr,不过大于100ms时同样会让用户感觉到延时和卡顿,可以放在线程中运行,但sqlite在并发方面存在局限,多线程控制较麻烦,这时候可使用单线程池,在任务中执行db操作,通过handler返回结果和ui线程交互,既不会影响UI线程,同时也能防止并发带来的异常。实例代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable() {
@Override public void run() {
// db operetions, u can use handler to send message after
db.insert(yourTableName, null, value);
handler.sendEmptyMessage(xx);
}
});
如何引用系统资源
Android中没有公开的资源,在xml中直接引用会报错。除了去找到对应资源并拷贝到我们自己的应用目录下使用以外,我们还可以将引用“@android”改成“@*android”解决。比如上面引用的附件图标,可以修改成下面的代码。
android:icon="@*android:drawable/ic_menu_attachment"
修改后,再次Build工程,就不会报错了。
利用系统的字符串资源
假设我们要实现一个Dialog,Dialog上面有“确定”和“取消”按钮。就可以使用下面的代码直接使用Android系统自带的字符串。
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/yes"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="@android:string/yes"/>
<Button
android:id="@+id/no"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="@android:string/no"/>
</LinearLayout>
如果使用系统的字符串,默认就已经支持多语言环境了。如上述代码,直接使用了@android:string/yes和@android:string/no,在简体中文环境下会显示“确定”和“取消”,在英文环境下会显示“OK”和“Cancel”。
利用系统的Style
假设布局文件中有一个TextView,用来显示窗口的标题,使用中等大小字体。可以使用下面的代码片段来定义TextView的Style。
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" />
其中android:textAppearance="?android:attr/textAppearanceMedium"就是使用系统的style。需要注意的是,使用系统的style,需要在想要使用的资源前面加“?android:”作为前缀,而不是“@android:”。
利用系统的颜色定义
除了上述的各种系统资源以外,还可以使用系统定义好的颜色。在项目中最常用的,就是透明色的使用。代码片段如下。
android:background ="@android:color/transparent"
Android系统本身有很多资源在应用中都可以直接使用,具体的,可以进入android-sdk的相应文件夹中去查看。例如:可以进入$android-sdk$\platforms\android-8\data\res,里面的系统资源就一览无余了。
<include /> 标签来重用layout的代码
<include layout="@layout/navigator_bar" />
Android有个隐藏布局ViewStub
ViewStub是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。当ViewStub被设置为可见,或者调用inflate()函数时,才会真的去加载这个布局资源文件。该ViewStub在加载视图时会在父容器中替换它本身。因此,ViewStub会一直存在于视图中,直到调用setVisibility(int)或者inflate()为止。ViewStub的布局参数会随着加载的视图数一同被添加到ViewStub父容器。同样,也可以通过使用inflated Id属性来定义或重命名要加载的视图对象的Id值。
请参考下面的代码片段。
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
通过“stub_import”这个id可以找到被定义的ViewStub对象。加载布局资源文件“progress_overlay”后,ViewStub对象从其父容器中移除。可以通过“panel_import”这个id找到由布局资源“progress_overlay”创建的View。
执行加载布局资源文件的推荐方式如下:
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// 或者
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
当inflate()被调用, 这个ViewStub被加载的视图所替代,并且返回这个视图对象。这使得应用程序不需要额外执行findViewById()来获取加载视图的引用。
静态变量引起内存泄露
在代码优化的过程中,我们需要对代码中的静态变量特别留意。静态变量是类相关的变量,它的生命周期是从这个类被声明,到这个类彻底被垃圾回收器回收才会被销毁。所以,一般情况下,静态变量从所在的类被使用开始就要一直占用着内存空间,直到程序退出。如果不注意,静态变量引用了占用大量内存的资源,造成垃圾回收器无法对内存进行回收,就可能造成内存的浪费。
先来看一段代码,这段代码定义了一个Activity。
private static Resources mResources;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
if (mResources == null) {
mResources = this.getResources();
}
}
这段代码中有一个静态的Resources对象。代码片段mResources = this.getResources()对Resources对象进行了初始化。这时Resources对象拥有了当前Activity对象的引用,Activity又引用了整个页面中所有的对象。
如果当前的Activity被重新创建(比如横竖屏切换,默认情况下整个Activity会被重新创建),由于Resources引用了第一次创建的Activity,就会导致第一次创建的Activity不能被垃圾回收器回收,从而导致第一次创建的Activity中的所有对象都不能被回收。这个时候,一部分内存就浪费掉了。
使用Application的Context
在Android中,Application Context的生命周期和应用的生命周期一样长,而不是取决于某个Activity的生命周期。如果想保持一个长期生命的对象,并且这个对象需要一个Context,就可以使用Application对象。可以通过调用Context.getApplicationContext()方法或者Activity.getApplication()方法来获得Application对象。
依然拿上面的代码作为例子。可以将代码修改成下面的样子。
private static Resources mResources;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
if (mResources == null) {
// mResources = this.getResources();
mResources = this.getApplication().getResources();
}
}
在这里将this.getResources()修改为this.getApplication().getResources()。修改以后,Resources对象拥有的是Application对象的引用。如果Activity被重新创建,第一次创建的Activity就可以被回收了。
及时关闭资源Cursor
Cursor是Android查询数据后得到的一个管理数据集合的类。正常情况下,如果我们没有关闭它,系统会在回收它时进行关闭,但是这样的效率特别低。如果查询得到的数据量较小时还好,如果Cursor的数据量非常大,特别是如果里面有Blob信息时,就可能出现内存问题。所以一定要及时关闭Cursor。
下面给出一个通用的使用Cursor的代码片段。
Cursor cursor = null;
try{
cursor = mContext.getContentResolver().query(uri,null,null,null,null);
if (cursor != null) {
cursor.moveToFirst();
// 处理数据
}
} catch (Exception e){
e.printStatckTrace();
} finally {
if (cursor != null){
cursor.close();
}
}
即对异常进行捕获,并且在finally中将cursor关闭。
同样的,在使用文件的时候,也要及时关闭。
使用Bitmap及时调用recycle()
前面的章节讲过,在不使用Bitmap对象时,需要调用recycle()释放内存,然后将它设置为null。虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。
在代码优化的过程中,如果发现某个Activity用到了Bitmap对象,却没有显式的调用recycle()释放内存,则需要分析代码逻辑,增加相关代码,在不再使用Bitmap以后调用recycle()释放内存。
对ListView Adapter进行优化
下面以构造ListView的BaseAdapter为例说明如何对Adapter进行优化。
在BaseAdapter类中提供了如下方法:
public View getView(int position, View convertView, ViewGroup parent)
当ListView列表里的每一项显示时,都会调用Adapter的getView方法返回一个View,
来向ListView提供所需要的View对象。
下面是一个完整的getView()方法的代码示例。
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText("line" + position);
return convertView;
}
private class ViewHolder {
TextView text;
}
当向上滚动ListView时,getView()方法会被反复调用。getView()的第二个参数convertView是被缓存起来的List条目中的View对象。当ListView滑动的时候,getView可能会直接返回旧的convertView。这里使用了convertView和ViewHolder,可以充分利用缓存,避免反复创建View对象和TextView对象。
如果ListView的条目只有几个,这种技巧并不能带来多少性能的提升。但是如果条目有几百甚至几千个,使用这种技巧只会创建几个convertView和ViewHolder(取决于当前界面能够显示的条目数),性能的差别就非常非常大了。
代码“微优化”
当今时代已经进入了“微时代”。这里的“微优化”指的是代码层面的细节优化,即不改动代码整体结构,不改变程序原有的逻辑。尽管Android使用的是Dalvik虚拟机,但是传统的Java方面的代码优化技巧在Android开发中也都是适用的。
下面简要列举一部分。因为一般Java开发者都能够理解,就不再做具体的代码说明。
创建新的对象都需要额外的内存空间,要尽量减少创建新的对象。
将类、变量、方法等等的可见性修改为最小。
针对字符串的拼接,使用StringBuffer替代String。
不要在循环当中声明临时变量,不要在循环中捕获异常。
如果对于线程安全没有要求,尽量使用线程不安全的集合对象。
使用集合对象,如果事先知道其大小,则可以在构造方法中设置初始大小。
文件读取操作需要使用缓存类,及时关闭文件。
慎用异常,使用异常会导致性能降低。
如果程序会频繁创建线程,则可以考虑使用线程池。
使用软引用和弱引用
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。
在java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。
由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。
首先定义一个HashMap,保存软引用对象。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
再来定义一个方法,保存Bitmap的软引用到HashMap。
public void addBitmapToCache(String path) {
// 强引用的Bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加该对象到Map中使其缓存
imageCache.put(path, softBitmap);
}
获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。
public Bitmap getBitmapByPath(String path) {
// 从缓存中取软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判断是否存在软引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
Bitmap bitmap = softBitmap.get();
return bitmap;
}
使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。
需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。