Статьи

Исправление исключения NetworkOnMainThread в приложении Android с помощью AsyncTask

Как только вы начнете выполнять длительные операции, такие как сетевые вызовы или что-то еще, что может занять более нескольких секунд в вашем приложении Android, вы столкнетесь с одной из следующих проблем:

  • При запуске приложения отображается всплывающее окно [Приложение] не отвечает (ANR) , и приложение может выполнять слишком много работы в своем основном потоке. предупреждение отображается в ваших следах Logcat.
  • Исключение NetworkOnMainThreadException запускается, как только вы вызываете что-либо по сети, например, API, независимо от продолжительности вызова.

Почему Android SDK жалуется, когда происходит одна из этих вещей? Почему код, который кажется правильным, не работает? Хорошо, если ваше приложение останется замороженным, ваши пользователи могут подумать, что оно зависло. Несколько секунд — это вечность, когда у вас нет никакой обратной связи о том, что происходит, и пользователи, вероятно, будут продолжать нажимать кнопки в качестве ответа или закрывать приложение и никогда не возвращаться. Таким образом, чтобы предотвратить это, Android SDK применяет минимальные стандарты реагирования и отображения ошибок, если эти стандарты не соблюдаются.

Чтобы понять, почему приложение зависает, мы должны взглянуть на архитектуру приложения. По умолчанию ваше Android-приложение имеет только один поток , называемый основным потоком. Весь ваш код выполняется в этом единственном потоке, и поток может делать только одно за раз.

Таким образом, если вы звоните в сеть и возникает задержка, все останавливается, пока приложение ожидает ответа, полностью замораживая ваш пользовательский интерфейс. Вы даже не сможете отображать индикатор выполнения, потому что больше ничего не может работать. Сроки выглядит так:

К счастью, вы можете создавать новые темы, которые позволяют вам делать больше, чем одну вещь одновременно:

Для одноразовых операций, которые занимают всего несколько секунд, самый простой способ справиться с этим — использовать класс AsyncTask, доступный в Android SDK. Он создаст новый поток для вас, который будет выполнять определенную задачу, а затем уведомит вас о завершении этой задачи, чтобы вы могли обновить свой пользовательский интерфейс.

Вот пример AsycTask, который вызывает GitHub API для вычисления количества хранилищ, содержащих строку «Android». Во время выполнения запроса в пользовательском интерфейсе отображается индикатор выполнения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private class GithubAndroidRepositoryQueryTask extends AsyncTask<String, Integer, String> {
 
@Override
protected void onPreExecute() {
   super.onPreExecute();
   // Prepare the UI in the main UI thread before executing the task.
   mGitRepositoryProgressBar.setVisibility(View.VISIBLE);
}
 
@Override
protected String doInBackground(String... params) {
   // Do the operation on a new thread and returns the result. It calls the GitHub API to
   // calculate the number of repository that contains the Android string
   return getAndroidRepositoryCountFromGitHub();
}
 
@Override
protected void onProgressUpdate(Integer... progress) {
   // Could be used to update the main UI thread with the progress of the background
   // operation.
}
 
@Override
protected void onPostExecute(String result) {
   // Receive the result of the operation done in the new thread in the main UI thread and
   // use it to update the UI.
   super.onPostExecute(result);
   mGitRepositoryProgressBar.setVisibility(View.INVISIBLE);
   mGitRepositoryTextView.setText(result);
}
}

И вот как вы выполняете эту задачу. В этом случае запускается событие onCreate для Activity, поэтому ответ сразу виден в макете, но его можно запустить из любого другого места:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class MainActivity extends AppCompatActivity {
 
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mGitRepositoryTextView = (TextView) this.findViewById(R.id.github_repositories_tv);
   mGitRepositoryProgressBar = (ProgressBar) this.findViewById(R.id.github_repositories_pb);
 
   // Fires off an AsyncTask to get the number of Android repositories on GitHub and displays
   // it in the application.
   new GithubAndroidRepositoryQueryTask().execute();
}

Когда вы работаете с любыми потоками, помните, что обновление пользовательского интерфейса должно выполняться только в основном потоке. Android SDK не поддерживает обновление пользовательского интерфейса из других потоков, поэтому при попытке этого вы, скорее всего, будете иметь случайные сбои и странное поведение.

Кроме того, в качестве предупреждения, Google не рекомендует полагаться на отображение индикатора выполнения исключительно для длительных операций. Они считают, что пользователи должны иметь возможность продолжать работать в приложении, а вы должны управлять тем, что происходит в бэкэнде.

Если у вас есть простое приложение, в котором есть только несколько простых случаев, оно может работать, но если у вас сложные взаимодействия между вашими данными, вы можете потратить много времени, пытаясь заставить все работать хорошо вместе и обрабатывать то, что происходит в случае сбоя операции.

Тем не менее, вы можете начать с простого AsyncTask и оптимизировать по мере продвижения . В большинстве случаев это будет хорошей отправной точкой, но как только вам нужно будет выполнить много задач одновременно, этого будет недостаточно. Когда вы будете готовы пойти дальше с этим, вы должны взглянуть на библиотеки, такие как RxJava, чтобы помочь вам управлять своими задачами