It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

rbenvのインストール(Mac)

Rubyのバージョン管理ツールであるrbenvを使って、
Rubyの実行環境をつくってみました。

1.rbenvをインストール

brew install rbenv

インストールが完了したらrbenvの初期設定を行います。
以下の設定を.zshenv.bashrcに記載します。

この設定で、[user_home]/.rbenvディレクトリ配下に書くバージョンのrubyがインストールされるようになります。

# rbenvの初期設定
if which rbenv > /dev/null; then
        export RBENV_ROOT=${HOME}/.rbenv
        export PATH=${RBENV_ROOT}/shims:${PATH}
        eval "$(rbenv init -)";
fi

その他、必要なツールのインストール

brew install ruby-build
brew install rbenv-gemset
brew install rbenv-gem-rehash

2.インストールの確認

ここまでで、正しくrbenvのインストールおよび設定ができているかを確認するために、
以下のコマンドを実行します。

rbenv versions

※ rbenv versions ~ インストール済みのRubyのバージョンを確認

設定されているパスが以下のように自分のホームディレクトリを指していれば、
正しく設定ができています。

* system (set by /Users/[user_home]/.rbenv/version)

3.rubyのインストール

実際に使用するRubyをインストールします。

インストール可能なバージョンの確認

rbenv install -l

Rubyのバージョンをインストール

rbenv install 2.0.0p451

4.インストールの確認

ruby -v

ruby 2.0.0p451 (2014-02-24 revision 45167) [universal.x86_64-darwin13]

ダウンロードした、Rubyのバージョンを指していれば正しくインストールされています。

インストールしたRubyの実行ファイルパスも確認してみます。

which ruby

/Users/[user_home]/.rbenv/shims/ruby

.rbenv配下にインストールされていれば大丈夫です。

rubyのバージョンを切り替える時

rbenv global [バージョン]
rbenv rehash

rehashしてあげないと、私の環境ではPATHが切り替わりませんでした。

【Android】youtubeの動画を再生する

Androidで、youtubeの動画を再生するには大きく分けて2つの方法があります。

1つは、youtubeアプリに対してIntentを飛ばす方法で、
例えば下記のように記載することで直接youtubeアプリを起動することができます。

Intent intent = new Intent(Intent.ACTION_SEARCH);
intent.setPackage("com.google.android.youtube");
intent.putExtra("query", "Android");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

もう一つは、YouTube Android Player APIを使用する方法です。
これは、googleが提供しているAPIで、自分のアプリ内でyoutubeの再生機能を利用することができます。
(youtubeを起動はしない)

今回は、YouTube Android Player APIを使用して、 アプリ内で動画を再生する方法を試してみました。

API Keyを作成する

まずは、Google APIをアプリ上で実行するために、 Google Developlers Consoleにて、
APIKeyを取得します。


1.新規プロジェクトを作成

f:id:inon29:20140507210213p:plain

2.Youtube APIを有効にする

APIs & auth を選択し、 YouTube Data API v3をONにします。

f:id:inon29:20140507210701p:plain

3.API Keyを作成する

APIs & auth >Credentials を選択、 Public API accessにあるCREATE NEW KEYボタンを押下します。

f:id:inon29:20140407220058p:plain

キー種別の選択ダイアログが表示されるため、 Android keyを選択します。
続いて、表示されるテキストボックスにAndroidに組み込むkeystoreのSHA1のフィンガープリントおよび、
API実行を許可するアプリケーションのパッケージ名を入力します。

f:id:inon29:20140407220350p:plain

入力フォーマットは、[フィンガープリント];(セミコロン)[パッケージ名]となっています。

CREATEボタンを押下すれば、API keyの作成は完了です。
完了後に画面に表示されているAPI keyAndroidアプリケーションに組み込んで使用します。

例: 9F:DA:1F:03:71:4B:1E:BD:90:76:51:AB:26:0C:B7:41:2F:94:AB:C6;com.sample.gcmtest

※ keystoreのフィンガープリントを確認するには

