Android: можете ли вы получить доступ к членам класса AsyncTask в DoInBackground?

Безопасно ли запускать метод класса класса AsyncTask внутри DoInBackground ? Или вам нужно использовать обработчик?

 private class MyAsyncTask extends AsyncTask<Void, Void, Void> { Object mObjA = null: private MyAsyncTask(Object objA) { mObjA = objA } protected void doInBackground(Void... params) { mObjA.aMethod() } } 

doInBackground безопасно запускать mObjA.aMethod() если mObjA не был передан как параметр? Есть многопоточная проблема?

Должны ли вы в doInBackground получать доступ только к объектам, которые были намеренно переданы, или можете ли вы свободно обращаться к любому члену класса без использования обработчиков?

Вы действительно можете получить доступ к любому полю вашей AsyncTask внутри doInBackground() , при условии, что mObjA не является классом Android, подобным ViewPager например ViewPager или LinearLayout или какой-либо ViewGroup .

Вопрос довольно общий, и для него есть несколько частей, давайте рассмотрим их по одному:

«Безопасно ли запускать [любой] метод AsyncTask класса AsyncTask [не указанный далее Object ] внутри doInBackground

Насколько это безопасно в отношении чего?

«Есть многопоточная проблема?»

Хорошо, так это безопасно в отношении многопоточности? Ответ отрицательный, он не является вообще безопасным, если вы не знаете, что этот конкретный метод на этом конкретном объекте безопасен для вызова из нескольких потоков. Другими словами, просто AsyncTask что-то в AsyncTask это не делает его более безопасным, чем использование любого другого Thread .

Пример:

 public class MainActivity extends Activity { private void testLoop(String logTag, SimpleDateFormat sdf, String inputString) { Log.d(logTag, "Starting..."); for (int i = 0; i < 100; i++) { try { String outputString; outputString = sdf.format(sdf.parse(inputString)); if (!outputString.equals(inputString)) { Log.d(logTag, "i: " + i + " inputString: " + inputString + " outputString: " + outputString); break; } } catch (Exception e) { Log.d(logTag, "Boom! i: " + i, e); break; } } Log.d(logTag, "Done!"); } public class MyAsyncTask extends AsyncTask<Void, Void, Void> { SimpleDateFormat sdf; public MyAsyncTask(SimpleDateFormat sdf) { this.sdf = sdf; } @Override protected Void doInBackground(Void... params) { testLoop("MyAsyncTask", sdf, "2014-12-24 12:34:56.789"); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); new MyAsyncTask(sdf).execute(); testLoop("MainActivity", sdf, "2015-04-01 23:23:23.232"); } } 

Вывод:

08: 52: 37.462: D / MainActivity (13051): Начало …
08: 52: 37.466: D / MyAsyncTask (13051): запуск …
08: 52: 37.466: D / MainActivity (13051): i: 3 inputString: 2015-04-01 23: 23: 23.232 outputString: 2014-12-01 0012: 34: 23.789
08: 52: 37.467: D / MainActivity (13051): Готово!
08: 52: 37.467: D / MyAsyncTask (13051): i: 0 inputString: 2014-12-24 12: 34: 56,789 outputString: 2014-12-01 12: 34: 23,789
08: 52: 37.467: D / MyAsyncTask (13051): Готово!

О, произошло что-то «странное».

Давайте запустим его снова:

08: 53: 44.551: D / MainActivity (13286): Начало …
08: 53: 44.562: D / MyAsyncTask (13286): запуск …
08: 53: 44.563: D / MainActivity (13286): i: 11 inputString: 2015-04-01 23: 23: 23.232 outputString: 1970-01-24 12: 00: 23.232
08: 53: 44.563: D / MainActivity (13286): Готово!
08: 53: 44.567: D / MyAsyncTask (13286): i: 0 inputString: 2014-12-24 12: 34: 56.789 outputString: 2014-01-24 12: 34: 56.789
08: 53: 44.567: D / MyAsyncTask (13286): Готово!

Все еще странно, но другое.

Давайте запустим его еще раз:

08: 54: 23.560: D / MainActivity (13286): Начало …
08: 54: 23.579: D / MyAsyncTask (13286): запуск …
08: 54: 23.596: D / MainActivity (13286): i: 3 inputString: 2015-04-01 23: 23: 23.232 outputString: 2015-01-01 00: 00: 00.000
08: 54: 23.596: D / MainActivity (13286): Готово!
08: 54: 24.423: D / MyAsyncTask (13286): Готово!

Другой раз, на этот раз он даже не проявил себя ни в одной из нитей.

Определенно многопоточность. Как мы это исправим?

Из вопроса: «Должны ли вы в doInBackground получить доступ к объектам, которые были намеренно переданы»?

Ну, давайте попробуем это, немного изменим код:

 public class MyAsyncTask extends AsyncTask<SimpleDateFormat, Void, Void> { @Override protected Void doInBackground(SimpleDateFormat... params) { testLoop("MyAsyncTask", params[0], "2014-12-24 12:34:56.789"); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); new MyAsyncTask().execute(sdf); testLoop("MainActivity", sdf, "2015-04-01 23:23:23.232"); } 

