Дельта-синхронизация крипто-дисков

Существуют разные способы зашифровать "облако". Один из них - поместить в облако крипто-диск. В предыдущей статье мы писали, почему это не всегда удобно.

Смотрим плавность хода с помощью BMW Rheingold

Всем знакома ситуация, когда двигатель немного "троит", но пропусков зажигания нет...

Дельта синхронизация без облака

Ранее мы показывали разные способы синхронизации криптодиска между ПК и Android-устройством.

Облачный хостинг VDS за 2 минуты

Настоящий облачный VDS-хостинг от UltraVDS: тестируем производительность

Сервисы и потоки


Сервисы и потоки

© Денис Колисниченко

Введение в потоки

Потоки позволяют выполнять несколько задач одновременно, что позволяет более эффективно использовать системные ресурсы. Потоки удобно использовать для фонового выполнения какой-то части программы. Самый простой пример многопотоковой программы — это музыкальный проигрыватель. Такая программа запускается и отображает список файлов и кнопки управления воспроизведением. Вы нажимаете кнопку Play и запускается отдельный поток — поток воспроизведение композиции. Но ведь программа должна обрабатывать и нажатия других кнопок, например, кнопок Stop и Pause. Иначе бы после нажатия кнопки Play не было бы возможности остановить, приостановить воспроизведение или переключиться на другую композицию. Благодаря потокам такая возможность у нас есть.

Запуск потока

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

Листинг 10.1. Версия обработчика кнопки Play (без потоков)

Button playBtn = (Button) findViewById(R.id.play);

playBtn.setOnClickListener(new View.OnClickListener() {

public void onClick(View view){

play();

}

});

Как видите, ничего сверхъестественного нет. Просто вызывается функция play(). Теперь реализуем то же самое, но с запуском функции play() в отдельном потоке. Первым делом нужно создать поток:

Thread Thread1 = new Thread(

// здесь будет описание объекта Runnable

);

Далее нужно описать объект Runnable, это делается в конструкторе потока:

new Runnable() {

public void run() {

play();

}

}

Внутри потока мы будем вызывать функцию play(). Затем запускаем поток:

Thread1.start();

Полный код создания потока выглядит так:

Thread Thread1 = new Thread(

new Runnable() {

public void run() {

play();

}

}

);

Thread1.start();

Что будет делать функция play()? Все, что нужно сделать в потоке, то есть код этой функции зависит от создаваемой программы. В нашем случае будет создан объект класса MediaPlayer и вызван метод start() для воспроизведения музыки. Примерно так:

MediaPlayer music = new MediaPlayer();

music = MediaPlayer.create(this, R.raw.music1);

music.start();

Метод sleep() позволяет отправить поток "в сон". Продолжительность сна указывается в миллисекундах (1000 мс = 1 с):

Thread1.sleep(1000);

Приоритет потока

Для установки приоритета процесса используется метод setPriority(), который нужно вызвать до метода start(). Значение приоритета может лежать в диапазоне от Thread.MIN_PRIORITY (1) до Thread.MAX_PRIORITY (10):

Thread1.setPriority(10);

Thread1.start();

Прерывание потока

Мы должны позаботиться об останове потока. Использовать метод stop() не рекомендуется, поскольку он оставляет приложение в неопределенном состоянии. Правильнее поток Thread1 завершать так (измените идентификаторы на идентификаторы вашего потока):

if(Thread1 != null) {

Thread dummy = Thread1;

Thread1 = null;

dummy.interrupt();

}

Существует и другой способ. Он заключается в том, что все запускаемые потоки вы объявляете демонами. В этом случае все запущенные потоки будут автоматически завершены при завершении основного потока приложения. Как по мне, это самый удобный вариант:

Thread1.setDaemon(true);

Thread1.start();

Класс Handler