■ 使用するコマンド

keytool -exportcert -alias androiddebugkey -keystore [署名するkeystoreのパス] -list -v

今回は、Android ADTで用意されているデバッグ用のkeystoreで確認します。
デバッグ用のkeystoreは、[ユーザーHOME]/.android/debug.keystoreにデフォルトkeyが配置されていて、 パスワードは、androidです。

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v

上記のコマンド実行結果から、表示されるフィンガープリントのSHA1の文字列をコピーすればOKです。


Androidアプリの実装

今回は、youtubeのプレーヤーをFragmentとして使用するサンプルアプリを実装します。

1.YouTube Android Player APIの最新バージョンをダウンロード

YouTube Android Player APIの最新版をダウンロードし、libs/にあるYouTubeAndroidPlayerApi.jarをプロジェクトへインポートします。

2.AndroidManifestの実装

下記、パーミッションを追加します。

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

3.layout.xmlYouTubePlayerFragmentを定義

今回は、動画の再生に使用するYouTubePlayerFragmentをlayout.xmlに定義して使用します。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
      android:name="com.google.android.youtube.player.YouTubePlayerFragment"
      android:id="@+id/youtube_fragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>

</LinearLayout>

4.Activityの実装

下記サンプルでは、Activityに登録されたYouTubePlayerFragmentインスタンスを、
上記で取得したAPI Keyで初期化しています。

また、初期化のコールバックを取得するために、YouTubePlayer.OnInitializedListenerを実装し、
初期化が成功したタイミングでプレーヤーの実行準備を行っています。

APIの詳細については、YouTube Android Player API - リファレンス ガイドを参照してください。

public class MainActivity extends Activity implements YouTubePlayer.OnInitializedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // フラグメントインスタンスを取得
        YouTubePlayerFragment youTubePlayerFragment =
                (YouTubePlayerFragment) getFragmentManager().findFragmentById(R.id.youtube_fragment);
        
        // フラグメントのプレーヤーを初期化する
        youTubePlayerFragment.initialize("[API Key]", this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onInitializationFailure(Provider provider,
            YouTubeInitializationResult error) {
            // プレーヤーの初期化失敗時に呼ばれる
    }

    @Override
    public void onInitializationSuccess(Provider provider, YouTubePlayer player,
            boolean wasRestored) {
            // プレーヤーの初期化成功時に呼ばれる
        if (!wasRestored) {
            // 指定された動画のサムネイルを読み込み、プレーヤーがその動画を再生する準備を行う
            player.cueVideo("nCgQDjiotG0");
          }
        
    }

}

これだけで、アプリ内でyoutubeの動画を再生することができます。
他にも、ウィジェットなどのAPIも用意されているようなので、 色々試して見たいと思います。

参考リンク

https://developers.google.com/youtube/android/player/ https://developers.google.com/youtube/android/player/setup

Appiumを使ってRubyでAndroidアプリのテストを行う①

Appiumをインストール

sudo npm install -g appium
npm install wd
appium &

Appiumリポジトリをクローン

git clone https://github.com/appium/appium.git

テスト実行環境を確認

以下のスクリプトを実行して、テストに必要な環境の構築を行います。
ビルドパスやコマンドが不足している場合は、必要に応じてインストールします。

cd appium
./reset.sh --verbose

参考までに私の環境でエラーになった内容について記載しておきます。

[mavenがインストールされていなかった]

Homebrewでインストール。

brew install maven

[androidSDKのAPI Level16がインストールされていなかった]

Android SDK Managerから指定のAPIをインストール。

android

テスト実行環境をインストール

今回は、Rubyでテストを行うつもりなので以下を実行します。

cd appium/sample-code/examples/ruby
bundle install

ここでハマったのが、bundle install時に以下のエラーでて、ビルドが失敗していました。

An error occurred while installing ffi (1.9.3), and Bundler cannot continue.
Make sure that `gem install ffi -v '1.9.3'` succeeds before bundling.

