У меня есть приложение, которое должно извлекать данные с сервера и вставлять их в базу данных SQLite в ответ на ввод пользователя. Я думал, что это будет довольно просто - код, который извлекает данные с сервера, является довольно простым подклассом AsyncTask, и он работает точно так, как я ожидаю, без зависания потока пользовательского интерфейса. Я реализовал функцию обратного вызова для него с помощью простого интерфейса и завернул его в статический класс, поэтому мой код выглядит следующим образом:
MyServerCaller.getFolderContents(folderId, new OnFolderContentsResponseListener() {
@Override
public void onFolderContentsResponse(final List<FilesystemEntry> contents) {
// do something with contents
}
}
Все по-прежнему хорошо. Даже если серверу требуется час для извлечения данных, пользовательский интерфейс все еще выполняется гладко, потому что код в getFolderContents запущен в методе doInBackground для AsyncTask (который находится в отдельном потоке от пользовательского интерфейса). В самом конце метода getFolderContents вызывается onFolderContentsResponse и передается список FilesystemEntry, полученный с сервера. Я просто говорю все это так, что, надеюсь, ясно, что моя проблема заключается не в методе getFolderContents, а в любом из моего сетевого кода, потому что там никогда не бывает.
Проблема возникает, когда я пытаюсь вставить в базу данных через мой подкласс ContentProvider в методе onFolderContentsResponse; пользовательский интерфейс всегда зависает во время выполнения этого кода, заставляя меня поверить, что, несмотря на вызов из метода doInBackground для AsyncTask, вставки как-то все еще работают в потоке пользовательского интерфейса. Вот как выглядит проблематичный код:
MyServerCaller.getFolderContents(folderId, new OnFolderContentsResponseListener() {
@Override
public void onFolderContentsResponse(final List<FilesystemEntry> contents) {
insertContentsIntoDB(contents);
}
}
И метод insertContentsIntoDB
:
void insertContentsIntoDB(final List<FilesystemEntry> contents) {
for (FilesystemEntry entry : contents) {
ContentValues values = new ContentValues();
values.put(COLUMN_1, entry.attr1);
values.put(COLUMN_2, entry.attr2);
// etc.
mContentResolver.insert(MyContentProvider.CONTENT_URI, values);
}
}
где mContentResolver ранее был установлен на результат метода getContentResolver().
Я пробовал помещать insertContentsIntoDB в свой собственный поток, например:
MyServerCaller.getFolderContents(folderId, new OnFolderContentsResponseListener() {
@Override
public void onFolderContentsResponse(final List<FilesystemEntry> contents) {
new Thread(new Runnable() {
@Override
public void run() {
insertContentsIntoDB(contents);
}
}).run();
}
}
Я также попытался запустить каждую отдельную вставку в своем потоке (метод insert в MyContentProvider синхронизирован, поэтому это не должно вызывать никаких проблем):
void insertContentsIntoDB(final List<FilesystemEntry> contents) {
for (FilesystemEntry entry : contents) {
new Thread(new Runnable() {
@Override
public void run() {
ContentValues values = new ContentValues();
values.put(COLUMN_1, entry.attr1);
values.put(COLUMN_2, entry.attr2);
// etc.
mContentResolver.insert(MyContentProvider.CONTENT_URI, values);
}
}).run();
}
}
И только для хорошей меры я также пробовал оба этих решения с соответствующим кодом в методе doInBackground другого AsyncTask. Наконец, я явно определил MyContentProvider как живущий в отдельном процессе в моем AndroidManifest.xml:
<provider android:name=".MyContentProvider" android:process=":remote"/>
Он работает нормально, но он по-прежнему работает в потоке пользовательского интерфейса. Это тот момент, когда я действительно начал разрывать мои волосы, потому что это не имеет никакого смысла для меня. Независимо от того, что я делаю, пользовательский интерфейс всегда висит во время вставок. Есть ли способ заставить их не делать этого?