При сложных вычислениях может понадобиться очередь Runnable-объектов. Помещая такой объект в очередь, вы можете задать время его запуска, например, спустя какое-то количество миллисекунд после помещения или же указать точное время запуска объекта.

Для демонстрации использования обработчика потока сейчас мы разработаем программу, запускающую фоновый процесс, который будет каждые 200 мс получать текущее время и обновлять текстовую надпись в главной деятельности приложения. В то же время у нас есть кнопка Start, которую вы можете нажать. После первого нажатия надпись "Start" заменяется числом — количеством нажатия кнопки. Вы можете нажимать кнопку Start — будет выполняться какое-то действие, в нашем случае — это просто изменение надписи кнопки. В тоже время в фоновом режиме наш поток обновляет текстовую надпись.

Создайте новый проект. Файл разметки приложения приведен в листинге 10.2.

Листинг 10.2. Разметка приложения

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

<TextView android:id="@+id/text"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

<Button android:id="@+id/start"

android:text="Start"

/>

</LinearLayout>

Файл кода приложения приведен в листинге 10.3.

Листинг 10.3. Файл кода приложения

package com.samples.my_timer;

import android.app.Activity;

import android.os.Bundle;

import android.os.Handler;

import android.os.SystemClock;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class Timer1 extends Activity {

private int buttonPressCount = 0;       // Счетчик нажатий кнопки

TextView buttonLabel;

private long sTime = 0L;                       // Счетчик времени

private TextView TimeLabel;

// Обработчик потока: обновляет сведения о времени

private Handler Handler1 = new Handler();

 

@Override

public void onCreate(Bundle savedInstanceState) {

 

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

if (sTime == 0L) {

sTime = SystemClock.uptimeMillis();

Handler1.removeCallbacks(TimeUpdater);

// Добавляем Runnable-объект TimerUpdater в очередь

// сообщений, объект будет запущен после

// задержки в 150 мс.

Handler1.postDelayed(TimeUpdater, 150);

}

TimeLabel = (TextView) findViewById(R.id.text);

buttonLabel = (TextView) findViewById(R.id.start);

Button startButton = (Button) findViewById(R.id.start);

startButton.setOnClickListener(new View.OnClickListener() {

// Обработчик нажатия кнопки

public void onClick(View view){

buttonLabel.setText(++buttonPressCount);

}

});

}

// Описание Runnable-объекта — нашего потока

private Runnable TimeUpdater = new Runnable() {

public void run() {

       // Вычисляем время

final long start = sTime;

long millis = SystemClock.uptimeMillis() — start;

int sec = (int) (millis / 1000);

int min = seconds / 60;

seconds = seconds % 60;

// Выводим время

TimeLabel.setText("" + min + ":"

+ String.format("%02d",sec));

// Задержка в 300 мс

Handler1.postDelayed(this, 300);

} // void run()

};

@Override

protected void onPause() {

       // Удаляем Runnable-объект

Handler1.removeCallbacks(TimeUpdater);

super.onPause();

}

@Override

protected void onResume() {

super.onResume();

       // Добавляем Runnable-объект

Handler1.postDelayed(TimeUpdater, 150);

}

}

Самый простой способ помещения объекта в очередь — метод post(), когда указывается только помещаемый объект, но не указывается время выполнения объекта:

post(Runnable r)

Подробно об обработчиках потока вы можете прочитать в руководстве разработчика:

https://developer.android.com/reference/android/os/Handler.html

Сервисы

Сервис (служба, service) — компонент Android-приложения, запускаемый в фоновом режиме без всякого интерфейса пользователя. Сервис может быть запущен или остановлен любым компонентом приложения. Пока сервис запущен, любой компонент может воспользоваться предоставляемыми сервисом возможностями.

