Warning: include(/home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-content/advanced-cache.php): failed to open stream: No such file or directory in /home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-settings.php on line 74

Warning: include(/home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-content/advanced-cache.php): failed to open stream: No such file or directory in /home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-settings.php on line 74

Warning: include(): Failed opening '/home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-content/advanced-cache.php' for inclusion (include_path='.:/usr/local/php/5.4/lib/php') in /home/users/2/lolipop.jp-a-virtual/web/dhmo/dhmo/wp-settings.php on line 74
Android widgetで電池アプリを開発してみよう-① – Androidアプリつくったった

Android widgetで電池アプリを開発してみよう-①

android widget

◇Androidでウィジェットアプリを作成する

Androidにあってiphoneにない機能の一つです。
電池アプリの『ぬこバッテリー』や『はちゅね電池』などは
iphoneユーザからもうらやましがられます。

今回、紹介するソースは非常に汎用性が高いので
電池アプリにするもよし
ニュースフィードを流すもよしです。

まずウィジェットについて

ウィジェットアプリには以下の3つ特徴があります。
・ホームスクリーンに常駐できる
・同一ウィジェットを複数起動できる
・起動中に画面を占領しない

詳しくはこちらの公式のページにすべては書いてあります。
今回の記事で一度作った後にでも次のヒントを得るために見に行くのもいいと思います。

パソコンだとデスクトップ上に時計など表示されているかもしれません。
(Windows Vistaユーザだとガジェットとばれているもの)
Activityを用いるアプリケーションというよりは

今回の製作(サンプルソース)に付いて

>> まず最初のゴールは
かの有名なすべての言語で実装可能なプログラム
Hello Worldを表示するウィジェットを作成します。
ただ、Hello worldでは芸がないので
Hello worldを10秒毎に回数を更新します。

本記事での完成形は以下のようになります。
 
android widget

手順はどの制作でも同じですが以下の手順をお勧めします。
クラス名やサービス名の構成は簡単にでもいいので
メモに箇条書きしておいてから制作は始めましょう。

① manifestファイルの設定
② XMLファイルでレイアウト(idの設定)(+res/asset/lib内のファイルの準備)
③ Javaでの実装

早速制作していきたいと思います。

STEP1 manifestファイルの設定

初期はこんな感じだと思います。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.gr.java_conf.test_widget"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="jp.gr.java_conf.test_widget.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

今回、ランチャーアイコンは要らないのと
簡単のためにアクティビティはないものとするので
以下の部分を削除します。

        <activity
            android:name="jp.gr.java_conf.test_widget.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

代わりに追加するのが次のコードです。
の間に挿入しましょう。
簡単に説明するとAppWidgetProvider を継承した独自クラスを宣言し
そのクラスがACTION_APPWIDGET_UPDATE broadcast を受け取れるように で指定します。
Widget$MyServiceと@xml/appwidgetは制作したファイルやクラス名・サービス名に合わせ変更しましょう。
メタ情報は細かい指定が可能ですがandroid:previewImage は API Level 11以上が必要など
バージョンに気を付けて設定する必要があります。

        <service android:name="Widget$MyService" >
	</service>
	   <receiver android:name="Widget" >
		<intent-filter>
			<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
		</intent-filter>
		<meta-data
			android:name="android.appwidget.provider"
			android:resource="@xml/appwidget" />
	</receiver>

STEP2 XMLファイルでレイアウト(idの設定)(+res/asset/lib内のファイルの準備)

サイズ指定
XMLではサイズの指定ができます。
サイズの指定はホーム画面に縦横それぞれアイコンの何個分の
『長方形』にするかで指定可能です。
(通常のスマートフォン端末は4×4個のサイズと考えて良いでしょう)

例えばスマートフォン端末用のGoogleの検索窓のウィジェットは
横幅は一辺を占領していて、縦は他に3個分アイコンが置けるので
4×1のサイズです。

