Showing posts with label 開発TIPS. Show all posts
Showing posts with label 開発TIPS. Show all posts

October 6, 2013

Windows8でAndroidアプリ開発環境構築

Windows8を導入したので、Androidアプリの開発環境を一から構築しました。

公式ドキュメントはAndroid Developersのここに掲載されているので、基本はここを参照すれば万事とどこおりなく作業は完了する

が、現時点でSystem requirementを見るとWindows8がない・・・しかもJDK6って、もうサポート終了で時代はJDK7なんですけど・・・古いセキュリティサポートの切れたJAVAなんて使いたくない・・・

ってとこだが、普通に最新のWindows8 + JDK7で動いた!良かった!
(Googleはさっさとドキュメント改訂しろと・・・なにか問題あるの???)

で、下記が手順です。前提はWindows8 PRO 64bitでSDKはADT Bundle for Windows版をインストールです。

まずはJDK7の最新版をインストール。32bit/64bitあるので自分のマシン環境にあわせて選択。自分のは64bitなので64bitを選択した。

続いて環境変数の設定(コンパネのシステムから詳細設定で環境変数を選択)
システム環境変数に対して、下記を設定
PATHを追加、例) C:\Program Files\Java\jdk1.7.0_40\bin
JAVA_HOMEを新規作成、例)C:\Program Files\Java\jdk1.7.0_40

そしたら本命のAndroid SDKをダウンロード
ZIPファイルなので適当に展開する。自分はCドライブ直下に展開。これは展開するだけで完了!
Eclipseも最新のSDKもすべて内蔵しているので、いきなり終了。これは楽ちん。

Eclipseを起動する
例)C:\adt-bundle-windows-x86_64\eclipse.exe
ショートカットは手動で作成してデスクトップなどへおけば便利

漏れなく記載できていれば、以上の手順で問題なく使えるはず



そして、エミュレーターでなく実際の端末を使ってアプリを開発するなら、Windowsの場合、もう1つやるべきことがある

WindowsはUSBドライバのインストールが必要!

USBドライバは各社で異なるので、自分の端末にあわせてドライバをインストールする
幸いなことにGoogleがここ(OEM USB Drivers)にまとめてくれている
基本Nexus系はGoogleのドライバ(サムソン除く)、それ以外は各社のドライバを使う

所望のドライバをダウンロードしたら、手動でインストールが必要

まずは端末をUSB接続するのだが、端末のUSBデバッグが有効になっていることを確認
詳しくはここを参照
Androidのversionによって有効にする方法が異なる(初期値はいずれもOFF)
特に4.2以降は隠されていて、記載されている特殊操作をしないとでてこないので要注意
(開発をしない普通の人に対して隠しモードにしたのかな?きっと)

有効にできたら、コンパネのデバイスマネージャーでUSBドライバを確認。?マークがついてるはずなので、ドライバのアップデートを実行し、フォルダ指定でダウンロードしたドライバのフォルダを指定する。ドライバが正しければ、うまくいく。

EclipseのRunタブにあるRun configurationsのTargetで、アプリ実行するときどの端末またはエミュレータで実行するかを設定できるので、適宜設定する

December 5, 2012

BACKキーで確認画面を表示する

BACKキーを押すと通常はそのActivityを終了して前のActivityに戻るといった動作をしますが、例えば、データ入力をするActivityでBACKキーを押されたとき、ポップアップを表示して入力をキャンセルして本当に前の画面に戻るかどうか促す、というUIにしたいときがあります。こういう処理をしたいときは、BACKキーのイベントをオーバーライドすることで自由に制御を変更して対応することができます。

例:BACKキーをおされたときにポップアップを表示してキャンセルか続行か促す




まずは、バックキーのイベントを取得して、ポップアップダイアログを表示します。ダイアログの結果を受け取って、キャンセルならfinish()でActivityを終了します。そうでなければ何もしません。

    @Override
    public boolean onKeyDown (int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            showAlert(mContext, new OnMyAlert() {
                @Override
                public void onResult(boolean isYes) {
                    if (isYes) {
                        finish();
                    }
                }
            });
        }
        return false;
    }

