對于GC這個近乎玄學(xué)的東西一直是感覺神龍見首不見尾,看得見但是摸不著睦焕,Monitor里面每次都有它炕贵,一切看起來都似乎是理所當(dāng)然地進行著,對GC的印象還是一直停留在一個自動運轉(zhuǎn)的垃圾回收器默默地幫我處理好了關(guān)于內(nèi)存分配和回收的一切茧吊,雖然事實上GC的作用就是這些,但是當(dāng)碰到界面卡頓的時候不得不懷疑是過于頻繁地GC所造成的八毯。
1.數(shù)據(jù)庫:怪我咯搓侄?
我們知道,一切涉及到數(shù)據(jù)庫的操作都比較耗費系統(tǒng)的性能话速,因為實質(zhì)上它是一個不斷I/O的過程讶踪,始終伴隨著變量的生成和回收,所以操作過于頻繁的時候或者當(dāng)數(shù)據(jù)庫過于龐大的時候這個過程就會因為過于頻繁地GC而造成界面卡頓泊交。
2.實例分析
需求:用戶點擊查詢某一天的歷史數(shù)據(jù)信息乳讥,判斷本地數(shù)據(jù)庫中是否存在數(shù)據(jù):
List<Datalog> datalogs = DataSupport.findAll(Datalog.class);
if (datalogs.size() > 0){
//TODO
}
繼續(xù)看findAll()
源碼,發(fā)現(xiàn)是個同步鎖方法
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()
來返回一個數(shù)據(jù)集合廓俭。
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();
}
可見整個數(shù)據(jù)庫操作的時間和數(shù)據(jù)庫的大小也就是數(shù)據(jù)量是有直接關(guān)系的云石,有幾條數(shù)據(jù)就會產(chǎn)生幾個數(shù)據(jù)單例,當(dāng)數(shù)據(jù)量過大的時候就會造成由于頻繁地GC而造成的界面卡頓效果研乒,下面Monitor調(diào)試監(jiān)控情況:
很明顯汹忠,在用戶點擊查詢之后由于進行了多達8次的GC過程,CPU地使用率一度高達50%,在ANR的邊緣不斷試探宽菜,界面在短時間內(nèi)處于不可操作的狀態(tài)奖地,加載對話框也無法正常顯示,用戶體驗這時候是極差的赋焕,由源碼我們知道就是因為在初始化
dataList
的時候由于數(shù)據(jù)量過于龐大造成的頻繁GC的結(jié)果参歹。簡而言之, 就是執(zhí)行GC操作的時候,任何線程的任何操作都會需要暫停隆判,等待GC操作完成之后犬庇,其他操作才能夠繼續(xù)運行, 故而如果程序頻繁GC, 自然會導(dǎo)致界面卡頓。
3.解決方法
由于在歷史數(shù)據(jù)庫中只存在某一天的數(shù)據(jù)侨嘀,所以在判斷數(shù)據(jù)中是否存在被查詢的數(shù)據(jù)時是需要查詢數(shù)據(jù)庫中的第一條數(shù)據(jù)的時間是否符合要求:
/*
取得數(shù)據(jù)庫第一條數(shù)據(jù)的年月日
*/
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("初始化階段","搜索數(shù)據(jù)庫");
/*
讀數(shù)據(jù)庫臭挽,顯示數(shù)據(jù)
*/
new Thread(new Runnable() {
@Override
public void run() {
searchAndShowData();
}
}).start();
}else {
Log.d("初始化階段","無該日數(shù)據(jù)-請求服務(wù)器");
DataSupport.deleteAll(Datalog.class);
//解析用戶輸入的年月日
parseAndQueryServer(date);
}
}else {
//解析用戶輸入的年月日
Log.d("初始化階段","無數(shù)據(jù)-請求服務(wù)器");
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()
方法里只需要查詢一條數(shù)據(jù),這就意味著while
只會循環(huán)一次咬腕,大大減少了GC的時間和次數(shù)欢峰。
改進之后的效果:
GC只進行了一次,界面卡頓問題完美解決涨共。