原因は、Xcode 5.1 へのアップデートによってclangが更新されたためとのことです。
下記のサイトを参考にさせていただき、

ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future bundle install

とすることで、成功しました。

参考サイト:

【Mac】clang によるビルドエラー【Xcode 5.1】

rspecのインストール

Appiumの実行には、Rspecを使用するため、gemでインストールしておきます。

gem install rspec

ここまでで、テストを実行する準備が整いました。
次回は、具体的にどうやってテストを書けばいいかを書いていきたいと思います。

参考サイト

http://qiita.com/2or3/items/261862eebbccc436265b
http://iti.hatenablog.jp/entry/2013/12/25/085726

【Android】NFCを使ってみる① (読み込み処理)

NFC(Near Field Communication)は、近距離無線のテクノロジーで、
Android 2.3からサポートされている技術です。

また、Android 4.0からは、Android Beamという機能が加わり、
2つのAndroid搭載のデバイス間でのピアツーピアのデータ交換が可能となりました。

アプリケーションへの NFC タグのディスパッチ方式

AndroidデバイスがNFCを検知すると対応するインテントを発行します。
NFCに関する起動インテントには、下記の3種類があり、 それぞれの優先度が決まっています。

[ACTION_NDEF_DISCOVERED]

読み込んだタグが、NDEF(NFC Data Exchange Format)ペイロードを持つ場合、
このアクションが定義されたアクティビティにIntentが通知されます。

起動インテントの優先度としては、最も高く、
このアクションでアクティビティが起動した場合は、ACTION_TECH_DISCOVEREDまたはACTION_TAG_DISCOVEREDで登録されているアクティビティがあっても起動されません。

[ACTION_TECH_DISCOVERED]

ACTION_NDEF_DISCOVEREDインテントをハンドルするアクティビティが登録されていない場合には、 このアクションでが定義されたアクティビティにIntentが通知されます。 優先度としては、ACTION_NDEF_DISCOVEREDの次に高いです。

[ACTION_TAG_DISCOVERED]

このインテントACTION_NDEF_DISCOVERED または、ACTION_TECH_DISCOVEREDインテントをハンドルするアクティビティがない場合に開始されます。

読み込みサンプル

以下のサンプルは、NFCカードをかざすとNFCの識別子情報を読み取るサンプルになります。

Android Manufestの定義

Android Manufestには、NFCを使用するためのパーミッションの定義と、
各ディスパッチ方式に対応するintent-filterの定義が必要です。

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

    <!-- NFCを使用する為のパーミッションを付与 -->
    <uses-permission android:name="android.permission.NFC" />
    <!-- Google Playで、NFCハードウェアを持つデバイスのみに制限するための   機能を定義 -->
    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

    ・・・

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.test.nfcreadsample.MainActivity"
            android:label="@string/app_name" >
            
             ・・・
            
            <!-- NFC NDEF text -->
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
            
            <!-- NFC TECH -->
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <!-- ACTION_TAG_DISCOVEREDで登録するフィルタを参照する -->
            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
            
            <!-- NFC TAG -->
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />

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

</manifest>

ACTION_NDEF_DISCOVEREDインテントをフィルタするには、
フィルタしたい対象データのタイプを加えたインテントフィルタを宣言します。
上記では、ACTION_NDEF_DISCOVEREDフィルタにtext/plainMIMEタイプを付加しています。

ACTION_TECH_DISCOVEREDインテントをフィルタさせる場合は、
XMLリソースファイルを作成し、tech-listセットの中にアクティビティがサポートするNFCのフォーマット形式を定義します。

作成したXMLファイルは、<project-root>/res/xmlディレクトリ配下へ保存します。

以下は、tech-listリソースファイルのサンプルです。

nfc_tech_filter.xml

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcF</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcV</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NdefFormatable</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

NFCを読み込む

下記では、起動Activityより受信インテントを受け取り、
NFCの起動によるものかを判定しています。