ポップアップダイアログを表示するメソッドです。Yes/Noの2つのボタンを表示して、結果を返します。

    private void showAlert(Context context, final OnMyAlert onMyAlert) {
        // Build
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(R.string.warning);
        builder.setMessage(R.string.mes_warn_cancel);
        // Left button
        builder.setPositiveButton(R.string.answer_no, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                onMyAlert.onResult(false);
            }
        });
        // Right button
        builder.setNegativeButton(R.string.answer_yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                onMyAlert.onResult(true);
            }
        });
        // Show
        builder.create().show();
    }

結果を渡すためのインターフェースです。

public interface OnMyAlert {
    public abstract void onResult(boolean isYes);
}

December 3, 2012

Intentにオブジェクトを付加して送信する

Intentを送るとき、さまざまな情報(intやStringなど)を付加することができますが、自分で作ったクラスのオブジェクトそのものを付加することもできます。

ただし、どんなクラスでも付加できるわけではなく、条件としてそのクラスがSerializableインターフェースを実装している必要があります。もちろんクラス内部のメンバー変数もすべてSerializableを実装したクラス、またはintなどのプリミティブである必要があります。

Serializableへの対応は特殊な使い方をしない限り、特に必須で実装するものはありません。通常は"implements Serializable"をクラスに追加し、serialVersionUIDを定義するだけです(eclipseで自動生成できます)。このUIDの値も気にする必要はありません。

例:クラスの定義
public class SampleClass implements Serializable {
    private static final long serialVersionUID = 1L;

例:intentを送る
public class SampleClass implements Serializable {
    SampleClass sample = new SampleClass();
    Intent intent = new Intent(getApplicationContext(), MyActivity.class);
    intent.putExtra("Foo", sample);
    startActivity(intent);

例:intentを受ける
    SampleClass sample = (SampleClass) getIntent().getSerializableExtra("Foo");

オブジェクトを送ることができると書きましたが、送り側と受け手側で同じ中身のオブジェクトが受け取れるという意味で、インスタンスは異なります。

参考:Android Developers:Serializable
参考:Android Developers:Intent

CharSequenceとStringの関係

コードの中で文字列の定義は通常Stringを使うと思いますが、UI系のメソッドをみると、文字列の受け取り側は大抵はStringでなくCharSequenceだったりします。が、Stringをそのまま渡すことができるので、その関係を整理してみました。

CharSequenceはインターフェースです。メソッドを見ると文字列を制御するための最小限のみ定義されています。Android Developersで確認すると多くのクラスがこれを実装しているのがわかります。


StringもCharSequenceを実装しているクラスなので、そのまま渡せるというわけです。


参考:Android Developers:CharSequence
参考:Android Developers:String

stringリソースからString文字列を取得する

文字列は通常はstringリソースファイルに記述します。このときリソースidであれば、R.string.XXXで直接取得できますが、文字列を取得する場合は、ContextにあるgetString(int resId)を使って取得します。

例:
getString(R.string.sample)

参考:Android Developers:Content - getString

November 28, 2012

スクリーンのON/OFFイベントを検出する


スクリーン(LCD)のON/OFFイベントを検出する方法です。

スクリーンのON/OFFはシステムからBroadcast intentで飛んできますので、それを取得することで対応できます。受信するにはIntentFilterに取得したいIntent(この場合はACTION_SCREEN_OFF)を指定してBroadcast receiverを登録します。例ではOFFの1つだけですが、複数登録することももちろんできます。Activityが有効な間受信したい場合はonCreateで登録し、onDestroyで解除します。受信するとBroadcastReceiverのonReceiveがコールされます。

例)ActivityでスクリーンOFFを検出します

public class SampleAppActivity extends Activity {
    private BroadcastReceiver screenStatusReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Receive screen off
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                // OFF
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Register a receiver to receive screen status updates
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        registerReceiver(screenStatusReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Unregister a receiver
        unregisterReceiver(screenStatusReceiver);
    }
}

参考:Android Developers:BroadcastReceiver

July 13, 2012

JellyBean(4.1)のソースコードを取得する

JellyBean(4.1)のソースコードがリリースされましたので、取得方法です。

$ repo init -u https://android.googlesource.com/platform/manifest -b android-4.1.1_r1
$ repo sync


今回はMacのターミナル上で実行しましたが、取得するにはrepoコマンドが必要です。
ホームフォルダにbinフォルダを作成して、そこにrepoコマンドをインストールしました。

$ mkdir bin
$ PATH=~/bin:$PATH
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod a+x ~/bin/repo

これでインストール完了です。

July 9, 2012

bindService()で始める、Binderクラスで作るService

Binderクラスを使って作る最も一般的なServiceの作成方法です。

bindService()を使ってServiceをBindし、Serviceで提供するユーザー関数を直接コールすることができます。Bindしている間Serviceは存在し、Unbindすると削除されます。複数のActivityからマルチでBindすることもできます。

bindService()を使ったServiceの作り方は3種類ありますが、今回紹介する方法が最も一般的です。なぜなら、アプリ内(正確にはServiceと同一プロセスで動くコンポーネントにServiceを提供する場合)でServiceを提供する場合はこの方法が最も適切かつ簡単なため、他のプロセスやアプリに機能を提供するなど特別なケースでない限り、他の方法を使う必要がないからです。

おおざっぱな流れは、Service側のクラスはBinderを拡張したユーザークラスを作成します。bindService()を実行するとonBind()がコールされるので、そのユーザーインスタンスを返します。呼び出し側のActivityではBindされるとServiceConnectionクラスのonServiceConnected()が呼ばれるので、ここでBinder経由でServiceインスタンスを取得します。あとはそのServiceインスタンスを使って、Service側でユーザー定義した関数を直接コールすることができます。

public class SampleService extends Service {
    private final static String TAB = "SampleService";
    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        SampleService getService() {
            return SampleService.this;
        }
    }

