美文网首页Android开发程序员Android 技术开发
【Android】自定义ContentProvider时踩的一个

【Android】自定义ContentProvider时踩的一个

作者: blink_dagger | 来源:发表于2016-11-29 08:51 被阅读0次

    最近突然要开发一个收集手机中所有应用信息,然后分别做权限处理的第三方应用,而我很不巧地负责数据库这一块(几乎全忘记了)。所以只能恶补了半天之后开始coding,一开始很完美,自己试着运行了几遍后就很自信地提交给测试mm了,然后很顺利地过了测试。把软件释放给pm之后,报了一个java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.的错误,然后就是悲哀地被领导邮件轰炸!所以这里总结一下ContentProvider的简单使用!

    1.继承ContentProvider,并实现增删改查、创建、更新等相关方法

    有的时候数据存入后,没有及时更新的话,就需要我们手动地getContentResolver().notifyChange去通知provider更新。这部分代码几乎都一样,大致如:

        @Override
        public int delete(Uri uri, String s, String[] as) {
            return 0;
        }
    
        @Override
        public String getType(Uri uri) {
            return null;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues contentvalues) {
            sqlDB = dbHelper.getWritableDatabase();
            long rowId = sqlDB.insert(TABLE_NAME, "", contentvalues);
            if (rowId > 0) {
                Uri rowUri = ContentUris.appendId(
                        MyInfo.Info.CONTENT_URI.buildUpon(), rowId).build();
                getContext().getContentResolver().notifyChange(rowUri, null);
                return rowUri;
            }
            throw new SQLException("Failed to insert row into " + uri);
        }
    
        @Override
        public boolean onCreate() {
            dbHelper = new DatabaseHelper(getContext());
            return (dbHelper == null) ? false : true;
        }
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            qb.setTables(TABLE_NAME);
            Cursor c = qb.query(db, projection, selection, null, null, null,
                    sortOrder);
            //c.setNotificationUri(getContext().getContentResolver(), uri);     
            return c;
        }
    
        @Override
        public int update(Uri uri, ContentValues contentvalues, String s,
                String[] as) {
            SQLiteDatabase db=dbHelper.getReadableDatabase();
            int num = db.update(TABLE_NAME, contentvalues,s, as);       
            db.close();
            return num;
        }
    
    2.将创建数据库的工作托付给SQLiteOpenHelper

    在自定义Provider的onCreate中,我们直接new一个数据库就可以了,然后增删改查时,工作就能都交给db来做了。这里我直接写了一个内部SQLiteOpenHelper类:

        private static class DatabaseHelper extends SQLiteOpenHelper {
            DatabaseHelper(Context context) {
                super(context, DATABASE_NAME, null, DATABASE_VERSION);
            }
    
            @Override
            public void onCreate(SQLiteDatabase db) {
                // 创建用于存储数据的表
                db.execSQL("Create table "
                        + TABLE_NAME
                        + "( _id INTEGER PRIMARY KEY AUTOINCREMENT, info_package TEXT NOT NULL,info_name TEXT NOT NULL,info_states INTEGER NOT                      NULL);");
            }
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
                onCreate(db);
            }
        }
    
    3.对外暴露数据库的操作API

    为了避免增删改查异步执行,导致数据的混乱,所以这里肯定要全部synchronized,然后外部直部直接传入相关的info就可以了。这里我直接写了一个util类来封装,栗子:

    public class DataUtils {
        private Context mContext;
    
        public DataUtils(Context context) {
            super();
            this.mContext = context;
        }
        private synchronized boolean isContain(Info info) {
    
        }
        private synchronized void upDateRecord(Info info) {
        
        }
        private synchronized int getState(Info info) {
    
        }
        private synchronized void insertRecord(Info info) {
    
        }
    
        public void saveData(Info info) {
            if(isContain(info)){
                upDateRecord(info);
            }else{
                insertRecord(info);
            }
        }
    
        public int getData(Info info) {
            int state = 0;
            if(isContain(info)){
                state = getState(info);
            }
            return state;
        }
        
    }
    

    --*
    --*

    接着在清单文件中注册下,就能直接使用了,我是直接在Activity resume的时候起线程去进行数据库的操作,这里就不贴具体代码了。自己运行时候功能一切正常,可是到了pm那边经过反人类的测试,一下子就发现毛病了。当我们的Activity还没完全resume完成的时候,如果直接退出该界面,就报java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.的错误了。

    一开始,我觉得是线程和synchronized是不是哪里出了问题,把代码一个一个注释掉了还是有错。只能科学上网查了下,发现可能是db的连接断开了,所以对它进行操作就报非法状态错误了。同时在官网文档中也发现了吻合的解释:
    IllegalStateException
    if the last reference to the object has already been released.

    void close ()
    Releases a reference to the object, closing the object if the last reference was released. Calling this method is equivalent to calling

    看样子是我哪里把数据库给手动close掉了,一找才发现是自己手贱,习惯性地在使用完db就释放了:

        @Override
        public int update(Uri uri, ContentValues contentvalues, String s,
                String[] as) {
            SQLiteDatabase db=dbHelper.getReadableDatabase();
            int num = db.update(TABLE_NAME, contentvalues,s, as);       
            db.close();
            return num;
        }
    

    把close给注释掉了,就没报上述错误了,看来是在ContentProvider中,我们只能关闭cursor,而不能手动关闭db。瞎猜想可能是为了节省再次访问或者更新时打开db的内存开销,反正一个应用一般只对应一个db,不关闭也不会有太多的问题。

    相关文章

      网友评论

        本文标题:【Android】自定义ContentProvider时踩的一个

        本文链接:https://www.haomeiwen.com/subject/wegopttx.html