2011年12月30日金曜日

Android Service を自動的に再起動する方法

Androidで常駐するアプリを作るときはサービスを用いて基本的にはずっと起動させておく。ところが以下の場合にはサービスが停止されてしまう。その場合に再起動させる方法を以下に示す。


1) Android OSがメモリ等リソースが少なくなると強制的に停止する場合がある。
Service.onStartCommandの戻り値を START_STICKY 又は START_REDELIVER_INTENT にすることで、OSが勝手に再起動してくれる。


2) 電源が落とされた場合。
Intent.ACTION_BOOT_COMPLETED ブロードキャストを受けるレシーバを作成しそこからサービスを起動する。

Manifestには

<receiver android:name=".BootUpReceiver"
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
     </intent-filter>
</receiver>

等と記述。注意点としてRECEIVE_BOOT_COMPLETEDのパーミッションを取る必要がある。

このブロードキャストを受けるクラスとして

public class BootUpReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            context.startService(new Intent(context, SomeService.class));
        }
    }
}

等としてサービスを起動しましょう。


***** 2012-10-21追記 *****
Android 4.04のスマフォ(XPERIA GXです)でテストしたところ、Intent.ACTION_BOOT_COMPLETED ブロードキャストが受け取れなくなっていた。
Manifestに


  <uses-permission
    android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>


を追加することで解決した。


3) アプリケーションをアップデートした場合。
Intent.ACTION_PACKAGE_REPLACED ブロードキャストを受けるレシーバを作成しそこからサービスを起動する。

Manifestには

<receiver android:name=".PackageReplacedReceiver">
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_REPLACED" />
        <data android:scheme="package" android:path="your.package.path" />
    </intent-filter>
</receiver>

等と記述。自分のものだけ受け他のアプリのものは除くようにandroid:pathを設定すること。

このブロードキャストを受けるクラスとして

public class PackageReplacedReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
             context.startService(new Intent(context, SomeService.class));
        }
    }
}

等としてサービスを起動しましょう。


***** 2012-1-17追記 *****
どうもandroid:pathを設定しても全てのアプリのアップデートを受けてしまう様子。PackageReplacedReceiverクラスの方で



public class PackageReplacedReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
         if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
             if (intent.getDataString().equals("package:your.package.path")) {
                context.startService(new Intent(context, SomeService.class));
             }
        }
    }
}

とIntentのDataをチェックしてからサービスを起動しましょう。


2011年12月25日日曜日

JavaのTimerとTimerTaskの罠

Androidアプリで定期的に行う処理を起動するのに

java.util.Timer



java.util.TimerTask

を使用してハマった件のメモ。

スマホの時刻を手動で一時的に先の時刻に変更すると(例えば今8時だったら9時とか)、時刻を元に戻したときTimerがまったく動かなくなる!

どうもTimer、TimerTaskはシステムの時刻を元に次に処理を起動する時刻を決めて、その時刻になったら処理を呼び出すという動作のようだ。そのため一時的に未来の時刻になるとその時刻を元に次に起動する時刻を設定してしまう。時刻を元に戻すとその未来の時刻まではうんともすんとも言わなくなってしまうようだ。

まあActivityのような画面に表示されているときだけ動作するものにはそれでも良いのだが、Serviceのようなバックグラウンドでずっと動くものには要注意である。
そういう場合は

Executors.newSingleThreadScheduledExecutor()
等で

ScheduledExecutorService

を作成しましょう。これなら上記の問題は無いです。

2011年7月10日日曜日

Tab ContentのActivityからServiceにbindする場合の注意

Tabで切り替えるActivityからServiceに以下の様にbindしようとすると、bindできない。

bindService(intent, connection, BIND_AUTO_CREATE);

解決法は

getApplicationContext().bindService(intent, connection, BIND_AUTO_CREATE);

とすること。
その場合はunbindも同様に

getApplicationContext().unbindService(connection);

としませう。

2011年6月5日日曜日

ssh(Ubuntu 11.04上)でX11転送する際の日本語入力ができない(かな漢字変換が動かない)件の解決法