    @Override
    public void onCreate() {
        Log.i(TAB,"onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onDestroy() {
        Log.i(TAB,"onDestroy");
    }

    // Methods for client
    public void userFunction() {
        Log.i(TAB,"userFunction");
    }
}

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";
    private SampleService mSampleService;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAB,"onCreate");

        setContentView(R.layout.main);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mSampleService.userFunction();
            }
        });
    }
 
    @Override
    public void onStart() {
        Log.i(TAB,"onStart");
        super.onStart();
        // Bind
        Intent intent = new Intent(getApplicationContext(), SampleService.class);
        bindService(intent, mSampleServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onStop() {
        Log.i(TAB,"onStop");
        super.onStop();
        // Unbind
        unbindService(mSampleServiceConnection);
    }

    private ServiceConnection mSampleServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            SampleService.LocalBinder binder = (SampleService.LocalBinder)service;
            mSampleService = binder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
}

参考:Android Developers:Bound Services

July 6, 2012

startService()で始めるService

startService()で始めるServiceとは、intentを使ってServiceを起動し、バックグラウンドで指定された処理を実行するものです。

最も簡単に実装するには、Serviceクラスでなく、IntentServiceクラスを利用することです。その場合下記の例のようになります。ServiceクラスそのものはUIスレッドで動作するため、ブロックしないために通常はスレッドを作って、そちらで処理を実行しますが、IntentServiceはそのような面倒な処理がすべて実装されていて、コンストラクタとスレッドで実行する処理内容をonHandleIntent()をオーバーライドして実装するだけです。onHandleIntentはもちろん別スレッドで実行されます。

例:呼び出し側のstartService()

startService(new Intent(getApplicationContext(), SampleService.class));

例:受け取り側のService

public class SampleService extends IntentService {
    public SampleService() {
        super("SampleService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(TAB,"onHandleIntent");
        // Execute something...
    }
}

あえてService()で実装しなければいけないケースは、マルチスレッドで同時実行したい特殊な例だけです。IntentServiceはシングルスレッドのため、実行中に次の要求(startService())がきた場合はキューイングされ、逐次実行になります。すべての処理がおわるとIntentServiceは終了します。

参考までにあえてIntentServiceと同じ動作をするものをServiceを使って作成すると下記のようになります。

public class SampleService extends Service {
    private ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            // Execute something...

            // Stop the service with start id
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        // Create and start new thread and handler
        HandlerThread thread = new HandlerThread("SampleService", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceHandler = new ServiceHandler(thread.getLooper());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Create message
        Message msg = mServiceHandler.obtainMessage();
        // Set start id
        msg.arg1 = startId;
        // Send message
        mServiceHandler.sendMessage(msg);
        
        return START_STICKY;
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

簡単に説明するとonCreate()でThreadとHandlerを新規作成します。Handlerは継承したユーザークラスを作り、handleMessage()に処理を記述します。startService()するとonStartCommand()がコールされ、Handlerから生成したMessageをThreadに送信し、Thread上でそのMessageとともにhandlerMessage()が実行されます。

マルチスレッドにする場合はonCreate()ではなくonStartCommand()がくるたびにスレッドを生成するようにするなど工夫する必要があります。

参考:Android Developers:Services

July 5, 2012

プロセス情報を取得する

システム上で動作しているプロセス情報を取得する方法です。

デバッグなどでバックグラウンドで存在しているプロセスを調べるのに利用できます。

例)プロセス情報をすべて取得し、プロセスID、パッケージ名を表示します。

ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appList = manager.getRunningAppProcesses();
Log.i(TAB, "Total apps:" + appList.size());
for (RunningAppProcessInfo info:appList) {
    Log.i(TAB,"PID:" + info.pid + " Name:" + info.processName);
}

プロセス情報を取得するにはパーミッションの設定が必要です。

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

実行すると下記のように表示されます。


ちなみにプロセスを停止させる場合は、ActivityManagerのkillBackgroundProcesses(パッケージ名)を使います。停止させる場合もパーミッションの設定が必要です。

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

参考:Android Developers:ActivityManager

onDestroy()してもプロセスは死なない

アプリはプロセスが割り当てられて動作しますが、ひとたび起動したアプリは、たとえ全てのActivityがonDestroy()してもプロセスは死にません。タスクはなくなってもプロセスは残ったままになります。アプリのActivityがonDestroy()まで到達し、タスクもなくなれば完全終了した気になりますが、実はプロセスとしてはまだ生きています。

例えば1つのActivityを持つアプリがあったとして、onCreate()で無限ループのThreadを起動したとします。Activityをバックキーで終了させ、onDestroy()までコールされるのですが、このThreadはだれも終了させることなく動作し続けます。Threadのdaemonフラグに関係なく生き残る仕様のようです。

設定アプリの「アプリケーションの管理」で表示されるアプリに「強制停止」ボタンがありますが、これはプロセスを殺す、ということをしています。プロセスがなくなって初めて起動していない元の状態に戻ります。

ここでなにが言いたいかというと、バックグラウンドで動作するThreadなどをうっかり終了し忘れると、生き残って動き続けてしまう、ということです。当然CPUには負荷がかかり、アプリを終わったのに端末の処理が重い、というのはこういう不具合で発生します。アプリでバックグラウンド処理を実行する場合は、きちんと終了できているか十分に確認することが必要です。

July 4, 2012

アプリケーションとLooperとHandlerの関係

アプリケーションとLooperとHandlerの関係についての説明です。Androidフレームワークの内部に踏み込んだお話です。

「スレッドからUIを操作する」で、UIスレッドで実行させるために他のスレッドからHandlerクラスを使って処理内容を送信すると説明しましたが、なぜnew Handler()としただけのインスタンスにpost()しただけでUIスレッドに送信されてしまうか?という疑問への回答です。

まずAndroidのアプリケーションは1つのプロセスが割り当てられて動作しています。別々のアプリは別々のプロセス(つまりメモリ空間が異なる)で動いています。

UIスレッドとはメインスレッドとも呼ばれ、内部ではActivityThreadクラスのことを指します。このクラスはStatic変数のLooperクラスを所持しており(つまり1プロセスに1つしかない)、メイン関数でLooperクラスのメインループを実行しています。Looperクラスの処理は単純で、メッセージキューに入ってきた処理があれば実行する、だけです。つまりどこかのスレッドからメッセージをLooperに送信すれば、ActivityThread(UIスレッド)で実行してくれるという仕組みになっています。その送信するのがHandlerクラスになります。Handlerクラスは引数なしでnewすると内部でStatic変数のLooperクラスを送信先とします。つまりActivityThreadが送信先になるということです。

ここでいうメッセージとは、内部ではMessageクラスを使っており、Handlerのインスタンスでpost()するときのRunnableインスタンスを渡しています。それがLooperのメイン関数で、渡されたRunnableのrun()を実行しています。

より詳細はAndroidのソースコードを読む必要があります。
  • android/os/Handler.java
  • android/os/Looper.java
  • android/os/Message.java
  • android/app/ActivityThread.java
ちなみに、このLooper、Handlerの仕組みは独自に他のスレッド同士で利用することも可能です。引数を指定することでUIスレッドとは無関係に、独自で作ったスレッド間で同じ仕組みを利用できます。

July 3, 2012

ランチャーからアプリを起動するときのIntent

ランチャー(ホームメニューからのアプリランチャー)からアプリを起動するときに飛んでくるIntentは下記のような設定になっています。
  • Action:android.intent.action.MAIN
  • Category:android.intent.category.LAUNCHER
  • Flags:FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
ActionとCategoryが設定されており、これはアプリのAndroidManifest.xmlで、ランチャーから起動されるActivityに設定するIntent filterと同じになります。

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name="SampleAppActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

フラグの意味は、アプリが起動していなければ新規起動、起動済みであればそれをフォアグラウンドに持ってくるだけです。

参考:Android Developers:Intent

July 1, 2012

タスク情報を取得する

システム上で動作しているタスク情報を取得する方法です。

デバッグなどでタスクがどう生成されているか理解するのに役に立ちます。

例)タスク情報をすべて取得し、タスクの先頭Activity、スタックされているActivity数を表示します。

ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningTaskInfo> list = manager.getRunningTasks(100);
Log.i(TAB, "Total tasks:" + list.size());
for (RunningTaskInfo info:list) {
    Log.i(TAB, "ID:" + info.id + " Top:" + info.topActivity.getClassName() + "Num:" + info.numActivities);
}

タスク情報を取得するにはパーミッションの設定が必要です。

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

実行すると下記のように表示されます。


参考:Android Developers:ActivityManager

June 29, 2012

NotificationのUIをカスタマイズする

NotificationのUIのカスタマイズ方法です。

Notificationのデフォルト表示方法は「Notificationで通知する」を参照してください。ここではそこからのカスタマイズ方法を説明します。

例)独自のレイアウトでNotificationを表示し、さらにその中の画像をクリックするとServiceにIntentを飛ばします。これはNotification上のボタンからServiceで動作している機能に、いろいろな操作(例:音楽再生の操作とか)が出来ることを意味します。

まずはNotificationに表示するレイアウトを作成します。ここでは画像とテキスト2つです。背景色がデバイスによって異なる可能性があるため、テキストには標準のstyleを設定しています。注意事項は高さがGingerBread(2.3.x)までは固定のようです。HoneyComb(3.x)以降は高さもコンテンツにあわせて変化します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView 
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true" />
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        style="@android:style/TextAppearance.StatusBar.EventContent.Title" />
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_below="@id/title"
        style="@android:style/TextAppearance.StatusBar.EventContent" />
</RelativeLayout>

レイアウトはRemoteViewsクラスを使って上記のレイアウトを生成し、notificationに設定します。またsetOnClickPendingIntentを使って画像をクリックするとServiceにIntentを飛ばすようにnotificationに設定します。ここではactionに"Play"を付加しています。注意事項として、このような画像クリックはHoneyComb(3.x)以降でないと機能しないようです。通常のクリックと同じ扱いになってしまいます。

private void showNotification() {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Intent intent = new Intent(this, SubAppActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    String tickerText = getString(R.string.hello);
    // Customized Layout
    RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.row);
    contentView.setImageViewResource(R.id.icon, R.drawable.jelly);
    contentView.setTextViewText(R.id.title, "Title");
    contentView.setTextViewText(R.id.text, "text message!");
    Intent intent2 = new Intent(this, SampleService.class);
    intent2.setAction("Play");
    PendingIntent pi2 = PendingIntent.getService(this, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);
    contentView.setOnClickPendingIntent(R.id.icon, pi2);
    // Notify
    Notification notification;
    try {
        Class.forName("android.app.Notification$Builder");
        notification = new Notification.Builder(this)
        .setContentIntent(contentIntent)
        .setContent(contentView)
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setTicker(tickerText)
        .setWhen(System.currentTimeMillis())
        .getNotification();
    } catch (ClassNotFoundException e) {
        notification = new Notification(android.R.drawable.stat_sys_download, tickerText, System.currentTimeMillis());
        notification.contentIntent = contentIntent;
        notification.contentView = contentView;
    }
    manager.notify(1, notification);
}

Intentを受け取るServiceクラスは下記のようにしています。Intentを受け取るとonStartCommand()が呼ばれますので、そのときのactionをログ表示します。ここに必要な機能を作り込みます。

public class SampleService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        Log.i("Service", action);
        return START_STICKY;
    }
}

