自己进程中的自定义 ContentProvider 并不总是可用

Custom ContentProvider in own process not always available

我正在开发一个具有 3 个主要进程的 android 应用程序。

主要UI、U在自己的进程中运行。

内容提供者,C,运行在自己的进程中

还有一个服务,S,运行在另一个进程中。

US都通过内容提供者C[=访问包含大约6个表的sqlite数据库59=].

我遇到的问题是 C 并不总是可用,这有时会导致某些查询失败。

我不得不做一个 hack 来查询 ContentProviderC,并检查在 ContentProvider's 中设置的布尔变量SQLiteOpenHelperC 的 onCreate 被调用并且其 SQLiteOpenHelper 也已完全初始化并准备好被 使用时变为真C 查询。

这很好用,但压力太大,我无法想象将它应用于 US 中的所有点,其中C 被访问。

How can I ensure that C is always up for U and S to use? especially as it seems as if C can be up now and down later on?

请注意

ContentProvider 在实际启动和关闭时工作正常,不会抛出任何异常。

这是 C

的清单条目
    <provider
                android:name=".data.cprov.Provider"     
 android:authorities="mynet.app.data.cprov.Provider"
                android:enabled="true"
                android:exported="false"
                android:process=":someproc"
    android:permission="mynet.app.data.PERMISSION"
                />

谢谢!

好吧,经过一些实验,我发现 Android OS 可能已经连接到杀死内容提供者,如果它有有一段时间没用了。我相信几天前我在 Whosebug 的某处读到过类似的内容。

为了让内容提供者在我的场景中按需可用, 我做了以下。

首先,我在 ContentProvider's SQLiteOpenHelper 中包含了一个名为 initializationRunning 的静态布尔变量,一旦 SQLiteOpenHelper 被 ContentProvider 完全初始化,它就被设置为 false .

这确保 ContentProvider 及其 SQLiteOpenHelper 都已做好充分准备。

然后我创建了一个额外的 URI,使我的其他进程可以访问此变量 (initializationRunning)。

我return这个变量通过一个MatrixCursor或者其他合适的Cursor

ContentProvider's query 方法中。

public class ChatsProvider extends ContentProvider {


    public static final String AUTHORITY = "mynet.app.data.cprov.Provider;

    private static final String CHATS_TABLE = "CHATS";
    private static final String CONTACTS_TABLE = "CONTACTS;
    private static final String UPDATES_TABLE = "UPDATES";
    private static final String UPLOADED_CONTACTS_TABLE = "UPLOADED_CONTACTS";
    private static final String PRODUCTS_TABLE = "PRODUCTS";
    private static final String ROOMS_TABLE = "ROOMS";


    private static final String MOCK_TABLE = "MOCK";



    public static final String MOCK_COLUMN = "MOCK_COLUMN";




    public static final Uri CONTENT_URI_CHATS =
            Uri.parse("content://" + AUTHORITY + "/" + CHATS_TABLE);
    public static final Uri CONTENT_URI_CONTACTS =
            Uri.parse("content://" + AUTHORITY + "/" + CONTACTS_TABLE);
    public static final Uri CONTENT_URI_UPDATES =
            Uri.parse("content://" + AUTHORITY + "/" + UPDATES_TABLE);
    public static final Uri CONTENT_URI_UPLOADED_CONTACTS =
            Uri.parse("content://" + AUTHORITY + "/" + UPLOADED_CONTACTS_TABLE);
    public static final Uri CONTENT_URI_PRODUCTS =
            Uri.parse("content://" + AUTHORITY + "/" + PRODUCTS_TABLE);
    public static final Uri CONTENT_URI_ROOMS =
            Uri.parse("content://" + AUTHORITY + "/" + ROOMS_TABLE);

    public static final Uri CONTENT_URI_MOCK =
            Uri.parse("content://" + AUTHORITY + "/" + MOCK_TABLE);


    public static final int CHATS = 1;
    public static final int CHATS_ID = 100;


    public static final int CONTACTS = 101;
    public static final int CONTACTS_ID = 200;

    public static final int UPDATES = 201;
    public static final int UPDATES_ID = 300;

    public static final int UPLOADED_CONTACTS = 301;
    public static final int UPLOADED_CONTACTS_ID = 400;

    public static final int PRODUCTS = 401;
    public static final int PRODUCTS_ID = 500;

    public static final int ROOMS = 501;
    public static final int ROOMS_ID = 600;


