Android Service チュートリアルとサンプル
*サービスとは、アンドロイドアプリケーションのコンポーネントで、以下のようなものを指します。
- アプリケーションは、ユーザーとのインタラクションがない状態で、長時間のオペレーションを実行したいと考えています。
- 他のアプリケーションに機能を提供するためのコンポーネントです。
サービスはどのような場合に使用するのですか?
長時間稼働する操作や、ライフサイクルの長いタスクがある場合には、サービスを使用します。長時間実行される操作はバックグラウンドで実行されるのが良い設計だと思います。
例えば、バックグラウンドのデータ同期タスクがあります。
この種のタスクでは、定期的または必要に応じて実行される「サービス」を作成することができます。システムアラームを利用することもできます。
そして、タスクが終了したら「サービス」を終了させることができます。
バックグラウンドでジョブを実行することで、メインの thread がこれらのタスクを実行する必要がなくなり、集中的なタスクが実行されるたびにユーザーインターフェイスがフリーズするのを防ぐことができます。
アクティビティー」の中でも新しい「スレッド」を立ち上げることはできますが、バックグラウンドでの操作を「サービス」に委ねる方が、はるかに優れたすっきりとした方法です。
サービスはどこで実行されるのですか?
サービス」を含むすべてのアンドロイドコンポーネントは、アプリケーションプロセスのメインの「スレッド」の中で実行されます。
サービス "のライフサイクルは "アクティビティ "のライフサイクルとは異なります。前者は後者よりも長時間稼働する処理に適しています。
長時間実行する操作をバックグラウンドの thread に移したら、service の中でその thread を起動して処理することができます。
ユーザーとのインタラクションを必要としない長時間動作のオペレーションは、サービスに委ねるのに最適な候補です。
サービスの利点
- サービス`は実行中の長い操作をユーザインターフェイスから切り離すためのより効率的なメカニズムを提供する。
サービスの使用例
(a). ユーザー入力のないサービスの利用
- 頻繁にネットワークを利用して更新情報を取得するアプリ。
- 音楽再生アプリ。
- メッセージングアプリ
(b). ユーザーが入力するサービスについて
- 写真共有アプリ。
Quick Service Snippets and How-to's
1. サービス`を起動する方法
最初の例では、クラス名を指定しています。
public static void startService(String className) {
try {
startService(Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
}
}
2つ目の例では、クラス自体とコンテキストが与えられています。
public static void startService(Class<?> cls, Context context) {
Intent intent = new Intent(context, cls);
context.startService(intent);
}
2. サービス」を停止する方法
サービス」を停止させたい場合、クラス名が与えられているとします。
public static boolean stopService(String className) {
try {
return stopService(Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
クラスそのものが提供されている場合はどうでしょうか。
public static boolean stopService(Class<?> cls, Context context) {
Intent intent = new Intent(context, cls);
return context.stopService(intent);
}
3. サービス`をバインドする方法
2つの例を紹介します。1つ目の例を紹介します。
public static void bindService(String className, ServiceConnection conn, int flags) {
try {
bindService(Class.forName(className), conn, flags);
} catch (Exception e) {
e.printStackTrace();
}
}
次に2つ目の例です。
public static void bindService(Class<?> cls, ServiceConnection conn, int flags,Context context) {
Intent intent = new Intent(context, cls);
context.bindService(intent, conn, flags);
}
4. Service のバインドを解除する方法
public static void unbindService(ServiceConnection conn,Context context) {
context.unbindService(conn);
}
5. 稼働中のサービスをすべて取得する方法
システムで動作しているすべての services を取得し、その名前を Set として返します。
public static Set getAllRunningService(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> info = activityManager.getRunningServices(0x7FFFFFFF);
Set<String> names = new HashSet<>();
if (info == null || info.size() == 0) return null;
for (RunningServiceInfo aInfo : info) {
names.add(aInfo.service.getClassName());
}
return names;
}
クイックサービスの例
1. サービス`の中のArrayListに曲を読み込む方法
ユーザーインターフェイスをフリーズさせたくないので、これはバックグラウンドの thread で行う必要があります。
ここでは曲をログに記録しますが、もちろんリサイクラービューやリストビューでレンダリングして、再生するクラスを実装することもできます。
まず、Songオブジェクトを定義することから始めましょう。これがSong Beanクラスです。
public final class Song {
private String name;
private String artist;
private String coverUri;
private long time;
private String location;
public String getName() {
return name;
}
public Song setName(String name) {
this.name = name;
return this;
}
public String getArtist() {
return artist;
}
public Song setArtist(String artist) {
this.artist = artist;
return this;
}
public String getCoverUri() {
return coverUri;
}
public Song setCoverUri(String coverUri) {
this.coverUri = coverUri;
return this;
}
public long getTime() {
return time;
}
public Song setTime(long time) {
this.time = time;
return this;
}
public String getLocation() {
return location;
}
public Song setLocation(String location) {
this.location = location;
return this;
}
@Override
public String toString() {
return String.format("name = %s, artist = %s, location = %s",
name,
artist,
location);
}
}
続いて、サービスの作成に入ります。
まず、Serviceを実装したクラスを作ります。
public class ScanMusicService extends Service {...}
通常、serviceが作成されると、onCreate()というライフサイクルメソッドがシステムから呼び出されます。
ここでは、バックグラウンドの thread で曲をスキャンして配列リストにロードする thread を開始します。
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<Song> songs = scanSongs(ScanMusicService.this);
LogUtils.d(songs);
ToastUtils.showLongToast(songs.toString());
}
}).start();
}
scanSongs()`メソッドは、曲をスキャンして配列リストにロードする役割を果たします。
これがクラスの全容です。
import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.IBinder;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import com.jiangkang.tools.bean.Song;
import com.jiangkang.tools.utils.LogUtils;
import com.jiangkang.tools.utils.ToastUtils;
import java.util.ArrayList;
public class ScanMusicService extends Service {
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
ArrayList<Song> songs = scanSongs(ScanMusicService.this);
LogUtils.d(songs);
ToastUtils.showLongToast(songs.toString());
}
}).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
private ArrayList<Song> scanSongs(Context context) {
ArrayList<Song> result = new ArrayList<>();
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
null);
if (cursor != null && !cursor.isClosed() && cursor.moveToFirst()) {
while (cursor.moveToNext()) {
String songName = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String location = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
Song song = new Song();
song.setName(songName)
.setArtist(artist)
.setLocation(location);
LogUtils.d(song.toString());
result.add(song);
}
}
return result;
}
}
これが、曲の読み込みを支援するクラスです。
2. System Download Manager と Service を使ったダウンロード方法
ここでは、System DownloadManagerを使って、データをダウンロードするサービスを作る方法を見てみたいと思います。
さっそくやってみましょう。まず、アンドロイドのサービスを作るところから始めます。
public class DownloadService extends Service {
次に、そのクラスの中にいくつかのクラスとインスタンスフィールドを作ります。
private BroadcastReceiver receiver;
private DownloadManager dm;
private long enqueue;
private String downloadUrl = "http://192.168.3.186/StaffAssistant.apk";
private static String apkname = "StaffAssistant.apk";
それは以下の通りです。
- BroadcastReceiver - ブロードキャスト受信機のインスタンスです。
- DownloadManager - 私たちのシステムのダウンロードマネージャー。
enqueue(long) - システムダウンローダによって割り当てられたユニークなダウンロードタスクのIDで、このIDを使ってダウンロードタスクの問い合わせや処理を行うことができます。
- ダウンロードURL` - apkをダウンロードするためのURL。
- ApK名 - アプリケーション名
ここで、serviceを拡張し、オーバーライドする必要のあるメソッドをまだ実装していないことを思い出してください。ここに1つあります。
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
ここでは null を返して、onStartCommand() に移ります。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
....
まず第一に、ダウンロードURLの文字列を Intent で受け取ります。
そして、BroadcastReceiverクラスをインスタンス化し、onReceive()メソッドをオーバーライドします。
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
install(context);
}
};
onReceive()メソッドの中では、install()`メソッドを呼び出しているのがわかります。このメソッドは、アプリケーションをインストールするために作成するカスタムメソッドです。
onReceive()`メソッドの外では、BroadcastReceiverを登録します。BroadcastReceiverはアンドロイドのコンポーネントなので。
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
明らかに、BroadcastReceiverを匿名のクラスとして作成したことで、プログラムで登録したことがわかります。
そして、後で作成する別のカスタムメソッドであるstartDownload()メソッドを呼び出します。
startDownload ( downloadUrl );
これにはSDカードの書き込み権限が必要であることに注意してください。targetSDKVersionが23以上であれば、ダイナミックパーミッションを使用することができます。
それでは、アプリをダウンロードした後、プログラムでインストールする方法を見てみましょう。
まず、install()メソッドを作成します。このメソッドはContextオブジェクトを受け取り、それによってstartActivity()メソッドを呼び出すことができます。
public static void install(Context context) {
次に、java.io.Fileをインスタンス化します。ファイルを作成する場所のパスとファイル名を渡します。
File file = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
, apkname);
そして、Intentオブジェクトを作成します。
Intent intent = new Intent(Intent.ACTION_VIEW);
ACTIVITY」環境では起動していないので、以下のようにラベルを設定します。
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
バージョンが7.0以上であるかどうか。
if (Build.VERSION.SDK_INT >= 24) {
次に,FileProviderクラスのgetUriForFile()メソッドを呼び出します。これらは以下の通りです。
- コンテキスト。
- プロバイダのホストアドレス
- インストールするファイル
Uri apkUri =
FileProvider.getUriForFile(context, "com.hzecool.slhStaff.fileprovider", file);
//Adding this sentence means temporarily authorizing the file represented by the Uri to the target application.
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
}
context.startActivity(intent);
}
次に、ServiceクラスのonDestroy()メソッドをオーバーライドします。ここでは、BroadcastReceiverの登録を解除しています。
@Override
public void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
それでは、ダウンロードの方法を見てみましょう。
まず、startDownload()メソッドを作ってみましょう。
private void startDownload(String downUrl) {
まず、システムのDownloadManagerを取得しましょう。
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
まず、DownloadManager.Requestクラスのインスタンス化から始めます。インスタンス化しながら、Uriクラスのparse()メソッドを使ってダウンロードURLを解析し、Requestクラスに渡します。
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downUrl));
次に、setMimeType()メソッドを使って、APKとしてのmimeタイプ(ファイルの種類)を設定します。
request.setMimeType("application/vnd.android.package-archive");
次に、setDestinationInExternalPublicDir()メソッドを使って、ダウンロードの宛先を設定します。ここでは、downloadsディレクトリを使用します。
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkname);
ダウンロードが完了したときに通知を表示するかどうかを示すために、setNotificationVisibility()を呼び出します。
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
次にタイトルを設定します。
request.setTitle("Download new version");
最後に、ダウンロードを実行して、タスクのユニークIDを返します。
enqueue = dm.enqueue(request);
}
}
これが service の完全なコードです。
import android.app.DownloadManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import java.io.File;
public class DownloadService extends Service {
private BroadcastReceiver receiver;
private DownloadManager dm;
private long enqueue;
private String downloadUrl = "http://192.168.3.186/StaffAssistant.apk";
private static String apkname = "StaffAssistant.apk";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
downloadUrl = intent.getStringExtra("downloadUrl");
if (TextUtils.isEmpty(downloadUrl)) {
apkname = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1, downloadUrl.length());
}
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
install(context);
}
};
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
startDownload(downloadUrl);
return Service.START_STICKY;
}
public static void install(Context context) {
File file = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
, apkname);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
Uri apkUri =
FileProvider.getUriForFile(context, "com.hzecool.slhStaff.fileprovider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
}
context.startActivity(intent);
}
@Override
public void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
private void startDownload(String downUrl) {
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downUrl));
request.setMimeType("application/vnd.android.package-archive");
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkname);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("Download new version");
enqueue = dm.enqueue(request);
}
}
フル サービス の例
ダウンロードして実行できる完全なスタンドアロンの例を見てみましょう。
1. シンプルなバウンド型サービスの例
MainActivity.java (メインアクティビティ)
これはメインの Activity です。このクラスはAppCompatActivityから派生したもので、ユーザーインターフェイスを担当しています。
ここでは、知られていないAPIの定義をいくつか紹介します。
(a). SeviceConnection。
これは、android.contentパッケージに存在するインターフェースで、アプリケーションserviceの状態を監視するためのものです。
(b). IBinder (b).
IBinder` はリモート可能なオブジェクトのベースとなるインターフェイスで、インプロセスおよびクロスプロセスコールを行う際に高いパフォーマンスを発揮するように設計された軽量のリモートプロシージャコールメカニズムのコア部分です。
package com.example.ankitkumar.boundservice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
BoundService myService;
boolean isBound = false;
TextView currenttime, time, date;
Button btn_start_service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Set the activity content from a layout resource
setContentView(R.layout.activity_main);
time = (TextView) findViewById(R.id.tv_time);
date = (TextView) findViewById(R.id.tv_date);
currenttime = (TextView) findViewById(R.id.textView_current_time);
btn_start_service = (Button) findViewById(R.id.button_start_service);
Intent intent = new Intent(MainActivity.this, BoundService.class);
/*
Connect to an application service
Automatically create the service as long as the binding exists
*/
bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
}
public void getcurrenttime(View view) {
currenttime.setText(myService.getCurrentTime());
time.setVisibility(View.VISIBLE);
date.setVisibility(View.VISIBLE);
}
//Setting Up BoundService
private ServiceConnection myConnection = new ServiceConnection() {
//Service Connected
public void onServiceConnected(ComponentName className,
IBinder service) {
BoundService.MyLocalBinder binder = (BoundService.MyLocalBinder) service;
myService = binder.getService();
isBound = true;
}
//Service is Disconnected
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
}
BoundService.java (バウンドサービス)
これは、serviceのサブクラスです。
package com.example.ankitkumar.boundservice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class BoundService extends Service {
//Base interface for a remotable object, the core part of a lightweight remote procedure call mechanism
private final IBinder myBinder = new MyLocalBinder();
//Binding Service
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return myBinder;
}
//Formatting and parsing dates in a locale-sensitive manner
public String getCurrentTime() {
SimpleDateFormat dateformat = new SimpleDateFormat("HH:mm:ss dd/MMM/yyyy", Locale.US);
return (dateformat.format(new Date()));
}
class MyLocalBinder extends Binder {
BoundService getService() {
return BoundService.this;
}
}
}
activity_main.xml これは activity のメインのレイアウトです。
メインの activity のレイアウトです。
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout
tools_context="com.example.ankitkumar.boundservice.MainActivity" android_background="#efedef" android_layout_height="match_parent" android_layout_width="match_parent" android_id="@+id/activity_main" >
<TextView
android_layout_height="wrap_content" android_layout_width="100sp" android_id="@+id/tv_time" android_visibility="gone" android_textStyle="bold" android_textSize="18sp" android_textColor="@android:color/background_dark" android_text="@string/time" android_gravity="end" android_fontFamily="sans-serif-condensed" android_layout_marginTop="40sp"/>
<TextView
android_layout_height="wrap_content"
android_layout_width="140sp" android_id="@+id/tv_date" android_visibility="gone" android_textStyle="bold" android_textSize="18sp" android_textColor="@android:color/background_dark" android_text="@string/date" android_gravity="bottom" android_fontFamily="sans-serif-condensed" android_layout_marginTop="40sp" android_layout_alignParentRight="true" android_layout_alignParentEnd="true"/>
<TextView
android_layout_height="wrap_content" android_layout_width="match_parent" android_id="@+id/textView_current_time" android_textSize="36sp" android_textColor="#ee6f64" android_gravity="center" android_fontFamily="sans-serif-condensed" android_layout_marginTop="70dp" android_hint="@string/time_label" android_layout_centerHorizontal="true"/>
<Button
android_background="#e54048" android_layout_height="60sp" android_layout_width="200sp" android_id="@+id/button_start_service" android_textStyle="bold" android_textSize="18sp" android_textColor="#ffff" android_text="@string/show_time" android_fontFamily="sans-serif-condensed" android_onClick="getcurrenttime" android_layout_centerInParent="true"/>
</RelativeLayout>
コードのダウンロードは以下の通りです。
| --- |
|---|
Android IntentService
public abstract class IntentService
extends Service
java.lang.Object
↳ android.content.Context
↳ android.content.ContextWrapper
↳ android.app.Service
↳ android.app.IntentService
IntentService は、非同期のリクエスト(Intents として表現される)を必要に応じて処理する Services のベースクラスです。クライアントは startService(Intent) 呼び出しでリクエストを送信します。サービスは必要に応じて起動され、ワーカー スレッド を使用して各 Intent を順に処理し、作業が終了すると自ら停止します。
この「ワークキュープロセッサ」パターンは、アプリケーションのメインの thread からタスクをオフロードするためによく使用されます。IntentServiceクラスは、このパターンを単純化し、その仕組みを提供します。これを使用するには、IntentServiceを継承してonHandleIntent(Intent)を実装します。IntentServiceはIntentを受信し、ワーカーthreadを起動し、必要に応じてservice` を停止します。
すべてのリクエストは単一のワーカー thread で処理されます。必要なだけ時間をかけて処理することができますが(アプリケーションのメインループをブロックすることはありません)、一度に処理されるリクエストは 1 つだけです。