実行すると下記のようになります。画像をクリックするとServiceにintentが飛びます。


参考:Android Developers:Status Notifications
参考:Android Developers:RemoteViews

June 26, 2012

Notificationで通知する

Notificationを使って通知する方法です。通常はBackgroundで動作しているServiceからユーザーに何かを通知するのが目的で使います。

通知はTickerと呼ばれる通知時のみ速報のように表示されるテキストメッセージと、通知欄に表示されるアイコン、通知を開いた時に表示されるタイトルとテキスト、通知をタップしたときの動作(Intent)が最小構成となります。

通知は自分で割り振ったID番号で管理することができ、通知ごとに新しい通知を作ることもできれば、通知済みであれば1つの通知に集約して作らないこともできます。基本は集約して同じ通知は1つにまとめるUIが推奨されています。

オプションで通知時にバイブレーション、LED、音楽再生もセットできます。また通知のUIレイアウトもカスタマイズ可能です。

例)通知します。タップすると単独の別Activityを起動します。通知は1つに集約するようにしています。

通知はいろいろな情報をセットしたNotificationを作成し、それをNotificationManagerを使って通知を実行します。IntentはPendingIntentというものに包み込んでセットします。これはそのIntentが自分のアプリが発行したかのように他のアプリから実行することができる特別なIntentになります。NotificationはAPI Level 11から作成方法が異なり、以前はnewしていましたが、今はNotification.Builderを使って作成するため、両方の作り方を記載しています。