    public static final int MOCK = 601;
    public static final int MOCK_ID = 700;













    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
/**
 * My SQLiteOpenHelper
 */
    public static DatabaseUtils databaseUtils;

    static {
        uriMatcher.addURI(AUTHORITY, CHATS_TABLE, CHATS);
        uriMatcher.addURI(AUTHORITY, CHATS_TABLE + "/#",
                CHATS_ID);

        uriMatcher.addURI(AUTHORITY, CONTACTS_TABLE, CONTACTS);
        uriMatcher.addURI(AUTHORITY, CONTACTS_TABLE + "/#",
                CONTACTS_ID);

        uriMatcher.addURI(AUTHORITY, UPDATES_TABLE, UPDATES);
        uriMatcher.addURI(AUTHORITY, UPDATES_TABLE + "/#",
                UPDATES_ID);

        uriMatcher.addURI(AUTHORITY, UPLOADED_CONTACTS_TABLE, UPLOADED_CONTACTS);
        uriMatcher.addURI(AUTHORITY, UPLOADED_CONTACTS_TABLE + "/#",
                UPLOADED_CONTACTS_ID);

        uriMatcher.addURI(AUTHORITY, PRODUCTS_TABLE, PRODUCTS);
        uriMatcher.addURI(AUTHORITY, PRODUCTS_TABLE + "/#",
                PRODUCTS_ID);

        uriMatcher.addURI(AUTHORITY, ROOMS_TABLE, ROOMS);
        uriMatcher.addURI(AUTHORITY, ROOMS_TABLE + "/#",
                ROOMS_ID);


        uriMatcher.addURI(AUTHORITY, MOCK_TABLE, MOCK);
        uriMatcher.addURI(AUTHORITY, MOCK_TABLE + "/#",
                MOCK_ID);

    }





    public ChatsProvider() {
    }


    @Override
    public synchronized boolean onCreate() {
        Utils.logErrorMessage("ContentProvider-"+this+" is up and running!", getClass());
        if(databaseUtils == null){
            databaseUtils = DatabaseUtils.getInstance(this.getContext());
        }
        return false;
    }



    @Override
    public synchronized Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        int uriType = uriMatcher.match(uri);
        String table = null;
        switch (uriType) {
            case CHATS:
                table = CHATS_TABLE;
                break;
            case CONTACTS:
                table = CONTACTS_TABLE;
                break;
            case UPDATES:
                table = UPDATES_TABLE;
                break;
            case UPLOADED_CONTACTS:
                table = UPLOADED_CONTACTS_TABLE;
                break;
            case PRODUCTS:
                table = PRODUCTS_TABLE;
                break;
            case ROOMS:
                table = ROOMS_TABLE;
                break;
            case MOCK:
                table = MOCK_TABLE;
                break;

            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        if(table.equals(MOCK_TABLE)){
            String[] value = { DatabaseUtils.initializationRunning + "" };
            String[] mockProjection = new String[]{"MOCK_COLUMN"};
            MatrixCursor c = new MatrixCursor(mockProjection);
            c.addRow(value);
            return c;
        }
        try {
            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
            queryBuilder.setTables(table);

            Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
            cursor.setNotificationUri(AppController.contentResolver,
                    uri);
            return cursor;
        }
        catch (Exception e){
            return null;
        }

    }

}

现在,在我的应用程序单例实例中,我检测当前 运行 进程是我的服务进程还是我的 UI 进程。

对于这两种情况,我都会这样做:

int count = 0;
while(  !checkContentProviderReady() ){++count;}

这具有 "rousing" ContentProvider 睡眠的效果,或者更确切地说,它鼓励 Android OS创建 ContentProvider 或其他东西。

方法,checkContentProviderReady() 只是一个查询 ContentProviderinitializationRunning 变量以知道它何时为假的方法。

紧接着,我在 Application class 中启动一个线程(用于 Service's 进程和每 5 秒调用一次 checkContentProviderReady() 方法的主 UI's 进程.

这会强制 Android OS 相信 ContentProvider 已被定期使用,因此它仍然存在。

好吧,我不知道这是否对您的应用有帮助,但它确实对我有帮助。

总结这个?

在您的 ContentProvider 中创建一个非昂贵的方法,并在您的客户端中有一个定时方法定期调用它(不要太快!)。

在我的例子中,因为我需要知道数据库助手已经准备好,所以我还在定时方法调用中 return 编辑了数据库助手的状态。

谢谢!