или public class ExampleService extends Service

advertisement
Платформа Android
Ведущий семинара: Максим Лейкин, компания «МЕРА НН»
Работа с потоками
Все операции, которые потенциально могут занять
существенное время (>5 сек) необходимо выносить в
отдельные потоки, чтобы они не тормозили UI Thread
Есть несколько способов способов разделить выполнение
на несколько потоков:
-cоздание потоков Thread/Runnable
-использование класса AsyncTask
Создание потоков
Передача сообщений между потоками
Нельзя просто взять и получить доступ к элементам UI из
другого потока. В силу модели многопоточности Android,
изменять состояние элементов интерфейса разрешается
только из того потока, в котором эти элементы были
созданы, иначе будет вызвано исключение
CalledFromWrongThreadException. На этот случай Android
API предоставляет сразу несколько решений.
1. View#post
2. Activity#runOnUiThread
3. Handler
View#post
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.hello);
WorkingClass workingClass = new WorkingClass();
Thread thread = new Thread(workingClass);
thread.start();
}
class WorkingClass implements Runnable{
public void run() {
//Фоновая работа
//Отправить в UI поток новый Runnable
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("The job is done!");
}
});
}
}
Activity#runOnUIThread
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.hello);
WorkingClass workingClass = new WorkingClass();
Thread thread = new Thread(workingClass);
thread.start();
}
class WorkingClass implements Runnable{
public void run() {
//Фоновая работа
//Отправить в UI поток новый Runnable
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("The job is done!");
}
});
}
}
Handler
1. Создать внутри класса экземпляр класса Handler
2. Переопределить метод handleMessage()
3. Создать новый поток (non-UI thread)
4. В non-UI thread необходимо получить объект сообщение
через метод obtainMessage()
5. Послать сообщение в Handler через один из методов
sendMessage()
Handler
Методы sendMessage():
1) boolean sendMessage(Message msg) – посылает
сообщение в конец очереди
2) boolean sendMessageAtFrontOfQueue(Message msg) –
посылает сообщение в начало очереди
3) boolean sendMessageAtTime(Message msg, long
uptimeMillis) – посылает сообщение в конец очереди в
заданное время
4) boolean sendMessageDelayed(Message msg, long
delayMillis) - посылает сообщение в конец очереди с
задержкой delayMillis миллисекунд по отношению к
текущему времени
AsyncTask
Класс AsyncTask позволяет выполнять «тяжелые» задачи
вне UI-потока и передавать в UI-поток результаты работы.
Это проще чем через handler , но имеет ряд ограничений.
Чтобы работать с AsyncTask, надо создать класс-наследник
и в нем переопределить методы:
-
-
doInBackground() – будет выполнен в новом потоке, здесь решаем
все «тяжелые» задачи. Т.к. поток не основной - не имеет доступа к UI.
onPreExecute() – выполняется перед doInBackground(), имеет доступ к
UI
onPostExecute() – выполняется после doInBackground() (не
срабатывает в случае, если AsyncTask был отменен), имеет доступ к
UI
onProgressUpdate() – отображает прогресс выполнения в UI-потоке
Поток запускается на выполнение из UI-потока методом
execute().
AsyncTask
При описании класса-наследника AsyncTask надо указать
три типа данных:
1) Тип входных данных. Это данные, которые пойдут на вход
метода doInBackground()
2) Тип промежуточных данных. Данные, которые
используются для вывода промежуточных результатов в
методе onProgressUpdate()
3) Тип возвращаемых данных. То, что вернет AsyncTask после
работы. Приходи в качестве параметра в метод
onPostExecute(), можно получить методом get()
Пример:
class MyTask extends AsyncTask<String, Integer, Void>
{
…
}
AsyncTask
Для отмены AsyncTask нужно вызвать метод:
public final boolean cancel (boolean mayInterruptIfRunning)
Будет произведена попытка отмены задачи. Если задача уже
закончила выполняться, уже была отменена раньше или не
может быть отменена – метод вернет false. Если задача еще
на начала выполняться, то ее выполнение не начнется. Если
она уже начала выполняться, то она будет прервана или не
прервана в зависимости от значения параметра
mayInterruptIfRunning
В результате вызова cancel() после того как закончит
выполнение метод doInBackground() будет вызван метод
onCancelled() вместо onPostExecute()
AsyncTask
Несмотря на вызов cancel() метод doInBackground() будет
выполняться до конца. Если мы этого не хотим, надо внутри
этого метода периодически проверять возвращаемое
значение метода isCancelled() и самим выходить из метода
doInBackground()
protected void doInBackground(Void... params) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(1);
if (isCancelled()) return null;
}
} catch (InterruptedException e) {
Log.d(LOG_TAG, "Interrupted");
e.printStackTrace();
}
return null;
}
Недостатки AsyncTask
1. Жизненный цикл
Если Activity создавшая AsyncTask будет уничтожена,
AsyncTask не будет уничтожен автоматически. Он продолжает
работать, пока его метод doInBackground() не завершится.
Если теперь AsyncTask попытается взаимодействовать с
компонентами уничтоженной Activity (например, в методе
onPostExecute()) программа упадет, т.к. Activity больше не
существует. Таким образом, мы всегда должны быть
уверены, что мы отменили нашу задачу до того как Activity
будет уничтожена.
2. Утечки памяти
Так как AsyncTask имеет методы, которые работают в
фоновом потоке (doInBackground()), а также методы, которые
работают в потоке пользовательского интерфейса (например
OnPostExecute()), он сохраняет ссылку на Activity, до тех пор
пока работает. Но, если Activity уничтожена, он будет попрежнему хранить эту ссылку в памяти. Это препятствует
освобождению памяти garbage collector.
Недостатки AsyncTask
3. Потеря результатов
Мы потеряем результаты выполнения AsyncTask если наша
Activity будет пересоздана. Например, это происходит при
повороте экрана. Activity будет уничтожена и создана заново,
но наш AsyncTask теперь будет иметь недействительную
ссылку на эту Activity, поэтому onPostExecute() не будет
иметь никакого эффекта. Чтобы решить эту проблему
нужно сохранять ссылку на AsyncTask во время изменения
конфигурации
Последовательность выполнения AsyncTask
Пример:
new AsyncTask1().execute();
new AsyncTask2().execute();
В каком порядке выполняются?
До API версии 1.6 (Donut): последовательно
С API версии 1.6 до API версии 2.3 (Gingerbread): параллельно
С API версии 3.0 (Honeycomb) по настоящее время: последовательно 
А вот так параллельно:
public static void execute(AsyncTask as) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) {
as.execute();
} else {
as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
Intents: Broadcast Intents
С помощью Broadcast Intents можно передавать
широковещательные сообщения системе. Таким образом, например,
операционная система уведомляет приложения о поступившем
звонке, уровне заряда батареи, появлении нового wi-fi спота и т.п.
Можно создавать свои широковещательные сообщения.
public static final String NEW_LIFEFORM_DETECTED =
“com.niit.android.intent.action.NEW_LIFEFORM”;
Intent intent = new Intent(NEW_LIFEFORM_DETECTED);
intent.putExtra(“lifeformName”, lifeformType);
intent.putExtra(“longitude”, currentLongitude);
intent.putExtra(“latitude”, currentLatitude);
sendBroadcast(intent);
Intents: Broadcast Intents
Прослушивание Broadcast Intents:
1. Создать класс наследуемый от BroadcastReceiver:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//TODO: React to the Intent received.
}
}
Внимание!
1) Метод onReceive() должен выполняться не более 5 сек. иначе ОС снимет
приложение, как не отвечающее на запросы.
2) Для реакции на Broadcast Intent приложение-ресивер не обязано быть
запущено, при поступлении Broadcast Intent оно запустится
автоматически
Intents: Broadcast Intents
2. Зарегистрировать его в AndroidManifest.xml:
<receiver android:name=”. MyBroadcastReceiver ”>
<intent-filter>
<action android:name=” com.niit.android.intent.action.NEW_LIFEFORM”/>
</intent-filter>
</receiver>
или в коде:
public static final String NEW_LIFEFORM_DETECTED =
“com.niit.android.intent.action.NEW_LIFEFORM”;
IntentFilter filter = new IntentFilter(NEW_LIFEFORM_DETECTED);
MyBroadcastReceiver r = new MyBroadcastReceiver ();
registerReceiver(r, filter);
3. Когда ресивер больше не нужен его нужно разрегистрировать:
unregisterReceiver(r);
Intents: Native Broadcast Actions
•
ACTION_BOOT_COMPLETED
•
ACTION_CAMERA_BUTTON
•
ACTION_DATE_CHANGED
•
ACTION_TIME_CHANGED
•
ACTION_GTALK_SERVICE_CONNECTED
•
ACTION_GTALK_SERVICE_DISCONNECTED
•
ACTION_MEDIA_BUTTON
•
ACTION_MEDIA_EJECT
•
ACTION_MEDIA_MOUNTED
•
ACTION_MEDIA_UNMOUNTED
•
ACTION_SCREEN_OFF
•
ACTION_SCREEN_ON
•
ACTION_TIMEZONE_CHANGED
Полный список:
http://developer.android.com/reference/android/content/Intent.html
Services
Cервис (служба) – это программа, которая работает в фоне
и не использует UI. Запускать и останавливать сервис можно
из приложений и других сервисов. Также можно подключиться
к уже работающему сервису и взаимодействовать с ним.
Сервис может быть запущен двумя способами:
-
клиент (например, активность) вызывает метод Context.startService() . В этом
случае сервис начинает выполняться и выполняется до тех пор пока не будет
вызван метод Context.stopService() или stopSelf()
-
клиент вызывает метод Context.bindService(), чтобы получить доступ к сервису и
получает объект IBinder, который ему возвращает сервис из метода
onBind(Intent), через который и осуществляется взаимодействие с сервисом.
Сервис будет работать ровно столько времени, сколько клиент будет
удерживать соединение через IBinder и сервис будет остановлен, когда клиент
уничтожит объект. Возможно одновременное подключение нескольких клиентов
к сервису, в этом случае сервис работает пока остаются подключенные клиенты.
Services, processes, threads
Важно понимать:
1. Сервис - это не отдельный процесс. Сервис запускается
в рамках приложения частью которого он является
2) Сервис - это не поток. По умолчанию он запускается в
главном потоке приложения (hosting app). Это потенциально
может приводить к возникновению ANR (Application Not
Responding). Если в сервисе выполняются «тяжелые»
операции, то лучше выполнять их в отдельном потоке внутри
сервиса.
Если приложению необходимо выполнять операции в фоновом
режиме, но только во время работы приложения – лучше
создавать поток (или AsyncTask), а не сервис!
Services
Services
Started
(приложение
стартует и
завершает сервис)
Bound
(приложение
подключается
(bind) и
отключается
(unbind) от
сервиса)
Сервис может быть одновременно и started и bound
Services: lifecycle
Чтобы создать сервис надо унаследоваться от класса
Service и переопределить коллбэки (не обязательно все):
• onStartCommand() – вызывается, когда клиент вызывает
startService(). Остановка сервиса в этом случае
выполняется вручную методом stopSelf() или stopService()
• onBind() – вызывается, когда клиент подсоединяется к
сервису методом bindService(). В имплементации
необходимо предоставить интерфейс, который клиенты
используют для связи с сервисом (IBinder). Этот метод надо
реализовывать всегда, если сервис не допускает коннекта
надо просто вернуть null.
• onCreate() – вызывается только при первом обращении к
сервису (на создание или на коннект) перед методами
onStartCommand() или onBind(), если сервис уже запущен не вызывается.
• onDestroy() – вызывается когда сервис больше не нужен
(нет ни одного коннекта или был вызван stopService()),
выполняет все действия по освобождению ресурсов
(остановка потоков, разрегистрация слушателей и т.д.)
Services: lifecycle
Services: lifecycle
public class ExampleService extends Service {
int mStartMode;
// indicates how to behave if the service is killed
IBinder mBinder;
// interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
public void onCreate() {
// The service is being created
}
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called }
public void onDestroy() {
// The service is no longer used and is being destroyed }
}
Services: manifest
<manifest ... >
...
<application ... >
<service
android:name="string"
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:permission="string"
android:process="string" >
<intent-filter>
…
</intent-filter>
</service> </application>
</manifest>
Services: started services
Intent intent = new Intent(this, ExampleService.class);
startService(intent);
public class ExampleService extends Service {
…
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// If we get killed, after returning from here, restart
return START_STICKY;
}
}
Services: started services
Intent intent = new Intent(this, ExampleService.class);
stopService(intent);
или
public class ExampleService extends Service {
…
public void processServiceCode() {
…
…
stopSelf();
}
}
Services: bound services
Bound service – позволяет клиентам подключаться к нему,
посылать запросы, получать ответы и т.п. Все это возможно
как на уровне одного процесса (Local Service) так и между
процессами через механизм IPC (Remote Service)
Bound service выполняется пока есть хотя бы один подключенный к
нему клиент.
Для создания bound service необходимо переопределить метод onBind()
в классе, унаследованном от Service. Этот метод должен вернуть
объект IBinder, определяющий интерфейс для взаимодействия
клиентов с сервисом.
Клиент подключается к сервису с помощью метода bindService(). Он
должен имплементировать интерфейс ServiceConnection, который
следит за соединением. Метод bindService() асинхронный
(возвращается немедленно), но когда соединение установлено
вызывается коллбэк onServiceConnected() реализации
ServiceConnection, в который приходит объект IBinder.
Services: bound services
Если к сервису подключаются несколько клиентов, onBind()
вызывается только один раз для первого клиента. Для
остальных клиентов возвращается тот же самый объект
IBinder.
Когда последний клиент отключается от сервиса, система
выгружает сервис из памяти (если он не был одновременно
запущен методом startService()).
Services: local bound services
Порядок создания:
1) На стороне сервиса:
• создать экземпляр класса, унаследованного от
Binder, который либо сам содержит public методы,
для вызова из клиента, либо возвращает ссылку на
объект Service, который содержит public методы
• вернуть из метода onBind() экземпляр Binder
2) На стороне клиента:
• в методе onServiceConnected() получить экземпляр
Binder, через этот объект получить ссылку на сервис
и вызывать методы сервиса.
Services: local bound services
class Client extends Activity
{ LocalService mService;
boolean mBound = false;
MyServiceConnection mConnection = MyServiceConnection();
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
class MyServiceConnection implements ServiceConnection
{
public void onServiceConnected(ComponentName className, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
class LocalService extends Service
mService = binder.getService();
{ private final IBinder mBinder = new LocalBinder();
mBound = true;
public IBinder onBind(Intent intent) {
}
return mBinder;
}
}
}
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
}
Services: remote bound services
Порядок создания:
1) На стороне сервиса:
• сервис реализует интерфейс Handler, в котором
обрабатываются сообщения от клиентов
• Handler используется для создания объекта
Messenger
• в методе onBind() с помощью Messenger создается
IBinder и возвращается клиенту
• получать сообщения от клиентов и обрабатывать их
в методе handleMessage()
2) На стороне клиента:
• клиенты используют полученный IBinder для
создания своего объекта Messenger, который будет
использоваться для отправки сообщений сервису
Services: remote bound services
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast t = Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT);
t.show();
break;
default:
super.handleMessage(msg);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
Services: remote bound services
public class ActivityMessenger extends Activity {
Messenger mService = null;
boolean mBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
Services: remote bound services
public class ActivityMessenger extends Activity {
…
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
Services: remote bound services
public class ActivityMessenger extends Activity {
…
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
Services: remote bound services
Важно:
1. Всегда необходимо обрабатывать RemoteException, когда
возникает когда связь с сервисом обрывается.
2. Нужно отключаться от сервиса, когда он больше не нужен
• Если сервис нужен пока активность видна на экране,
нужно подключаться в onStart() и отключаться в onStop()
• Если сервис нужен даже когда активность в бэкграунде,
нужно подключаться в onCreate() и отключаться в
onDestroy()
• Не рекомендуется подключаться в onResume() и
отключаться в onPause(), т.к. эти методы выполняются
слишком часто
Services: started & bound services
Сервис может быть одновременно и started и bound. Т.е. его
можно запустить методом startService() или подключиться к нему
методом bindService()
В этом случае в коде сервиса необходимо переопределять и
onBind() и onStartCommand(). Система не будет разрушать сервис
при отключении всех клиентов, если он был запущен через
startService(), вместо этого он должен останавливаться сам через
методы stopSelf() или stopService().
Также, если сервис и started и bound, когда вызывается
onUnbind() можно вернуть true если вы хотите, чтобы в
следующий раз при подключении клиента был вызван onRebind()
вместо onBind().
Services: started & bound services
Intent Services
Android API предоставляет класс IntentService,
расширяющий стандартный Service, но выполняющий
обработку переданных ему данных в отдельном потоке.
При поступлении нового запроса (в виде Intent,
пришедшего в onStartCommand()) IntentService сам
создаст новый поток и вызовет в нем метод
IntentService#onHandleIntent(Intent intent), который можно
переопределить. Если при поступлении нового запроса
обработка предыдущего еще не закончилась, он будет
поставлен в очередь. Когда последний Intent из очереди
обработан, сервис сам завершает свою работу.
Download