ここでは通知の発行は下記の関数を用意しました。

private void showNotification() {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Intent intent = new Intent(this, SubAppActivity.class);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    String tickerText = getString(R.string.hello);
    Notification notification;
    try {
        Class.forName("android.app.Notification$Builder");
        notification = new Notification.Builder(this)
        .setContentIntent(contentIntent)
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setTicker(tickerText)
        .setContentTitle("Title")
        .setContentText("Text")
        .setWhen(System.currentTimeMillis())
        .getNotification();
    } catch (ClassNotFoundException e) {
        notification = new Notification(android.R.drawable.stat_sys_download, tickerText, System.currentTimeMillis());
        notification.setLatestEventInfo(this, "TITLE", "TEXT", contentIntent);
    }
    manager.notify(1, notification);
}

タップされると起動するSubAppActivityは単独のタスクとするため、AndroidManifestで下記のようにActivityを設定します。

<activity
    android:name="SubAppActivity"
    android:launchMode="singleTask"
    android:taskAffinity=""
    android:excludeFromRecents="true">
</activity>

実行すると、まずアイコンとTickerが表示されます。


通知を開くとアイコン、TitleとTextが表示され、タップするとSubAppActivityが独立したタスクで起動します。


参考:Android Developers:Status Notifications

June 17, 2012

