I am working on an android application that has 3 major processes.
The main UI, U, runs in its own process.
A content provider, C, running in its own process
And a service, S, running in another process.
Both U and S access an sqlite database containing about 6 tables through the content provider, C.
The issue I have is that C is not always available and this causes some queries to fail, at times.
I had to make an hack that makes a query to the ContentProvider
, C, and checks for a boolean variable, set in the ContentProvider's
SQLiteOpenHelper
that becomes true when C's onCreate has been called and its SQLiteOpenHelper
has been fully initialized too and is ready to be used by C for queries.
This works nice, but it is so stressful and I cant imagine applying it to all the points in U and S, where C is accessed.
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?
PLEASE NOTE
The ContentProvider works fine whenever it is actually up and when it goes off, no exception whatsoever is thrown.
Here is the manifest entry for 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"
/>
Thanks!
Okay, so after some experimentation, I figured out that the Android OS might have been wired to kill off a content provider if it has not been used for a while. I believe I read something like this somewhere on StackOverFlow some days back.
In order to make the content provider available on demand as my scenario required, I did the following.
First, I included a static boolean variable called initializationRunning
in the ContentProvider's
SQLiteOpenHelper
which is set to false once the SQLiteOpenHelper has been fully initialized by the ContentProvider
.
This ensures that both the ContentProvider
and its SQLiteOpenHelper
are fully prepared for use.
Then I created an additional URI which makes this variable(initializationRunning
) accessible to my other processes.
I return this variable through a MatrixCursor
or other appropriate Cursor
in the ContentProvider's
query
method.
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;
}
}
}
Now, in my Application singleton instance, I detect if the current running process is my Service's process or my UI's process.
For both cases, I do something like:
int count = 0;
while( !checkContentProviderReady() ){++count;}
This has the effect of "rousing" the ContentProvider
from sleep, or rather, it encourages the Android OS to create the ContentProvider or something.
The method, checkContentProviderReady()
is simply a method that queries the ContentProvider
for the initializationRunning
variable to know when it is false.
Immediately after this, I start a thread in the Application class
(for the Service's
process and the main UI's
process which calls the checkContentProviderReady()
method every 5 seconds.
This forces the Android OS to believe that the ContentProvider
is been used regularly and so it leaves it alive.
Well, I don't know if this will help your app, but it sure helped mine.
The summary of this?
Create a non-expensive method in your ContentProvider
and have a timed method in your clients call it periodically(not too fast!).
In my case because I needed to know that the database helper was ready, I also returned the state of the database helper in the timed method call.
Thanks!