(4 x 74dip)– 2dip = 294dip
(1 x 74dip)– 2dip = 72dip
と指定することになります。

今回はそんなに幅あっても意味がないので
横幅、アイコン2個分
縦、アイコン1個分
とします。
細かいレイアウトは
指示したレイアウトファイルに従う事になります。

ソースは以下のようになります

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="146dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/main"
/>

それぞれの内容

android:minWidth ウィジェットの幅(dp)
android:minHeight ウィジェットの高さ(dp)
android:updatePeriodMillis 更新間隔(ms)
android:initialLayout 起動時のレイアウト(@layout/レイアウトファイル名)
android:configure ウィジェット設定画面を指定

※今回は設定画面がないのでandroid:configureは不要です。
 
◎ウィジェットのレイアウトファイルの作成
ウィジェットで使用できるレイアウトには制限があります。
以下の3つのみ利用可能です。
FrameLayout
Linearlayout
RelativeLayout

GUIコンポーネントも以下の7種類のみ利用可能です。

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView

これを踏まえて

今回はRelativeLayoutでさっくりこんな感じで作ります。
ソースはこんな感じです。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_ll"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/ImageViewId"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:src="@drawable/penshin" >
    </ImageView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical" >

        <TextView
            android:id="@+id/textView2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dip"
            android:layout_weight="1"
            android:text="設置されました"
            android:textColor="#0000FF"
            android:textSize="12sp" />
    </LinearLayout>

</RelativeLayout>

画像で使用しているペンギンはこんな画像です。
penshin
イメージやキャラクターの作成・利用の希望ございましたら
お問い合わせください。右上のアドレスからお問い合わせください。

STEP 3 Javaでの実装

◎ウィジェットのおおまかな解説

ウィジェットは、AppWidgetProviderを継承して作ります。
ウィジェットの見た目の変更・更新はRemoteViewで行います。
ウィジェットのクリックイベントはPendingIntentを利用して行います。
PendingIntentとはタイミングを指定して発行することができる特別なIntentです。
一般のアプリケーション作成でも添付処理など処理を待って実行する場合に利用するIntentです。

◎ウィジェットのライフサイクル
アクティビティに比べ画面の見える見えないといった区分けがなく
どちらかというとサービスに近いので基本的には

(1)onEnabled
1つ目のウィジェットがホームに追加されるときまず呼ばれます。
2つ目以降はこのonUpdateだけがコールされます。
(メモウィジェットなど2つ以上が想定される場合は注意が必要です。)

(2)onUpdate
ウィジェットがホームに追加されるときによばれます。

(3)onDeleted
ウィジェットがホームから削除されるとき。
他に同じウィジェットが残っているときはこのonDeletedだけがよばれます。

(4)onDisabled
ホームから最後のウィジェットが削除されるとき、onDeletedの後によばれます。

といった流れとなります。

それではソースを公開させて頂きます。
雛型として使って頂いて構いません。

内容としては
1.更新時間の設定が可能
2.アプリ上の情報の書き換えが可能
3.タスクキラーなどで強制終了させられても復活が可能
となっています。

以下がソースです。

public class Widget extends AppWidgetProvider {

	private final long interval = 10 * 1 * 1000; // 更新間隔
	private static final String ACTION_START_MY_ALARM = "jp.gr.java_conf..test_widget.ACTION_START_MY_ALARM";