List Viewの作り方とカスタマイズ

List Viewの作り方とカスタマイズ方法です。

List Viewはその名の通り、リスト表示を行う専用のViewです。標準はテキストのみのリスト表示ですが、カスタマイズすることで画像付きのテキストなど自由なレイアウトでリスト表示することができるようになります。

リスト表示するときは通常のActivityではなく、ListActivityというリスト表示専用のActivityを使います。このListActivityのデフォルトはsetContentViewでレイアウトを指定しなくても全画面がListViewになっていますが、あえて設定することで通常のActivityのように自由なレイアウトでListViewを配置することもできます。ただし配置できるListViewは1つだけで、そのid属性に"@android:id/list"とする必要があります。

例)中央にリスト表示、最上部にタイトルのテキスト、最下部にボタンの3段構成で表示する場合、レイアウトは下記のようにします。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/text"
        android:background="#EE3333"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/myname" />
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/launch" />
</LinearLayout>

リスト表示の1行ごとのレイアウトとそこで表示するデータを決めます。ここでは左端にアイコン画像、右側にテキストを表示するレイアウトにします。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <ImageView 
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/title"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="wrap_content" />    
</LinearLayout>

表示に必要なデータはクラスで定義します。コンストラクタで初期値を設定できるようにしておくと便利です。

public class MyItems {
    public int mIconId;
    public String mTitle;

    public MyItems(int iconId, String title) {
        mIconId = iconId;
        mTitle = title;
    }
}

このデータを使ってレイアウトに表示するクラスを作ります。ArrayAdapterを継承して独自のカスタマイズクラスを作ります。このクラスの役割はgetView()で上記で作ったレイアウトに指定された行のデータを設定したViewを生成することです。そのViewがリストの1行として表示されます。

