26
2017
09

一次ContentProvider踩坑之旅

最近产品中有这样一个需求,有多个进程,在系统开机时通过ContentProvider去查询另外一个进程的数据库,获取一个公共字段。

在实际开发中,有四个进程分别去执行这个操作,但是发现第一个或者前两个进程总是出现开机查询失败对情形。报错信息如下:

07-16 07:08:32.435: E/AndroidRuntime(1869): java.lang.NullPointerException
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:224)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.example.neuronsdk.DatabaseHandler.open(DatabaseHandler.java:59)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.example.neuronsdk.DatabaseHandler.addEvent(DatabaseHandler.java:72)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.example.neuronsdk.NeuronActivity.onClick(NeuronActivity.java:28)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.example.test_application.MainActivity$1.onClick(MainActivity.java:47)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.view.View.performClick(View.java:4438)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.view.View$PerformClick.run(View.java:18422)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.os.Handler.handleCallback(Handler.java:733)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.os.Handler.dispatchMessage(Handler.java:95)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.os.Looper.loop(Looper.java:136)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at android.app.ActivityThread.main(ActivityThread.java:5017)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at java.lang.reflect.Method.invokeNative(Native Method)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at java.lang.reflect.Method.invoke(Method.java:515)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
07-16 07:08:32.435: E/AndroidRuntime(1869):     at dalvik.system.NativeStart.main(Native Method)

根据这个报错信息,很容易的就能将错误信息定位在SQLiteOpenHelper类中的getDatabaseLocked方法中:

private SQLiteDatabase getDatabaseLocked(boolean writable) {
  //省略代码 ```
   db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
  //省略代码 ```
    }

很明显初始化数据库帮助类时context为空了。由于数据库所在的进程也是一个单独的进程,当时就想,是不是查询数据库的时候数据库进程还没有完全起来,导致context为空呢?但是心里也犯嘀咕,因为明显方法已经走到ContentProvider查询方法中了,代表这数据库进程(ContentProvider进程)已经起来了。那就只剩下一个种可能了,就是数据库帮助类初始化时,传入的context为空了。

可是自始至终,我写负责的代码部分,都没有给数据库帮助类传入过context,自然的,去查看封装好的数据库帮助类的工具类,见到如下代码:

public abstract class SingleDBHelper extends BaseDBHelper {
    //省略代码 ```
    public SingleDBHelper() {
        this((Context)null);
    }

    public SingleDBHelper(Context context) {
        if(context == null) {
                this.mDBHelper = SQLiteDBHelper.getSQLiteDBHelper(BaseApplication.getContext(), this.getDBController());
            } else {
                this.mDBHelper = SQLiteDBHelper.getSQLiteDBHelper(context, this.getDBController());}
  //省略代码 ```
}

可以看到,两个构造方法,如果使用无参构造,时间上在拿数据库时使用的是通过BaseApplication.getContext()得到的context,而我在查询数据库时恰是使用的无参构造,恍然大悟。

ContentProvider和Application都有getContext方法,但是这个连个方法,在各自的onCreat方法被调用之前,得到的都是null。而ContentProvider是一个通过Binder提供跨进程数据共享的组件,当ContentProvider所在的进程启动时,ContentProvider会同时启动并发布到AMS中,而这个时候,ContentProvider的onCreate方法,会先于Application的onCreate方法。也就是说,在其他进程通过ContentProvider查询数据库的时候,数据库进程起来了,ContentProvider的onCreate方法被调用,但此时Application的onCreate方法可能还没有被调用,于是通过ContentProvider增删改查方法操作数据库时,就会报上面提到的错误。

总结:虽然ContentProvider的getContext方法,和Application的getContext方法,得到的context是一个context,但是由于ContentProvider和Application的onCreate方法有一个先后顺序的问题,导致ContentProvider的getContext不为null了,但是Application的getContex方法却处于可能为空的状态。因此,初始化数据库时,能保证使用ContentProvider的contex,就能避免两者onCreate方法执行时机带来的空指针异常。

上一篇:Spring定时器Quartz,定时调用存储过程 下一篇:谷歌和火狐浏览器下的input的padding不同表现