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 12, 2012

対応すべきプラットフォーム

Androidにはさまざまなデバイス(端末)がありますが、現在市場にでまわっている状況が確認できます。AndroidのOS version、ディスプレイサイズ、Open GLのversionから製作するアプリがサポートすべき環境を想定すると良いと思います。

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

Android EmulatorでWebカメラを使う

Android EmulatorでWebカメラを使う方法です。

Front camera、Back camera個別に設定できます。

デフォルトのカメラはEmulatorになっていますが、AVDの設定を変更してWebカメラに設定することで利用できるようになります。

AVD ManagerからAVDの編集(Edit)をクリックし、HardwareのPropertyで"Configures camera facing back"を選択し、Valueを"webcam0"にします。これでBack cameraがwebcam0になります。Front cameraを設定したい場合は"Configure facing front"を修正します。


ちなみにどんなWebカメラデバイスが使えるかは、コマンドラインから確認できます。emulatorはandroid-sdks/toolsにあります。

$ emulator -avd JellyBeanPhone -webcam-list


webcam0が有効なことがわかります。

実行するとemulatorカメラでなくWebカメラの出力が表示されます。


Android SDKをJelly Bean(4.1) にアップデートする

Android SDKをJelly Bean (Android 4.1)にアップデートする方法です。MacのEclipseで作業した場合の例です。

最初にAndroid SDK Managerを起動し、Toolsをアップデートします。


続いて、Install New SoftwareでADT Pluginをアップデートします。


再度Android SDK Managerを起動すると、Android 4.1(API 16)がでてきますので、インストールします。


続いて、AVD Managerを起動し、JellyBeanのAVDを作成します。


以上で、完了です!

ランチャーからアプリを起動するときの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