public class MyListAdapter extends ArrayAdapter<MyItems> {
    private LayoutInflater mInflater;

    public MyListAdapter(Context context, int textViewResourceId, List<MyItems> objects) {
        super(context, textViewResourceId, objects);
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyItems items = getItem(position);
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.row, null);
        }
        // Icon
        ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
        imageView.setImageResource(items.mIconId);
        // Title
        TextView textView = (TextView) convertView.findViewById(R.id.title);
        textView.setText(items.mTitle);

        return convertView;
    }
}

ListActivityで実際に表示する具体的なデータのリストをArrayListで作成し、それを使って上記のMyListAdapterを作成します。それをListActivityに設定すればリスト表示の完成です。また、アイテムをクリックされた時の処理は、ListActivityが持っているListViewのOnItemClickListenerが呼ばれるので、ここではデータのリストからタイトルを取得して表示するようにしています。

public class SampleAppActivity extends ListActivity {
 private final static String TAB = "SampleAppActivity";
 private List<MyItems> mMylist;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mMylist = new ArrayList<MyItems>();
        mMylist.add(new MyItems(android.R.drawable.ic_menu_today, "Today"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_agenda, "Agenda"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_gallery, "Gallery"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_month, "Month"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_mylocation, "My Location"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_rotate, "Rotate"));
        mMylist.add(new MyItems(android.R.drawable.ic_menu_more, "More..."));
        setListAdapter(new MyListAdapter(this, 0, mMylist));
        
        getListView().setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                MyItems items = mMylist.get(position);
                Log.i(TAB, "Clicked:" + items.mTitle);
            }
        });
 }
}

実行すると下記のような表示になります。


参考:Android Developers:ListActivity
参考:Android Developers:ListView
参考:Android Developers:ArrayAdapter
参考:Android Developers:Hello, Views - List View

Action Barからサブメニューを表示する

Action Barのオプションメニューからサブメニューを表示する方法です。

Action Barにオプションメニューを表示するまでは「Action Barにオプションメニューを表示する」を参照してください。ここでは、そこからの差分を説明します。

サブメニューを表示するには、ActionProviderクラスを使用します。このクラスを継承してユーザー独自のクラスを作ることでオリジナルのサブメニューが表示できます。

まずはオプションメニューからサブメニューを呼び出します。呼び出すには"actionProviderClass"属性にクラス名を設定します。これでこのメニューアイテムが選択されると、このクラスを呼び出します。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:showAsAction="ifRoom|collapseActionView" android:icon="@android:drawable/ic_menu_search" android:actionViewClass="android.widget.SearchView"></item>
    <item android:id="@+id/item2" android:icon="@android:drawable/ic_menu_share" android:showAsAction="ifRoom" android:actionProviderClass="jp.myapp.sample.SampleActionProvider"></item>
    <item android:id="@+id/item3" android:title="@string/menu3" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item4" android:title="@string/menu4" android:showAsAction="ifRoom"></item>
</menu>

呼び出し先のActionProviderクラスを作成します。後でViewを作成するためにコンストラクタでcontextを保存しておくのが通例です。onCreateActionViewではViewを返す必要はなくnullで構いません。サブメニューがあることを伝えるためにhasSunMenu()ではtrueを返します。メニュー表示のたびにonPrepareSubMenu()が呼ばれるので、そこでサブメニューを作成します。この例ではxmlで作ったmenuリソースをセットし、それをクリックしたときのリスナーを自分自身になるようにしています(onMenuItemClick()が呼ばれます)。

public class SampleActionProvider extends ActionProvider implements OnMenuItemClickListener {
    private final static String TAB = "SampleActionProvider";
    private Context mContext;

