Ответ 1
Вместо вызова mContentResolver.insert()
используйте AsyncQueryHandler
и startInsert()
. AsyncQueryHandler
предназначен для облегчения асинхронных запросов ContentResolver
.
У меня есть приложение, которое должно извлекать данные с сервера и вставлять их в базу данных 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"/>
Он работает нормально, но он по-прежнему работает в потоке пользовательского интерфейса. Это тот момент, когда я действительно начал разрывать мои волосы, потому что это не имеет никакого смысла для меня. Независимо от того, что я делаю, пользовательский интерфейс всегда висит во время вставок. Есть ли способ заставить их не делать этого?
Вместо вызова mContentResolver.insert()
используйте AsyncQueryHandler
и startInsert()
. AsyncQueryHandler
предназначен для облегчения асинхронных запросов ContentResolver
.
Я думаю, что ваша первоначальная проблема, возможно, заключалась в том, что вы вызываете метод run
в своем новом потоке (который вызывает выполнение для продолжения в текущем потоке) вместо вызова метода start
. Я думаю, это то, что Яркий Великий пытался сказать в его/ее ответе. См. Разница между запуском и запуском потока. Это распространенная ошибка.
Man.Relax yourself.And все будет выглядеть лучше. Сначала "Начать поток" - это запуск Func без запуска Func, если вы хотите запустить новую тему не только вызывают запуск func.
new Thread(Runnable runnable).start();
Тогда я уверен, что использование Handler иногда было бы лучше, чем AsyncTask.
Вы можете выполнить запрос в переопределенном методе doInBackground(Integer int)
AsynTask и обновить основной пользовательский интерфейс по методу onPostExecute(Integer int)
.