インテントNFCのアクションである場合、
NfcAdapter.EXTRA_IDというキーを使ってNFCIDm(固有識別子)を読み込んでいます。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        Intent intent = getIntent();
        String action = intent.getAction();
        
        setContentView(R.layout.activity_main);
        
        // NFCかどうかActionの判定
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
                ||  NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
                ||  NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
            
            // IDm(固有識別子)を表示させる
            String idm = getIdm(getIntent());
            if (idm != null) {
                TextView idmView = (TextView) findViewById(R.id.idm);
                idmView.setText(idm);
            }
        }
    }

    /**
     * IDmを取得する
     * @param intent 受信インテント
     * @return IDm文字列
     */
    private String getIdm(Intent intent) {
        String idm = null;
        StringBuffer idmByte = new StringBuffer();
        byte[] rawIdm = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
        if (rawIdm != null) {
            for (int i = 0; i < rawIdm.length; i++) {
                idmByte.append(Integer.toHexString(rawIdm[i] & 0xff));
            }
            idm = idmByte.toString();
        }
        return idm;
    }
}

【Android】GCMを使ってみる

GCM(Google Cloud Messaging for Android)は、

開発者がサーバからAndroidデバイス上のAndroidアプリケーションに対して、
データを送信できるようにする仕組みです。

Developers Consoleの準備

Developers Consoleにて、アプリケーションを登録してAPI実行に必要なKey情報を取得します。

Google Developers Console

1.任意のアプリケーションを作成する

アプリケーションを作成すると、APIコンソールのDashboardProject Numberが表示されます。
この数字は、後ほど作成するGCM受信アプリにて、登録ID(registration ID)取得の際に必要になるので覚えておきます。

f:id:inon29:20140407215605j:plain

2.GCMの設定を有効にする

APIs & authを選択し、表示されるリストの中からGoogle Cloud Messaging for Androidを探してONにします。

f:id:inon29:20140407215837p:plain

3. API Keyを発行する

API Keyを作成するには、左メニューのAPIs & auth > credentialボタンを押下し、
Public API access領域にあるCREATE NEW KEYボタンを押下します 。

f:id:inon29:20140407220058p:plain

以下のように作成するキーを選択するダイアログが表示されるます。
今回は、Pushの確認テストとしてサーバーからリクエストを送るため、Server keyを選択します。

f:id:inon29:20140409212815p:plain

Server keyボタンを押下すると、アクセスを許可するIPアドレスを設定するためのダイアログが表示されるので、
アクセスを許可するグローバルIPを入力します。

f:id:inon29:20140407220350p:plain

処理が正常に完了すると下記のようにAPI keyが発行されます。
このKeyを使用して、サーバー側からのGCMのメッセージを送信します。(後述)

f:id:inon29:20140409213514p:plain

Androidアプリの準備

GCMを受信するためのAndroidアプリケーションを実装します。

1.GSM用のライブラリをeclipseにimportする

Android SDK Managerを起動し、Extras配下のGoogle Play servicesを選択し、
importします。

f:id:inon29:20140410201849p:plain

importが完了すると<ANDROID_HOME>/extras/google/google_play_services/libproject/配下に、google-play-services_libというディレクトリがDLされているので、
これをAndroid Project From Exting Code形式でeclipseにimportします。

次に、GCMを受信する為のプロジェクトを新規で作成し、
Projectを選択 > 右クリックでPropertiesを選択 > Androidから、
先ほど、eclipseにimportしたライブラリプロジェクトを追加します。

これで、Android プロジェクトでGCMクラスを使用するための準備は完了です。

2. GCM受信の準備

GCMの通知を受け取るためのReceiverクラスを実装します。

GCMの受信処理中に端末がスリープしないように、ReceiverクラスはWakefulBroadcastReceiverを継承しています。

WakefulBroadcastReceiverは、処理を実行している間は、デバイスがスリープしないことが保証できる特殊なReceiverクラスです。

public  class GcmBroadcastReceiver extends WakefulBroadcastReceiver {