    public SampleActionProvider(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public View onCreateActionView() {
        return null;
    }

    @Override
    public boolean hasSubMenu() {
        return true;
    }

    @Override
    public void onPrepareSubMenu(SubMenu subMenu) {
        subMenu.clear();
        MenuInflater inflator = new MenuInflater(mContext);
        inflator.inflate(R.menu.provider_menu, subMenu);
        for (int i = 0; i < subMenu.size(); ++i) {
            subMenu.getItem(i).setOnMenuItemClickListener(this);
        }
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        Log.i(TAB, "Title:" + item.getTitle());
        return true;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:icon="@android:drawable/ic_menu_compass"></item>
    <item android:id="@+id/item2" android:title="@string/menu2" android:icon="@android:drawable/ic_menu_camera"></item>
</menu>

実行すると下記のようになります。Action Barのオプションメニューの2番目のShareアイコンのボタンを押すとサブメニューが現れます。


June 16, 2012

Action Barにオプションメニューを表示する

Action Barにオプションメニューを表示する方法です。Action BarはAndroid 3.0(API 11)から追加された機能ですので、それ以上のデバイスで有効です。Action Barとは画面最上部に表示されるバー(アイコンやタイトルなどが表示される)のことです。

オプションメニューの作り方は「オプションメニューの表示」を参照してください。ここではその差分を説明します。

メニューのリソースを下記のように作成します。差分は"showAsAction"属性に"ifRoom"を設定していることです。この設定をすると、スペースがあればAction Barに表示するようになります。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:showAsAction="ifRoom" android:icon="@android:drawable/ic_menu_add"></item>
    <item android:id="@+id/item2" android:icon="@android:drawable/ic_menu_share" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item3" android:title="@string/menu3" android:showAsAction="ifRoom"></item>
    <item android:id="@+id/item4" android:title="@string/menu4" android:showAsAction="ifRoom"></item>
</menu>

縦スクリーンで表示した場合、一部のみAction Barに表示されています。残りはオプションメニューでの表示となります。


横スクリーンで表示した場合、スペースがあるため、すべてAction Barに表示されています。


Action Barはアイコン優先、オプションメニューではテキストが表示されます。

また、縦スクリーンでは右上に表示するのではなく、画面下部に分割して表示することも可能です(横幅が狭いときのみ有効なので、幅広な横スクリーンでは使えません)。AndroidManifest.xmlのapplicationやactivityに下記のような"uiOptions"を設定します。

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:uiOptions="splitActionBarWhenNarrow">


参考:Android Developers:Action Bar
参考:Android Developers:Action Bar Icons

オプションメニューの表示

オプションメニューの表示方法です。メニューボタンを押すと表示されるオプションメニューのことです。

作り方は、menuリソース(xml)を作成し、res/menuにおきます。初期化時に1回だけonCreateOptionsMenu()が呼ばれるので、そこでメニューアイテムを登録し、メニューアイテムが選択されるとonOptionsItemSelected()が呼ばれるので、そこに押された時の処理を加えます。

例)4つのメニューアイテムを持つ場合

public class SampleAppActivity extends Activity {
    private final static String TAB = "SampleAppActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.mymenu, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean ret = true;
        switch (item.getItemId()) {
        case R.id.item1:
            Log.i(TAB, "Menu item 1");
            break;
        case R.id.item2:
            Log.i(TAB, "Menu item 2");
            break;
        case R.id.item3:
            Log.i(TAB, "Menu item 3");
            break;
        case R.id.item4:
            Log.i(TAB, "Menu item 4");
            break;
        default:
            ret = super.onOptionsItemSelected(item);
        }
        return ret;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item1" android:title="@string/menu1" android:icon="@android:drawable/ic_menu_add"></item>
    <item android:id="@+id/item2" android:title="@string/menu2"></item>
    <item android:id="@+id/item3" android:title="@string/menu3"></item>
    <item android:id="@+id/item4" android:title="@string/menu4"></item>
</menu>

メニュー1だけシステムの画像を設定して表示していますが、独自の画像を使うこともできます(デザイン方法は参考を参照)。メニューアイテムは最大6つまで同時に表示され、それ以上は6つ目のアイテムがMoreにかわり、ダイアログがでて6つ以上のメニューアイテムも選択できます。

Android 2.3(API 10)までのデバイスで実行すると下記のように表示されます。


Android 3.0(API 11)以降のデバイスで実行すると下記のように表示されます。



この違いは、今回の手法がAndroid2.3(API 10)までを対象としたやり方のためです。Android 3.0からは物理的なメニューボタンを必須からオプション扱いにしたため、それにあわせてオプションメニューに対するUIも変更したからです。Android 3.0からはAction Barというものが表示されるようになり、基本的にオプションメニューはそこに表示して、よりダイレクトに選択できるUIにかえています。

Android 3.0以降でもAction Barなしのテーマを選択している場合、Action BarがないのでAndroid 2.3以前と同様な表示になります。

参考:Android Developers:Activity
参考:Android Developers:Say Goodbye to the Menu Button
参考:Android Developers:Menu Icons