Приведем примеры использования сервисов. Одна деятельность запускает сервис, который загружает изображения на сайт. Другая деятельность подключается к этому сервису с целью узнать, сколько файлов уже загружено, чтобы отобразить эту информацию пользователю. Аналогично, можно использовать сервисы при копировании файлов с одной SD-карты на другую - деятельность может подключаться к службе копирования файла, чтобы узнать, сколько байтов было скопировано. В случае с музыкальным проигрывателем деятельность может подключаться к сервису, чтобы узнать позицию воспроизводимого трека.

Рассмотрим общий алгоритм создания службы. Первым делом нужно создать класс, расширяющий класс Service. В Eclipse для этого нужно щелкнуть правой кнопкой мыши на проекте и выбрать команду New, Class. В качестве супер-класса нужно указать класс android.app.Service (см. рис. 10.1).

Рис. 10.1. Создание класса службы

По умолчанию будет создана заготовка класса сервиса, представленная в листинге 10.4.

Листинг 10.4. Заготовка класса сервиса

package com.samples.my_first_service;

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

public class MyService extends Service {

       @Override

       public IBinder onBind(Intent arg0) {

             // TODO Auto-generated method stub

             return null;

       }

}

В файле манифеста приложения вам нужно описать сервис:

<service android:name=".myService"></service>

Элемент <service> добавляется в элемент <application>. Пример файла манифеста представлен в листинге 10.5.

Листинг 10.5. Пример файла манифеста для приложения с компонентом Service

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="https://schemas.android.com/apk/res/android"

package="com.cookbook.simple_service"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon"

android:label="@string/app_name">

<activity android:name=".SimpleActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<service android:name=".SimpleService"></service>

</application>

<uses-sdk android:minSdkVersion="3" />

</manifest>

Затем вам нужно переопределить методы onCreate() и onDestroy(). Для этого щелкните правой кнопкой на файле класса в Eclipse и выберите команду Source, Override/Implements Methods.

Метод onBind() нужно переопределять в том случае, если новый компонент привязывается к этому сервису после его создания.

Запустите службу функцией startService(). Остановить сервис можно функцией stopService(). Представим, что у нас есть деятельность MainActivity с кнопками Start и Stop. Кнопка Start запускает сервис MyService, описанный в классе MyService (файл MyService.java). Обработчик нажатия этой кнопки будет выглядеть так:

startButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view){

startService(new Intent(MainActivity.this,

MyService.class));

}

});

Обработчик кнопки Stop будет таким:

stopButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View v){

stopService(new Intent(MainActivity.this,

MyService.class));

}

});

В листинге 10.6 приведена расширенная заготовка сервиса. Наш сервис при запуске и останове выводит соответствующее уведомление.

Листинг 10.6. Расширенная заготовка сервиса

package com.samples.my_first_service;

import android.app.Service;

import android.content.Intent;

import android.media.MediaPlayer;

import android.os.IBinder;

import android.widget.Toast;

public class MyService extends Service {

@Override

public IBinder onBind(Intent arg0) {

return null;

}

@Override

public void onCreate() {

super.onCreate();

Toast.makeText(this,"Service started...",

Toast.LENGTH_LONG).show();

}

@Override

public void onDestroy() {

super.onDestroy();

Toast.makeText(this, "Service destroyed...",

Toast.LENGTH_LONG).show();

}

}

10.3. Класс BroadcastReceiver

Широковещательные приемники прослушивают широковещательные сообщения системы, например, сообщение о срабатывании (нажатии) кнопки камеры, о низком заряде батареи, об установке нового приложения или о наличии обновления приложения и т.д.