    public GcmBroadcastReceiver() {
        super();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // GCMを受信 メッセージ受信用のサービスを起動する
        ComponentName comp = 
                new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
              startWakefulService(context, (intent.setComponent(comp)));
              setResultCode(Activity.RESULT_OK);
    }
}

Receiverクラスが受け取ったGCMのインテントを処理するServiceクラスを作成します。

public class GcmIntentService extends IntentService {
    private static final String TAG = "GcmIntentService";
    
    // 引数なしのコンストラクタを作成しないと例外で落ちてしまった
    public GcmIntentService() {
        super(GcmIntentService.class.getName());
    }
    
    public GcmIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();

        // intentからGCMのメッセージを取得する
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        String messageType = gcm.getMessageType(intent);
 
        // GCMのメッセージをタイプ別にフィルタリングする。
        //  将来的に拡張されることを考慮し、存在しないタイプを無視するようにする
        if (!extras.isEmpty()) {
            if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { 
                // エラー
                Log.d(TAG,"messageType: " + messageType + ",send error:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
                // サーバー側でメッセージを削除された
                Log.d(TAG,"messageType: " + messageType + ",message deleted:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { 
                // メッセージ受信
                Log.d(TAG,"messageType: " + messageType + ",received message:" + extras.toString());
            }
        }
        // 処理の終了をReceiverに通知し、ロックを解放する
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }
}

3. AndroidManifestを更新する

GCMを受け取るためのPermissionおよび、
上記で実装したメッセージ受信するReceiverクラス、
メッセージを処理するServiceクラスの定義をAndroidManifestに記載します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.gcmtest"
    android:versionCode="1"
    android:versionName="1.0" >

    ・・・

    <!-- Permissionの宣言 -->
    <permission android:name="com.test.gcmtest.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="com.test.gcmtest.permission.C2D_MESSAGE" /> 
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        ・・・        
        <!-- Receiverクラスの宣言 -->
        <receiver
            android:name=".GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.test.gcmtest" />
            </intent-filter>
        </receiver>
        <!-- Serviceクラスの宣言 -->
        <service android:name="com.test.gcmtest.GcmIntentService" />
    </application>

・・・

これで、受信側の準備は完了です。

3. GCM送信の準備

次に、サーバーからGCMを送信するために必要なregistration_idを取得するクラスを実装します。

下記サンプルでは、Activity起動時に非同期でregist処理を行っています。
onPostExecute()で取得できる文字列がregistration_idです。


gcm.register()の引数に渡すIDには、Developer Consolに表示されていたProject Numberを指定します。

public class MainActivity extends Activity {
    private GoogleCloudMessaging gcm;
    private Context context;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
        
        // GCMクラスを作成して非同期でregist処理を行う。
        gcm = GoogleCloudMessaging.getInstance(this);
        registerInBackground();
    }
 
    private void registerInBackground() {
        new AsyncTask<Void, Void, String>() {
            @Override
            protected String doInBackground(Void... params) {
                String msg = "";
                try {
                    if (gcm == null) {
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    String regid = gcm.register("<app_id>");
                    msg = regid;
                    Log.d("tag", "Device registered, registration ID=" + msg);
                } catch (IOException ex) {
                    msg = "Error :" + ex.getMessage();
                }
                return msg;
            }
 
            @Override
            protected void onPostExecute(String msg) {
                // registration IDを取得
                // 従来であれば、ここから送信サーバーへregistration IDを送信するような流れになる
                Log.d("tag", msg); 
            }
        }.execute(null, null, null);
    }
}

GCMのテスト送信

最後に、サーバーからのGCMテスト送信を行います。

下記、curlコマンドでは、
Developers Consoleの準備手順で取得したAPI Keyと上記で取得したregistration IDを使って、
作成したAndroidアプリへ”Hello”という文字列を送信しています。

curl --header "Authorization: key=<API Key>" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"<registration ID>\"],\"data\":{\"message\":\"Hello\"}}"

参考

http://zatomiya.blogspot.jp/2013/05/androidnotification.html
http://dev.classmethod.jp/smartphone/android/gcm/
http://developer.android.com/google/gcm/gs.html
http://developer.android.com/google/gcm/client.html

【Android】コンテンツプロバイダ(ContentProvider)を使ってみる

コンテンツプロバイダは、Androidのアプリケーションが管理しているリソースを 他アプリケーションから参照するための仕組みです。

例えば、連絡先アプリなど他アプリケーションから活用できるデータを持つアプリケーションが、
その他アプリケーションへデータを提供する際に使用されます。

今回は、SQLiteデータベースの情報を取得するサンプルを作成しました。

SQLiteデータベースへアクセスするためのクラスを作成

コンテンツプロバイダのデータ提供元となるデータベースを作成します。

testdbという名前のDBにfruitというテーブルを作成しています。

public class ShareDbHelpler extends SQLiteOpenHelper {