	/** アラームマネージャをで更新間隔を設定する。 */
	private void setAlarm(Context context) {
		Intent alarmIntent = new Intent(context, Widget.class);
		alarmIntent.setAction(ACTION_START_MY_ALARM);
		PendingIntent operation = PendingIntent.getBroadcast(context, 0,
				alarmIntent, 0);
		AlarmManager am = (AlarmManager) context
				.getSystemService(Context.ALARM_SERVICE);
		long now = System.currentTimeMillis() + 1; // + 1 は確実に未来時刻になるようにする保険
		long oneHourAfter = now + interval - now % (interval);
		am.set(AlarmManager.RTC, oneHourAfter, operation);
	}

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		super.onUpdate(context, appWidgetManager, appWidgetIds);
		/*
		 * ホームボタンを押したら、receiveを呼び出す タスクキラーなどで、アプリを強制終了された際、 Widgetも終了してしまうため。
		 */
		context.getApplicationContext().registerReceiver(
				new BroadcastReceiver() {
					@SuppressWarnings("unused")
					public void onUpdate(Context context,
							AppWidgetManager appWidgetManager,
							int[] appWidgetIds) {
						setAlarm(context);
					}

					@Override
					public void onReceive(Context context, Intent intent) {
						Widget.this.onReceive(context, intent);
					}
				}, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));

		setAlarm(context);

		/** Click時のActivity */
		for (int i : appWidgetIds) {
			RemoteViews views = new RemoteViews(context.getPackageName(),
					R.layout.main);
			// Textをセットする。
			views.setTextViewText(R.id.textView1, "Hello World");
			appWidgetManager.updateAppWidget(i, views);
		}

	}

	@Override
	public void onReceive(Context context, Intent intent) {
		super.onReceive(context, intent);
		if (intent.getAction().equals(ACTION_START_MY_ALARM)) {

			if (ACTION_START_MY_ALARM.equals(intent.getAction())) {
				Intent serviceIntent = new Intent(context, MyService.class);
				context.startService(serviceIntent);
			}
			setAlarm(context);
		}
	}

	// 起動したServiceを停止する(1)
	@Override
	public void onDisabled(Context context) {
		super.onDisabled(context);
		Intent in = new Intent(context, MyService.class);
		context.stopService(in);
	}

	static int itemcount = 0;
	// サービスの内容
	public static class MyService extends Service implements LocationListener {
		private Thread runner;
		private volatile boolean keepRunning;

		@Override
		public void onCreate() {
			Log.v("Service", "onCrreated");
			keepRunning = true;
			runner = new Thread(null, new Runnable() {
				public void run() {
					workerLoop();
				}
			});
			runner.start();
		}

		private void workerLoop() {

			int cnt=0;
			// Main worker loop for the service
			while (keepRunning) {

				try {
						Thread.sleep(10000);
						cnt++;

				} catch (Exception e) {
				}
				RemoteViews remoteViews = new RemoteViews(getPackageName(),
						R.layout.main);
				remoteViews.setTextViewText(R.id.textView1,"Hello world\n" +cnt + "times\n");
				ComponentName thisWidget = new ComponentName(this, Widget.class);
				AppWidgetManager manager = AppWidgetManager.getInstance(this);
				manager.updateAppWidget(thisWidget, remoteViews);
			}
		}

		@Override
		public void onDestroy() {
			keepRunning = false;
			runner.interrupt();
		}

		@Override
		public int onStartCommand(Intent intent, int flags, int startId) {
			Log.v("TEST", "OK onStartCommand");;
			return super.onStartCommand(intent, flags, startId);
		}
		@Override
		public IBinder onBind(Intent intent) {
			return null;
		}


		@Override
		public void onProviderDisabled(String arg0) {
		}
		@Override
		public void onProviderEnabled(String arg0) {
		}
		@Override
		public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
		}

		@Override
		public void onLocationChanged(Location location) {
		}
	}
	@Override
	public void onDeleted(Context context, int[] appWidgetIds) {
		super.onDeleted(context, appWidgetIds);

	}
}

これで10経過後から10秒おきに実行されていくはずです。
次回はいよいよ電池アプリに改造していこうと思います。

以上です。

参考になれば幸いです。


About the Author

dhmo
Author:DHMO(ディベロッパー名) 仕事では自社サービス・メディアの開発を行ってます。 趣味でAndroidアプリ製作を行っています。 カラオケではランキングバトルにはまっております。 Mail: dihydromooxide7@gmail.com 

Be the first to comment on "Android widgetで電池アプリを開発してみよう-①"

Leave a comment

Your email address will not be published.


*