Стандартные широковещательные сообщения следующие:

  • ACTION_TIME_TICK - изменение времени, генерируется каждую минуту.
  • ACTION_TIME_CHANGED - установлено новое время.
  • ACTION_TIMEZONE_CHANGED - установлен новый часовой пояс.
  • ACTION_BOOT_COMPLETED - загрузка выполнена.
  • ACTION_PACKAGE_ADDED - установлено новое приложение.
  • ACTION_PACKAGE_CHANGED - приложение (пакет) было изменено, например, включен/выключен какой-то компонент.
  • ACTION_PACKAGE_REMOVED - пакет (приложение) был удален.
  • ACTION_PACKAGE_RESTARTED - приложение было перезапущено.
  • ACTION_PACKAGE_DATA_CLEARED - очищены данные приложения.
  • ACTION_BATTERY_CHANGED - информация об изменении заряда батареи.
  • ACTION_POWER_CONNECTED - подключено внешнее питание.
  • ACTION_POWER_DISCONNECTED - отключено новое питание.
  • ACTION_SHUTDOWN - началось завершение работы.

Дополнительную информацию о событиях можно получить по адресу:

https://developer.android.com/reference/android/content/Intent.html

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

Широковещательный приемник — это объект класса BroadcastReceiver или одного из его подклассов. Самый главный метод этого класса — onReceive(), который вызывается, когда приемник получает сообщение.

Рассмотрим пример запуска службы на основании получения широковещательного сообщения — нажатия кнопки камеры. Приемник будет прослушивать сообщения, фильтр намерения будет установлен на действие Intent.ACTION_CAMERA_BUTTON, которое соответствует нажатию кнопки камеры.

Зарегистрировать приемник можно функцией registerReceiver(), а удалить приемник — функцией unregisterReceiver().

Итак, начнем создавать наше приложение. Код основной деятельности MainActivity представлен в файле MainActivity.java (лист. 10.7).

Листинг 10.7. Код основной деятельности (MainActivity.java)

package com.samples.my_receiver;

import android.app.Activity;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

public class MainActivity extends Activity {

       // Создаем объект iReceiver класса MyReceiver

MyReceiver iReceiver = new MyReceiver();

/** Вызывается, когда деятельность запускается впервые. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Создаем фильтр намерения

IntentFilter iFilter = new IntentFilter(Intent.ACTION_CAMERA_BUTTON);

             // Добавляем действие в фильтр

iFilter.addAction(Intent.ACTION_PACKAGE_ADDED);

// Регистрируем приемник

registerReceiver(iReceiver, iFilter);

}

@Override

protected void onDestroy() {

       // Уничтожаем приемник при завершении приложения

unregisterReceiver(iReceiver);

super.onDestroy();

}

}

Разберемся, что есть что. Первым делом мы объявили объект iReceiver класса MyReceiver. Этот класс будет описан позже. Затем мы создали фильтр намерения:

IntentFilter iFilter = new IntentFilter(Intent.ACTION_CAMERA_BUTTON);

iFilter.addAction(Intent.ACTION_PACKAGE_ADDED);

После этого нужно только зарегистрировать приемник с помощью registerReceiver(). Первый параметр — это сам приемник, второй — фильтр намерений:

registerReceiver(iReceiver, iFilter);

Теперь нужно создать класс MyReceiver. Как это сделать с помощью Eclipse, вы уже знаете, поэтому привожу сразу код класса (лист. 10.8).

Листинг 10.8. Код класса MyReceiver (файл MyReceiver.java)

package com.samples.my_receiver;

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

// Класс MyReceiver является расширением класса BroadcastReceiver

public class MyReceiver extends BroadcastReceiver {

       // Переопределяем метод onReceive()

@Override

public void onReceive(Context rcvContext, Intent rcvIntent) {

String action = rcvIntent.getAction();

// Если действие = Intent.ACTION_CAMERA_BUTTON

if (action.equals(Intent.ACTION_CAMERA_BUTTON)) {

       // то запускаем сервис MyService

rcvContext.startService(new Intent(rcvContext,

MyService.class));

}

}

}

Приемник анализирует полученное действие. Если нажата кнопка камеры, то будет запущен сервис (служба) MyService, описанный в файле MyService.java. Код этого файла представлен в листинге 10.6. Вам нужно изменить только первую строчку, она должна выглядеть так:

package com.samples.my_receiver;