    private static final String DB_NAME = "testdb";
    private static final int DB_VERSION = 1;

    // テーブル情報を定義
    public static final String TABLE_NAME = "fruit";
    public static final String COL_NAME = "firstName";

    // カラムには、必ず「_id」という主キーをAUTOINCREMENTで作成する必要がある
    private static final String STRING_CREATE =
            "CREATE TABLE " + TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, "
            +COL_NAME+" TEXT);";

    public ShareDbHelpler(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // データベースのテーブルを作成する
        sqLiteDatabase.execSQL(STRING_CREATE);

        // データベースにサンプル値を挿入
        ContentValues cv = new ContentValues(1);
        cv.put(COL_NAME, "Apple");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);

        cv = new ContentValues(3);
        cv.put(COL_NAME, "Orange");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);

        cv = new ContentValues(3);
        cv.put(COL_NAME, "Banana");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i2) {
        // DBに変更があった場合は、とりあえずテーブルを削除する
        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
    }
}

コンテンツプロバイダを継承したクラスを作成

コンテンツプロバイダを提供するためにContentProviderのサブクラスを定義します。
ContentProviderを継承したクラスは、以下の6つのメソッドを実装する必要があります。

  • query()
  • insert()
  • update()
  • delete()
  • getType()
  • onCreate()