sshでX11の転送を行ったとき(ssh -Y hogehoge)、日本語入力ができないという不具合に遭遇。その解決法のメモ(Ubuntu11.04どうしの通信)。
ローカル側の /etc/ssh/ssh_configに

SendEnv XMODIFIERS

リモート側の /etc/ssh/sshd_configに

AcceptEnv XMODIFIERS

を加えること。
リモート側は

sudo service ssh restart

でsshdを再起動すること。
こうすることでローカル側の環境変数 XMODIFIERS (Ubuntu 11.04(日本語版)ではデフォルトでは @im=ibus が設定されている)がリモート側に送られる。

2011年5月19日木曜日

Android Tab Layout の Icon についてのメモ

Tab Layoutのアイコンは低、中、高解像度のデバイス用にそれぞれアイコンを用意する。
アイコンのサイズ等の仕様はAndroid Developers の Icon Design GuidelinesTab Icons にかかれている。これによると


低解像度(ldpi) 中解像度(mdpi) 高解像度(hdpi)
アイコンサイズ 24 x 24 px 32 x 32 px 48 x 48 px
描画領域のサイズ 22 x 22 px 28 x 28 px 42 x 42 px
アイコンの置き場所
Android 2.0以上
res/drawable-ldpi-v5 res/drawable-mdpi-v5 res/drawable-hdpi-v5
アイコンの置き場所
Android 2.0未満
res/drawable-ldpi res/drawable-mdpi res/drawable-hdpi

アイコンの絵はサイズギリギリではなく、周囲に save margine として余白を置くよう推奨している。また絵が四角型の場合はバランスを取るためにもう少し小さくするように推奨している。

Androidでは2.0からタブアイコンのスタイルが大きく変わったので(どう変わったのかはよく知らない)、それぞれのアイコンの置き場所を表の様に分けている。注意点として Manifestファイルの <uses-sdk> の属性 android:targetSdkVersion を 5 以上(Android 2.0以上)に設定すること。たぶんこれでアイコンが上記の2つの場所に分けておかれていることを(Android 2.0以上の)システムが認識できる。

実際に(Illustrator、inkscape等で)アイコンを描く時には、各解像度のサイズの整数倍のサイズで書くように推奨している。 24, 32, 48 さらにメニュー用アイコンで使う72 px を考慮すると、これらの最小公倍数は lcm(24, 32, 48, 72) = 144 となる。よって144 x 144 px の整数倍のサイズの画像を作成し(例えば 864 x 864 px)、これを各サイズに縮小するよう勧めている。

2011年5月17日火曜日

Tab Layoutを使ってみる

Tab Layoutの使い方のメモ。ちょっと未来の自分のために(^_^;
内容は Android Developers の Tutorial と同じ。これを自分に分かりやすく(日本語で)書いたもの。

タブを使うときのレイアウトは以下のような構成になる。

TabHost <- root node, 一番上の親
    LinearLayout <- 以下の子を縦に配置する
        TabWidget <- タブが表示される領域
        FrameLayout <- タブによって切り替えられたコンテンツが表示される領域

まず最初に、メインのレイアウトファイル main.xml を以下の様につくろう。


<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="5dp">
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:padding="5dp" />
    </LinearLayout>
</TabHost>

ポイントは LinearLayout の orientation を "vertical" にすること、また TabWidget の id は "@android:id/tabs"、コンテンツを表示する FrameLayout の id は "@android:id/tabcontent" とする必要がある。これはこの id を使って親の TabHost がこれらを認識するため。

このサンプルでは3つの Activity をタブによって切り替える。外観は次の様になる。


お次はこれらの Activity を作ろう。
Activityは
  • ArtistsActivity
  • AlbumsActivity
  • SongsActivity
それぞれのソースコードは
ArtistsActivity.java:

package test.hellotabwidget;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class ArtistsActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView textview = new TextView(this);
        textview.setText("This is the Artists tab");
        setContentView(textview);
    }
}

AlbumsActivity.java:

package test.hellotabwidget;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class AlbumsActivity extends Activity {
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView textview = new TextView(this);
        textview.setText("This is the Albums tab");
        setContentView(textview);
    }
}

SongsActivity.java:

