谈到性能问题,你可能首先就想到的是麻烦或者头大,其实我有时候也非常头疼,因为性能优化涉及的太广,因此一时间无从下手,就像我一样特别是项目里涉及到底层代码时,就更加不知所措,那么我们如何来进行优化?其实引用Android官网的建议,无非就是以下,说的好轻松,可是实际操作还是有很多学问,因为时间原因,我总结了一点自己在项目里的优化建议。
- 不要做冗余的工作
- 尽量避免次数过多的内存分配操作
- 深入理解所有语言特性和系统平台的API,具体到Android开发,熟练掌握java语言,并对SDK的API熟悉,了如指掌
一、谨慎使用Service
项目用到Service执行不是长期任务时,任务结束后一定要停止该Service,否则会一直占用内存,也可以使用IntentService來代替,因为它执行完成会自动停止。
二、Bitmap对象和ImageView
1:Bitmap的优化无非就是进行压缩,比如页面仅仅是一个很小的ImageView,此时展示高清图片没有任何意义,而且占用内存过大,需要根据项目要求对图片进行合理的压缩,具体如何压缩网上一大堆资料,如果涉及到创建Bitmap对象时,使用完手动recycle()清除资源。
2:开发中经常在xml里或者代码指定本地图片/网络图片,当不使用时,可以主动调用以下代码进行释放资源
BitmapDrawable db = (BitmapDrawable)ImageView.getDrawable()
//BitmapDrawable db =(BitmapDrawable)ImageView.getBackground()
Bitmap bp = db.getBitmap();
bp.recycle();
bp = null;
三、布局优化
1:减少嵌套,优先使用LinearLayout实现,当LinearLayout需要多层嵌套才能完成时,建议使用RelativeLayout实现。
2:使用include标签实现布局复用减少嵌套,比如显示断网的布局,几乎每个页面都使用到,可以将该布局单独定义出来,使用者使用include来引入。
好处1->是避免重复代码提高复用性。
好处2->维护方便,哪天领导说换个图片,只需要修改一个地方即可。
3:使用merge标签包裹(当View的父View是LinearLayout布局,而父View的父View也是LinearLayout,方向一致时),这样可以去除多余相同的
比如定义merage_demo_item文件是很多地方可以共用的,布局排版是线性的垂直方向,里面两个按钮
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="被引入的View1"
android:textColor="@android:color/white"
android:background="#666060"
android:textSize="20sp"
android:gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="被引入的View2"
android:textColor="@android:color/white"
android:background="#666060"
android:textSize="20sp"
android:gravity="center"/>
</merge>
刚好引入的地方它的父容器也是线性垂直方法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/merage_demo_item"/>
</LinearLayout>
这样也能实现效果
如果我们不用merage标签,而是在 merage_demo_item文件里顶层使用LinearLayout虽然也是可以实现同样效果,但是会多嵌套一层,性能有所下降。
3:使用ViewStub标签进行懒加载,虽然定义在xml里,但是系统不会加载到内存里,当我们使用的时候才进行加载。
<ViewStub
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/viewstub_state"/>
四、其它优化
1、对字符串的+拼接会导致创建很多临时对象非常影响性能,强烈建议使用StringBuilder或者StringBuffer完成。
2、for循环的使用适当结束循环
for( int i=0; i<arr.size(); i++ ){
if("".queals(arr.get(i)))
break; //条件满足时,一定要结束循环
}
3、SQLite数据库优化
当我们给数据库插入数据时,每插入一次数据,就会操作一次数据库,插入多少数据就直接对磁盘操作多少次,大大降低了效率。
如何进行优化?
1、1:事务优化
SqliteDataBase.beginTransaction()开启事务
SqliteDataBase.setTransactionSuccessful()设置事务成功
SqliteDataBase.endTransaction()结束事务
操作数据库之前开启事务,操作完成设置事务成功,最后在结束事物,结束事务的时候会判断事务有没有成功,如果成功则一次批量执行,否则回滚到之前。
正常操作代码
long startTime = System.currentTimeMillis();
String table = "user";
for (int i = 0; i < 100; i++) {
ContentValues contentValues = new ContentValues();
contentValues.put("name", "张三" + i);
contentValues.put("age", i);
sqLiteDatabase.insert(table, null, contentValues);
}
long endTime = System.currentTimeMillis();
String message = "总共耗时" + (endTime - startTime);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
Log.v(Contacts.TAG, message); //1457ms
优化后代码
long startTime = System.currentTimeMillis();
String table = "user";
/**开启一个事务**/
sqLiteDatabase.beginTransaction();
try {
for (int i = 0; i < 100; i++) {
ContentValues contentValues = new ContentValues();
contentValues.put("name", "张三" + i);
contentValues.put("age", i);
sqLiteDatabase.insertOrThrow(table, null, contentValues);
}
/**将数据库事务设置为成功**/
sqLiteDatabase.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
/**结束数据库事务**/
sqLiteDatabase.endTransaction();
}
long endTime = System.currentTimeMillis();
String message = "总共耗时" + (endTime - startTime);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
Log.d(Contacts.TAG, message); //68ms
正常执行需要1457ms,优化之后只需要68ms
1、2使用原生SQL语句
系统提供了一些封装方法比如insert()、upDate()、delete()等,查询源码发现这些,里面一大堆判断拼接SQL语句,相比我们直接使用原始SQL语句效率越低。
1、3使用SQLiteStatementt预编译对象
通过查看源码,无论我们怎么操作数据库,最终都是创建了SQLiteStatement对象来操作对应的方法,结合事务可以使效率更快,两个方法各运行了10次得到的总耗时对比,代码如下
public void saveUser3() {
long start = SystemClock.currentThreadTimeMillis();
SQLiteDatabase db = mHelper.getWritableDatabase();
db.beginTransaction();
for (int i=0;i<300;i++){
ContentValues values = new ContentValues();
values.put("name", "事务"+i);
values.put("pwd", "");
db.insert("mAccount", null, values);
}
db.setTransactionSuccessful();
db.endTransaction();
db.close(); //47 34 44 44 45 46 49 48 48 48 = 453
Log.v("wjw", "事务优化===" + (SystemClock.currentThreadTimeMillis() - start));
}
public void insert4() {
long start = SystemClock.currentThreadTimeMillis();
SQLiteDatabase db = mHelper.getWritableDatabase();
db.beginTransaction();
String sql = "INSERT INTO mAccount(name,pwd) values(?,?)";
for (int i=0;i<300;i++){
SQLiteStatement statement = db.compileStatement(sql);
statement.bindString(1,"Statement"+i);
statement.bindString(2,"");
statement.executeInsert();
}
db.setTransactionSuccessful();
db.endTransaction();
db.close(); //46 23 29 45 38 35 31 33 38 34 = 352
Log.v("wjw", "事件+SQLiteStatement优化===" + (SystemClock.currentThreadTimeMillis() - start));
}
1、3使用索引
比如表名table,里面有name字段,当你查询name=张三时,会全局遍历直到添加满足,假设给name添加索引,内部会根据索引进行获取,详细使用情况可以网上查看。
五、过渡绘制
1、View重叠,比如背景是白色,上面叠一个View,View又设置一个背景,同一个地方绘制了两次。
2、消除默认背景,在Activity的onCreate中getWindow().setBackroundDrable(null);
3、ClipRect使用
六、线程池
为什么使用线程池?
在Java里,创建和销毁线程都是很大的开销,可能消耗的资源比我们执行任务用的更多,这个时候就要考虑线程的复用,线程池里的线程执行完任务会处于等待状态,达到线程复用。
线程池结构:
线程池管理:管理创建,销毁,添加任务等。
线程:线程池中的线程,如果是没有任务的时候,会一直处于等待。
任务接口:每个任务实现的接口,方便任务的执行。
任务队列:缓存机制,存放一些没有处理的任务。