また、 サブクラスには、ContentProviderへアクセスするためのURI(content://~)および、アクセス可能なカラム名を定数として定義します。

ContentProviderがアクセスする先がデータベースの場合、定義するカラム名とデータベースのカラム名を同じにするのが一般的です。

カラム名には、 レコードのIDとして整数型の_id(定数ID)という名前のカラムを必ず含めるようにしなくてはなりません。
※他に全レコードで一意になるであろうフィールド ( URL など ) を持っていても含めなくてはならない
さらに、SQLiteデータベースを使用する場合は、この
ID フィールドをINTEGER PRIMARY KEY AUTOINCREMENTにしなくてはいけません。

以下は、frauitテーブルへアクセスするためのContentProviderのサンプルです。

public class TestProvider extends ContentProvider {

    // コンテンツプロバイダのURIを定義
    public static final Uri CONTENT_URI =
            Uri.parse("content://com.inon.shareddb.testsprovider/frauit");

    // アクセスが可能なカラムを定義
    public static final class Columns {
        public static final String _ID = "_id";
        public static final String NAME = "name";
    }

    /* URI マッチング */
    // 全行取得とidでの取得を行えるよう、UriMatcherを使用する
    private static final int FRAUIT = 1;
    private static final int FRAUIT_ID = 2;

    private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        matcher.addURI(CONTENT_URI.getAuthority(), "frauit", FRAUIT);
        matcher.addURI(CONTENT_URI.getAuthority(), "frauit/#", FRAUIT_ID);
    }

    SQLiteDatabase db;

    @Override
    public boolean onCreate() {
        // 接続DBの作成
        ShareDbHelpler helpler = new ShareDbHelpler(getContext());
        db = helpler.getWritableDatabase();
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[]
            selectionArgs, String sortOrder) {
        int result = matcher.match(uri);

        // 指定のURIが正しければ、取得結果(Cursor)を返却する
        switch (result) {
            case FRAUIT:
                return db.query(ShareDbHelpler.TABLE_NAME, projection, selection,
                        selectionArgs, null, null, sortOrder);
            case FRAUIT_ID:
                return db.query(ShareDbHelpler.TABLE_NAME, projection, "_ID = ?",
                        new String[]{uri.getLastPathSegment()}, null, null, sortOrder);
            default:
                return null;
        }
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        long id = db.insert(ShareDbHelpler.TABLE_NAME, null, contentValues);

        if (id >= 0) {
            return Uri.withAppendedPath(uri, String.valueOf(id));
        } else {
            return null;
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int result = matcher.match(uri);
        switch (result) {
            case FRAUIT:
                return db.delete(ShareDbHelpler.TABLE_NAME, selection, selectionArgs);

            case FRAUIT_ID:
                return db.delete(ShareDbHelpler.TABLE_NAME, "_ID = ?",
                        new String[] {uri.getLastPathSegment()});

            default:
                return 0;
        }
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
        int result = matcher.match(uri);
        switch (result) {
            case FRAUIT:
                return db.update(ShareDbHelpler.TABLE_NAME, contentValues, selection, selectionArgs);

            case FRAUIT_ID:
                return db.update(ShareDbHelpler.TABLE_NAME, contentValues, "_ID = ?",
                        new String[]{uri.getLastPathSegment()});

            default:
                return 0;
        }
    }
}

AndroidManifestでコンテンツプロバイダを宣言する

Content Providerを使用するためにAndroidManifestに定義を追加する必要があります。

<provider
    android:authorities="com.sample.shareddb.testsprovider"
    android:name="com.sample.shareddb.TestProvider">
 </provider>

android:nameには、クラス名を定義します。
android:authorities には、外部からアクセスする際に使用するURIを定義します。
これは、パッケージ名を含めたクラス名を定義するのが一般的です。

コンテンツプロバイダへの問い合わせ

コンテンツプロバイダを介してデータを取得するには、ContentResolverインスタンスを利用します。 query()メソッドを使って取得したCursorオブジェクトから各取得データへのアスセスが可能となります。

以下のサンプルでは、ActivityからContentResolverを利用してデータを取得し、ListViewへデータを表示しています。
本来は、取得したCursorオブジェクトは使用しなくなったタイミングで適切に破棄する必要がありますが、
今回は、サンプルのため何もしていません。

public class MainActivity extends Activity {

    Cursor mCoursor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String[] projection = new String[] {TestProvider.Columns._ID,
            TestProvider.Columns.NAME};
        mCoursor = getContentResolver().query(TestProvider.CONTENT_URI, projection, null, null,
        null);

        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
                mCoursor,
                new String[] {TestProvider.Columns.NAME},
                new int[] {android.R.id.text1},
                0);

        ListView list = (ListView) findViewById(R.id.list_view);
        list.setAdapter(adapter);
    }
}

参考

http://www.techdoctranslator.com/android/guide/content-providers

【Android】Serviceをつかってみる

バックグラウンドで動くプログラムServiceを使ってみました。

Serviceの使用方法

Serviceの起動方法には、

  • startService()を使用
  • bindService()を使用

の大きく分けて2種類があります。

Service起動方法の違い

起動方法による違いは、以下のとおりです。

startService

  • Service起動中は、ActivityへIntentの発行が可能。
  • Service起動後は、ActivityからServiceを制御する経路がない。
  • Serviceの生存期間はActivityに依存しない。明示的にstopServiceが呼ばれるまで動き続ける。

bindService

  • バインドを使うことでActivityからServiceを制御できる。
  • Serviceの生存期間はコネクションに依存。コネクションが切断されるとServiceは終了する。

startService()を使用した起動

startService()を使用して、Serviceを起動した時のライフサイクルを以下に記載します。