package test.hellotabwidget;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class SongsActivity extends Activity {
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView textview = new TextView(this);
        textview.setText("This is the Songs tab");
        setContentView(textview);
    }
}

それぞれの内容はほとんど同じで、表示する文字列だけが異なる。またこれらの Activity はここでは簡単のためにレイアウトファイル(*.xml)は使わない。直接 TextView を生成してコンテンツとして設定している。

次はタブに表示するアイコン。これは先のTutorialからコピーしたものを用いる。面倒なので3つのタブ全て同じアイコンとする。実際にはそれぞれに別のアイコンを用意する。
タブが選択されたときのアイコン(ic_tab_artists_grey.png)
非選択時のアイコン(ic_tagb_artists_white.png)

これらのアイコンを res/drawable/ に保存する。
さらに以下の xml ファイルを同じ res/drawable/に保存し、タブの選択・非選択時のアイコン画像を指定する。

ArtistsActivityのタブには
ic_tab_artists.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- When selected, use grey -->
    <item android:drawable="@drawable/ic_tab_artists_grey"
          android:state_selected="true" />
    <!-- When not selected, use white-->
    <item android:drawable="@drawable/ic_tab_artists_white" />
</selector>

同様に AlbumActivity には ic_tab_albums.xmlを、SongsActivity には ic_tab_songs.xml 用意する。内容はic_tab_artists.xml とまったく同じでよい(実際にはそれぞれに使用するアイコンを別に指定する)。

次はユーザーがアプリ起動時に呼び出す一番上の Activity を作成する。
HelloTabWidget.java:

package test.hellotabwidget;

import android.app.TabActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TabHost;

public class HelloTabWidget extends TabActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Resources res = getResources(); // Resource object to get Drawables
TabHost tabHost = getTabHost();  // The activity TabHost
TabHost.TabSpec spec;  // Resusable TabSpec for each tab
Intent intent;  // Reusable Intent for each tab

// Create an Intent to launch an Activity for the tab (to be reused)
intent = new Intent().setClass(this, ArtistsActivity.class);

// Initialize a TabSpec for each tab and add it to the TabHost
spec = tabHost.newTabSpec("artists").setIndicator("Artists",
res.getDrawable(R.drawable.ic_tab_artists))
.setContent(intent);
tabHost.addTab(spec);

// Do the same for the other tabs
intent = new Intent().setClass(this, AlbumsActivity.class);
spec = tabHost.newTabSpec("albums").setIndicator("Albums",
res.getDrawable(R.drawable.ic_tab_albums))
.setContent(intent);
tabHost.addTab(spec);

intent = new Intent().setClass(this, SongsActivity.class);
spec = tabHost.newTabSpec("songs").setIndicator("Songs",
res.getDrawable(R.drawable.ic_tab_songs))
.setContent(intent);
tabHost.addTab(spec);

tabHost.setCurrentTab(2);
}
}

ここでは通常の Activity ではなく TabActivity を継承する。
getTabHost() で TabHost を取得し、 newTabSpec(String) で TabHost.TabSpec を作成しそれぞれのタブの特性を設定する。そして addTab(TabSpec) でそれぞれのタブを追加していく。
最後にsetCurrentTab(int)で最初に表示されるタブをインデックスで指定する。

最後にManifestにこれらのActivityを追加するのを忘れないこと。
AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="test.hellotabwidget"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="6" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".HelloTabWidget"
 android:label="@string/app_name"
           android:theme="@android:style/Theme.NoTitleBar">
             <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
           
        <activity android:name=".ArtistsActivity" />
        <activity android:name=".AlbumsActivity" />
        <activity android:name=".SongsActivity" />
    </application>
</manifest>

精度

スマートフォンを操作したりしているときに歩数計アプリが誤動作しないように、一定時間カウントが続かないと歩いてると見なさない様にした。これでずいぶん誤動作が減ったんだけど、家の中でちょっと歩いたりとかがカウントされにくい。
難しいな~。

2011年5月16日月曜日

歩数計アプリ

歩数計のAndroidアプリを作っている。とりあえず動作確認のプロトタイプは作っていてまずまず動作する。次は公開するために、UIをきちんと作り込んだり、搭載する機能を決めたりする予定。6月半ばには公開できるかな?