Что происходит, когда мы запускаем это?

09: 11: 43.734: D / MainActivity (15881): начиная …
09: 11: 43.763: D / MyAsyncTask (15881): Начало …
09: 11: 43.782: D / MainActivity (15881): i: 7 inputString: 2015-04-01 23: 23: 23.232 outputString: 2015-004-01 00: 00: 00.789
09: 11: 43.782: D / MainActivity (15881): Готово!
09: 11: 43.783: D / MyAsyncTask (15881): i: 5 inputString: 2014-12-24 12: 34: 56.789 outputString: 2014-012-24 12: 34: 56.789
09: 11: 43.783: D / MyAsyncTask (15881): Готово!

Ничего себе, все еще странно. Таким образом, похоже, что AsyncTask даже не защищает нас, если мы передаем наш Object в качестве параметра.

Так от чего он нас защищает? В документах указано :

AsyncTask гарантирует, что все вызовы обратного вызова синхронизируются таким образом, что следующие операции безопасны без явных синхронизаций.
– Задайте поля элемента в конструкторе или onPreExecute () и обратитесь к ним в doInBackground (Params …).
– Установите поля-члены в doInBackground (Params …) и обратитесь к ним в onProgressUpdate (Progress …) и onPostExecute (Result).

Это все, что нужно, без проверки.

Итак, как мы можем это исправить?

Существует несколько вариантов. В этом конкретном надуманном примере очевидным выбором является не разделение sdf а создание нового экземпляра в AsyncTask . Обычно это хороший вариант, если это возможно. Если нет, вы (разработчик) должны убедиться, что доступ каким-то образом синхронизирован. В нашем примере вы можете использовать синхронизированный оператор:

 synchronized (sdf) { outputString = sdf.format(sdf.parse(inputString)); } 

08: 56: 59.370: D / MainActivity (13876): Начало …
08: 56: 59.375: D / MyAsyncTask (13876): начало …
08: 57: 00.287: D / MainActivity (13876): Готово!
08: 57: 01.216: D / MyAsyncTask (13876): Готово!

Ура! В заключение!

Вы также можете синхронизировать весь метод:

 private synchronized void testLoop(String logTag, SimpleDateFormat sdf, String inputString) { // ... } 

08: 59: 11.237: D / MainActivity (14361): начало …
08: 59: 12.036: D / MainActivity (14361): Готово!
08: 59: 12.036: D / MyAsyncTask (14361): начало …
08: 59: 12.862: D / MyAsyncTask (14361): Готово!

Все еще работает, но теперь вы в основном сериализовали всю работу, которую мы надеялись сделать параллельно с несколькими потоками. Небезопасная, но не хорошая производительность. Еще одна ошибка многопоточного программирования, но не связанная с вопросом. (На самом деле версия с синхронизированной версией выше не намного эффективнее, так как в нашем примере мы вообще ничего не делаем).

Как насчет обработчиков?

Из вопроса: «вам нужно использовать обработчик?»

Давайте изменим пример использования Handler :

 public class MyAsyncTask extends AsyncTask<Handler, Void, Void> { SimpleDateFormat sdf; public MyAsyncTask(SimpleDateFormat sdf) { this.sdf = sdf; } @Override protected Void doInBackground(Handler... params) { params[0].post(new Runnable() { @Override public void run() { testLoop("MyAsyncTask", sdf, "2014-12-24 12:34:56.789"); } }); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); new MyAsyncTask(sdf).execute(new Handler()); testLoop("MainActivity3", sdf, "2015-04-01 23:23:23.232"); } 

10: 36: 15.899: D / MainActivity (17932): запуск …
10: 36: 16.028: D / MainActivity (17932): Готово!
10: 36: 16.038: D / MyAsyncTask (17932): начало …
10: 36: 16.115: D / MyAsyncTask (17932): Выполнено!

Handler будет работать в этом случае (хотя теперь этот пример начинает казаться очень беден), поскольку он по существу устраняет проблемы многопоточности для определенного фрагмента кода, вообще запуская его на другой теме. Это также означает, что это не будет работать, если вам нужно немедленно использовать результат этой части кода в вашей AsyncTask .

Итак, что это за разговоры об использовании элементов пользовательского интерфейса в doInBackground() ?

Новый пример:

 public class MainActivity extends Activity { private TextView tv; public class MyAsyncTask extends AsyncTask<Void, Void, Void> { TextView tv; public MyAsyncTask(TextView tv) { this.tv = tv; } @Override protected Void doInBackground(Void... params) { tv.setText("Boom!"); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout layout = new LinearLayout(this); tv = new TextView(this); tv.setText("Hello world!"); Button button = new Button(this); button.setText("Click!"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new MyAsyncTask(tv).execute(); } }); layout.addView(tv); layout.addView(button); setContentView(layout); } } 

Запустите это, нажмите кнопку, приложение остановится, и вы найдете следующую трассировку стека в logcat:

11: 21: 36.630: E / AndroidRuntime (23922): FATAL EXCEPTION: AsyncTask # 1
11: 21: 36.630: E / AndroidRuntime (23922): Процесс: com.example.testsothreadsafe, PID: 23922
11: 21: 36.630: E / AndroidRuntime (23922): java.lang.RuntimeException: Произошла ошибка при выполнении doInBackground ()
11: 21: 36.630: E / AndroidRuntime (23922): at android.os.AsyncTask $ 3.done (AsyncTask.java:304)
11: 21: 36.630: E / AndroidRuntime (23922): при java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:355)
11: 21: 36.630: E / AndroidRuntime (23922): at java.util.concurrent.FutureTask.setException (FutureTask.java:222)
11: 21: 36.630: E / AndroidRuntime (23922): at java.util.concurrent.FutureTask.run (FutureTask.java:242)
11: 21: 36.630: E / AndroidRuntime (23922): at android.os.AsyncTask $ SerialExecutor $ 1.run (AsyncTask.java:231)
11: 21: 36.630: E / AndroidRuntime (23922): at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1112)
11: 21: 36.630: E / AndroidRuntime (23922): at java.util.concurrent.ThreadPoolExecutor $ Worker.run (ThreadPoolExecutor.java:587)
11: 21: 36.630: E / AndroidRuntime (23922): на java.lang.Thread.run (Thread.java:818)
11: 21: 36.630: E / AndroidRuntime (23922): вызвано: android.view.ViewRootImpl $ CalledFromWrongThreadException: только исходный поток, создавший иерархию представлений, может коснуться его представлений.
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.ViewRootImpl.checkThread (ViewRootImpl.java:6357)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.ViewRootImpl.requestLayout (ViewRootImpl.java:874)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.View.requestLayout (View.java:17476)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.View.requestLayout (View.java:17476)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.View.requestLayout (View.java:17476)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.View.requestLayout (View.java:17476)
11: 21: 36.630: E / AndroidRuntime (23922): на android.view.View.requestLayout (View.java:17476)
11: 21: 36.630: E / AndroidRuntime (23922): на android.widget.TextView.checkForRelayout (TextView.java:6871)
11: 21: 36.630: E / AndroidRuntime (23922): at android.widget.TextView.setText (TextView.java:4057)
11: 21: 36.630: E / AndroidRuntime (23922): at android.widget.TextView.setText (TextView.java:3915)
11: 21: 36.630: E / AndroidRuntime (23922): на android.widget.TextView.setText (TextView.java:3890)
11: 21: 36.630: E / AndroidRuntime (23922): at com.example.testsothreadsafe.MainActivity $ MyAsyncTask.doInBackground (MainActivity.java:22)
11: 21: 36.630: E / AndroidRuntime (23922): at com.example.testsothreadsafe.MainActivity $ MyAsyncTask.doInBackground (MainActivity.java:1)
11: 21: 36.630: E / AndroidRuntime (23922): at android.os.AsyncTask $ 2.call (AsyncTask.java:292)
11: 21: 36.630: E / AndroidRuntime (23922): at java.util.concurrent.FutureTask.run (FutureTask.java:237)
11: 21: 36.630: E / AndroidRuntime (23922): … еще 4

Android специально не позволяет вам манипулировать иерархией представления из другого потока, кроме того, который ее создал. Но запрещен ли какой-либо доступ ?

Измените пример на:

 @Override protected Void doInBackground(Void... params) { Log.d("MyAsyncTask", tv.getText().toString()); return null; } 

Нажмите кнопку, выход будет:

11: 25: 20.950: D / MyAsyncTask (24329): Привет, мир!

Похоже, возможен некоторый доступ.

Так что это сообщение здесь? Многопоточное программирование сложно. Просто потому, что Android не позволяет вам делать что-то, это не значит, что это не мешает вам делать это автоматически «безопасно».

AsyncTask является потокобезопасным. Но, я думаю, более удобно использовать общий характер AsyncTask . Таким образом, вы можете избежать просто анонимных внутренних классов.

 new AsyncTask<Object, Void, Void>() { protected Void doInBackground(Object... params){ params[0].aMethod(); return null; } }.execute(obj); 

Но, как говорят другие, объект, который вы проходите, может не быть потокобезопасным.

Intereting Posts
Можете ли вы установить цвет фона на кнопке переключения, не закрывая переключатель? Активность просочилась в окно com.android.internal.policy.impl.PhoneWindow$DecorView@46029dd0, которое было первоначально добавлено здесь Авторизация через Android-приложение Android без Facebook SDK Ошибка идентификации идентификатора найдена для атрибута даже после использования пространства имен http://schemas.android.com/apk/res-auto Android HttpURLConnection: сжатие gzip Проверка ZipAlign OnConfigurationChanged () вызов дважды при изменении ориентации Пейзаж в портрет Ошибка minSdkVersion при загрузке APK в Android Market Ошибка в com.google.android.gms.common.SignInButton Черный экран во внутреннем предпочтении. Экран Android: настройка вкладок в состоянии: как сделать селектор доступным Отображение карты Google в виде круга Приложение занимает слишком много времени для запуска Переход на другую вкладку несколько раз дает EEROR Как разделить меню, например, хром