对于GC这个近乎玄学的东西一直是感觉神龙见首不见尾,看得见但是摸不着,Monitor里面每次都有它,一切看起来都似乎是理所当然地进行着,对GC的印象还是一直停留在一个自动运转的垃圾回收器默默地帮我处理好了关于内存分配和回收的一切,虽然事实上GC的作用就是这些,但是当碰到界面卡顿的时候不得不怀疑是过于频繁地GC所造成的。
1.数据库:怪我咯?
我们知道,一切涉及到数据库的操作都比较耗费系统的性能,因为实质上它是一个不断I/O的过程,始终伴随着变量的生成和回收,所以操作过于频繁的时候或者当数据库过于庞大的时候这个过程就会因为过于频繁地GC而造成界面卡顿。
2.实例分析
需求:用户点击查询某一天的历史数据信息,判断本地数据库中是否存在数据:
List<Datalog> datalogs = DataSupport.findAll(Datalog.class);
if (datalogs.size() > 0){
//TODO
}
继续看findAll()
源码,发现是个同步锁方法
public static synchronized <T> List<T> findAll(Class<T> modelClass, long... ids) {
return findAll(modelClass, false, ids);
}
public static synchronized <T> List<T> findAll(Class<T> modelClass, boolean isEager,
long... ids) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onFindAll(modelClass, isEager, ids);
}
<T> List<T> onFindAll(Class<T> modelClass, boolean isEager, long... ids) {
List<T> dataList;
if (isAffectAllLines(ids)) {
dataList = query(modelClass, null, null, null, null, null, "id", null,
getForeignKeyAssociations(modelClass.getName(), isEager));
} else {
dataList = query(modelClass, null, getWhereOfIdsWithOr(ids), null, null, null, "id",
null, getForeignKeyAssociations(modelClass.getName(), isEager));
}
return dataList;
}
这边是通过query()
来返回一个数据集合。
if (cursor.moveToFirst()) {
SparseArray<QueryInfoCache> queryInfoCacheSparseArray = new SparseArray<QueryInfoCache>();
Map<Field, GenericModel> genericModelMap = new HashMap<Field, GenericModel>();
do {
T modelInstance = (T) createInstanceFromClass(modelClass);
giveBaseObjIdValue((DataSupport) modelInstance,
cursor.getLong(cursor.getColumnIndexOrThrow("id")));
setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor, queryInfoCacheSparseArray);
setGenericValueToModel((DataSupport) modelInstance, supportedGenericFields, genericModelMap);
if (foreignKeyAssociations != null) {
setAssociatedModel((DataSupport) modelInstance);
}
dataList.add(modelInstance);
} while (cursor.moveToNext());
queryInfoCacheSparseArray.clear();
genericModelMap.clear();
}
可见整个数据库操作的时间和数据库的大小也就是数据量是有直接关系的,有几条数据就会产生几个数据单例,当数据量过大的时候就会造成由于频繁地GC而造成的界面卡顿效果,下面Monitor调试监控情况:
很明显,在用户点击查询之后由于进行了多达8次的GC过程,CPU地使用率一度高达50%,在ANR的边缘不断试探,界面在短时间内处于不可操作的状态,加载对话框也无法正常显示,用户体验这时候是极差的,由源码我们知道就是因为在初始化
dataList
的时候由于数据量过于庞大造成的频繁GC的结果。简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿。
3.解决方法
由于在历史数据库中只存在某一天的数据,所以在判断数据中是否存在被查询的数据时是需要查询数据库中的第一条数据的时间是否符合要求:
/*
取得数据库第一条数据的年月日
*/
Datalog firstLog = DataSupport.findFirst(Datalog.class);
if (firstLog != null){
String time = firstLog.getDtime();
String checkTime = time.substring(0,8);
if (date.substring(2,10).equals(checkTime)){
Log.d("初始化阶段","搜索数据库");
/*
读数据库,显示数据
*/
new Thread(new Runnable() {
@Override
public void run() {
searchAndShowData();
}
}).start();
}else {
Log.d("初始化阶段","无该日数据-请求服务器");
DataSupport.deleteAll(Datalog.class);
//解析用户输入的年月日
parseAndQueryServer(date);
}
}else {
//解析用户输入的年月日
Log.d("初始化阶段","无数据-请求服务器");
DataSupport.deleteAll(Datalog.class);
parseAndQueryServer(date);
}
<T> T onFindFirst(Class<T> modelClass, boolean isEager) {
List<T> dataList = query(modelClass, null, null, null, null, null, "id", "1",
getForeignKeyAssociations(modelClass.getName(), isEager));
if (dataList.size() > 0) {
return dataList.get(0);
}
return null;
}
在onFindFirst()
方法里只需要查询一条数据,这就意味着while
只会循环一次,大大减少了GC的时间和次数。
改进之后的效果:
GC只进行了一次,界面卡顿问题完美解决。