ContentProvider的批量操作优化

ContentProvider中实现了常见的insert,query,delete和update的方法就能完成基本操作了,可是遇到批量的操作怎么办?可以用循环一条条的插入,当然也可以使用ContentProvider的如下两个方法:

    public ContentProviderResult[] applyBatch(ArrayList operations)
            throws OperationApplicationException {
        final int numOperations = operations.size();
        final ContentProviderResult[] results = new ContentProviderResult[numOperations];
        for (int i = 0; i < numOperations; i++) {
            results[i] = operations.get(i).apply(this, results, i);
        }
        return results;
    }
    public int bulkInsert(Uri uri, ContentValues[] values) {
        int numValues = values.length;
        for (int i = 0; i < numValues; i++) {
            insert(uri, values[i]);
        }
        return numValues;
    }
上述是默认实现,可以看出实际上还是循环实现,和你自己写循环没啥区别。那么批量操作如何优化效率,从最近遇到的一个bug讲起。

最近一个app批量操作问题相当严重,插入500多条数据要耗时86秒,真是跌破眼镜。开始尝试使用applyBatch方法,可是如上分析是不会有什么效果的,日志分析发现多次引发数据库监听的回调和查询。

原因是insert,delete和update标准实现中,一般都会在操作完毕后发送通知,例如:

			getContext().getContentResolver().notifyChange(AUTHORITY_URI, null,
					false);
这样app层次会得到通知刷新页面。那么在循环中的每次操作中都会引发数据的重查,而且由于不专业的代码重查会触发多次:

多余的数据库监听

ContentObserver

使用了ContentObserver数据库监听,得到通知会发生回调,触发查询

手动的查询

在数据库操作完毕后,加入重查的代码

CursorAdapter的自动刷新

实际上CursorAdapter是默认会自动刷新的,只要ContentProvider中代码中有notifyChange。

三种方式代码中都有,即插一条数据会触发三次重查,太恐怖了。保留三种中的一个就可以了,是个listview列表页面的话推荐用最后一种方式。


批量操作优化


notifyChange的优化

减少了两次重查后发现,最后一次还是多余的,应该是批量插入后重查一次即可,而不是循环中的每一次都触发重查。

要想实现这个,那么必须继承修改bulkInsert和applyBatch方法,使得notify减少到最少。

事务优化

批量操作加入事务,只写一次数据库,避免了多次数据库操作,见网上的诸多使用事务后的效率时间对比,使用事务可以大幅度的提高效率。默认的ContentProvider是没有事务的,要自己加上。下面是个模板代码,和业务无关,任何ContentProvider稍微改下都可以用。

private boolean mIsInTransaction = false;
    public ContentProviderResult[] applyBatch(
            ArrayList operations)
                    throws OperationApplicationException {      
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.beginTransaction();//开始事务
        mIsInTransaction = true;
        try{  
                 ContentProviderResult[] results = super.applyBatch(operations);  
                 db.setTransactionSuccessful();//设置事务标记为successful  
                 return results;  
        } finally {  
                 mIsInTransaction = false;
                 db.endTransaction();//结束事务  
                 notifyChange();
        }  
    }
	protected void notifyChange() {
		if(!mIsInTransaction) {
			getContext().getContentResolver().notifyChange(AUTHORITY_URI, null,
					false);			
		}
	}

总结

这个问题中,实现ContentProvider的代码中没有加入notifyChange,导致app侧有同学为了解决bug到处加入手动查询的代码,后续有人接手后又加入了数据库监听的代码,再往后ContentProvider加入了notifyChange的代码。而且app侧没有使用批量方法,数据库也没有实现批量方法。

归根到底是对数据库基本知识和Android源码的了解不足,很多问题都是开发自己造出来的。

很多问题Android本身现有的框架都可以解决的,不用自己造轮子,同时造轮子的99%以上的不会比google造的好。



你可能感兴趣的:(android)