順序 メソッド 説明
1 onCreate() Serviceが初回作成時に呼ばれる。
2 onStartCommand() クライアントが明示的にstartService(Intent)を呼び出したタイミングで呼ばれる。
このメソッドを直接呼び出してはいけない。
APIレベル5以前は、onStart()が使用されるため注意。
3 onDestroy() Serviceが使用されなくなったタイミングで呼ばれる
Serviceの持っているリソースをクリーンアップする。

サンプルソース

■ AndroidManifest.xml

<service android:name="Service名" />

■ Activityの呼び出し

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button startButton = (Button) findViewById(R.id.startService);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceの起動
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });
        
        Button stopButton = (Button) findViewById(R.id.stopService);
        stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceの停止
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });
    }
}

■ Serviceクラス

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("service", "onCreate");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("service", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Override
    public void onDestroy() {
        Log.d("service", "onDestroy");
        super.onDestroy();
    }
}

bindService()を使用した起動方法

bindService()を使用して、Serviceを起動した時のライフサイクルを以下に記載します。

順序 メソッド 説明
1 onCreate() Serviceが初回作成時に呼ばれる。
2 onBind() Service接続時に呼ばれる。ServiceとActivityを仲介するIBinderを返却する。
3 ServiceConnection#
onServiceConnected()
Service接続後、Binderが確立したタイミングで呼び出される。
4 unBind() クライアントがServiceと切断されたタイミングで呼ばれる。
5 onDestroy() Serviceが使用されなくなったタイミングで呼ばれる。
Serviceの持っているリソースをクリーンアップする。

ServiceをbindService()経由で呼び出す場合は、インタフェースとしてServiceConnectionを使用します。
ServiceConnectionによって取得するIBinderを介することで、Serviceへの制御を行うことが可能です。

サンプルソース

■ AndroidManifest.xml

<service android:name="Service名" />

■ Activityの呼び出し

public class MainActivity extends Activity {

    // Serviceとのインターフェースクラス
    private ServiceConnection mConnection = new ServiceConnection() {
        BindService mBindService;
        public void onServiceConnected(ComponentName className, IBinder service) {
            // Serviceとの接続確立時に呼び出される。
            // service引数には、Onbind()で返却したBinderが渡される
            mBindService = ((BindService.LocalBinder)service).getService();
            //必要であればmBoundServiceを使ってバインドしたServiceへの制御を行う
        }
     
        public void onServiceDisconnected(ComponentName className) {
            // Serviceとの切断時に呼び出される。
            mBindService = null;
        }
    };

    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button bindButton = (Button) findViewById(R.id.bindtService);
        bindButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをbindする
                Intent i = new Intent(MainActivity.this,BindService.class);
                bindService(i, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
        
        Button unbindButton = (Button) findViewById(R.id.unbindService);
        unbindButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをunbindする
                unbindService(mConnection);
            }
        });
    }
}

■ Serviceクラス

public class BindService extends Service {
 
    // Serviceに接続するためのBinderクラスを実装する
    public class LocalBinder extends Binder {
        //Serviceの取得
        BindService getService() {
            return BindService.this;
        }
    }

    // Binderの生成
    private final IBinder mBinder = new LocalBinder();
    
    @Override
    public IBinder onBind(Intent intent) {
        // Service接続時に呼び出される
        // 戻り値として、Serviceクラスとのbinderを返す。
        Log.i("", "onBind" + ": " + intent);
        return mBinder;
    }
 
    @Override
    public void onRebind(Intent intent){
        // Unbind後に再接続する場合に呼ばれる
        Log.i("", "onRebind" + ": " + intent);
    }
 
    @Override
    public boolean onUnbind(Intent intent){
        // Service切断時に呼び出される
        //onUnbindをreturn trueでoverrideすると次回バインド時にonRebildが呼ばれる
        return true;
    }
}

参考

http://www.techdoctranslator.com/android/guide/services/bound-services
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html
http://techbooster.org/android/application/3270/