LoginSignup
5
5

More than 5 years have passed since last update.

Beacappを使って簡単なアプリを作ってみる(Android編)

Last updated at Posted at 2016-06-29

今回はBeacappを使ったAndroidアプリ開発について、ご紹介します。
iOS編はこちら。

環境準備

今回の開発に必要なものは以下の通りです。

  • ビーキャップのアカウント(3ヶ月無料)
  • Android (OS 4.3以上)
  • Beacon端末または、iPhone(発信端末として使います)
  • BeacappSDK for Android 1.2.1
  • Mac (OS X EL Capitan)
    • Android Studio 2.1.1

ビーキャップのアカウント作成

以下の記事を参考に、アカウント作成、イベント登録を実施します。
Beacappを使って簡単なアプリを作ってみる(準備編)

Androidアプリへの組み込み

アプリを組み込むプロジェクトを用意します。
既存のプロジェクトでも新規プロジェクトでも問題ありません。

今回は、新規のプロジェクトを作成します。

ちなみに、今回のAndroidサンプルはこちらに置いてあります。
Beacapp Android Sample - Github

プロジェクトの作成

MacからAndroid Studioを起動します。
[Start a new Android Studio project]を選択しましょう。

スクリーンショット 2016-06-28 午後6.33.27.png

適当にアプリ名を入力します。

スクリーンショット 2016-06-28 午後6.34.16.png

[Phone and Tablet] を選択し、[API Lebel] 4.3以上にします。

スクリーンショット 2016-06-28 午後6.34.29.png

[Empty Activity] を選択します。

スクリーンショット 2016-06-28 午後6.34.40.png

最後に適当にActivity名を入力します。

スクリーンショット 2016-06-28 午後6.34.48.png

これで、プロジェクトの作成は完成です。

プロジェクトにBeacappSDKをインポートする

GithubからBeacappSDKをダウンロードします。
BeacappSDK for Android -Github

必要なのは、BeacappSDKforAndroidフォルダ内の.jarファイルだけです。
ダウンロードしたら、Androidプロジェクトにインポートしましょう。

表示を[Project]ビューに変更します。

スクリーンショット 2016-06-28 午後9.40.08.png

あとは、[libs]フォルダにドラッグ&ドロップで終わり。
スクリーンショット 2016-06-28 午後9.41.15.png

その他の必要なライブラリをインポートする

[build.gradle]に必要なライブラリを記載します。
2種類ありますが、(Module: app)と記載のあるファイルに、情報を追記します。

スクリーンショット 2016-06-28 午後6.37.59.png

必要なライブラリは、AWSSDK関連なので、以下のように記載します。

build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.amazonaws:aws-android-sdk-core:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-ddb:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-ddb-mapper:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-kinesis:2.2.10'
}

Gradleファイルを変更すると、右上に[Sync Now]と出てきますので、Syncしておきましよう。

これで、ライブラリ関連の準備はOKです。

JBCPサービスを呼び出す

Beacappは、バックグラウンドでもBeaconを検知するために、サービスを使用していますので、サービスをManifestに追加します。

AndrdoiManifest.xml
 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <!-- Beacappを使用するために必要 -->
        <service
            android:name="com.beacapp.service.JBCPScanService"
            android:exported="false"
            android:process=":bleservice">
            <intent-filter>
                <action android:name="com.beacapp.BLEServiceAIDL"/>
                <action android:name="com.beacapp.BLEServiceCalbackAIDL"/>
            </intent-filter>
        </service>
    </application>

また、Bluetoothアクセス、GPSアクセス、インターネットアクセスを行いますので、パーミッションを記載する必要があります。

AndrdoiManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

Beacappを呼び出す

パッケージのインポート

まずは、呼び出すクラスで、必要なパッケージをインポートします。

MainActivity.java

    import com.beacapp.FireEventListener;
    import com.beacapp.JBCPException;
    import com.beacapp.JBCPManager;
    import com.beacapp.ShouldUpdateEventsListener;
    import com.beacapp.UpdateEventsListener;

  • com.beacapp.FireEventListener
    イベントの発火を受け取ります。

  • com.beacapp.JBCPException
    BeacappがスローするExceptionです。

  • com.beacapp.JBCPManager
    BeacappSDKのコア部分。

  • com.beacapp.ShouldUpdateEventsListener
    イベントの更新を行うかどうかを返却します。

  • com.beacapp.UpdateEventsListener
    イベント更新が完了したタイミングで呼び出されます。
    このタイミングで、スキャンを開始するのが良いと思います。

アクティベーション処理

JBCPManager.getManager()を呼び出せば良いのですが、通信処理を行うため、別スレッドから呼び出す形にしておきます。

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

        //通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                jbcpManager = JBCPManager.getManager(MainActivity.this,
                        "BEACAPP_REQUEST_TOKEN",
                        "BEACAPP_SECRET_KEY",
                        null);
            }
        }).start();
    }

各種リスナーの登録

リスナーをそれぞれ生成して、

MainActivity.java
// リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {

        }

        @Override
        public void onFinished(JBCPException e) {

        }
    };

    // リスナーを生成
    private ShouldUpdateEventsListener shouldUpdateEventsListener = new ShouldUpdateEventsListener() {
        @Override
        public boolean shouldUpdate(Map<String, Object> map) {
            return true;
        }
    };

    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {


        }
    };

登録します。

MainActivity.java
//通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                jbcpManager = JBCPManager.getManager(MainActivity.this,
                        "BEACAPP_REQUEST_TOKEN",
                        "BEACAPP_SECRET_KEY",
                        null);
                if (jbcpManager == null){
                    return;
                }
                // リスナーを登録
                jbcpManager.setUpdateEventsListener(updateEventsListener);
                jbcpManager.setShouldUpdateEventsListener(shouldUpdateEventsListener);
                jbcpManager.setFireEventListener(fireEventListener);

            }
        }).start();

最後にイベントの更新を要求します。

MainActivity.java
                // イベントを更新する
                jbcpManager.startUpdateEvents();

リスナー内に処理を書く

イベント更新完了のタイミングで、スキャン開始

MainActivity.java
// リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {

        }

        @Override
        public void onFinished(JBCPException e) {
            if (e != null)
            {
                jbcpManager.startScan();
            }
        }
    };

とりあえずイベントを検知したら、ログを出力するだけの処理

MainActivity.java
    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {
            JSONObject action_data = jsonObject.optJSONObject("action_data");
            String action = action_data.optString("action");


            // URLの場合
            if(action.equals("jbcp_open_url"))
            {
                Log.d("DEBUG",action_data.optString("url"));
            }
            //画像の場合
            else if(action.equals("jbcp_open_image"))
            {
                Log.d("DEBUG",action_data.optString("image"));
            }
            //カスタムの場合
            else if(action.equals("jbcp_custom_key_value"))
            {
                Log.d("DEBUG",action_data.optString("key_values"));
            }
            //テキストの場合
            else if(action.equals("jbcp_open_text"))
            {
                Log.d("DEBUG",action_data.optString("text"));
            }

        }
    };

これでおしまい、ではなく。。。

Android6だとパーミッションに記載があっても動作しません。
なので、明示的にアラートを表示して認証をしないといけません。

最終的に、以下のようになりました。

MainActivity.java
package jp.co.jmas.beacappandroidsample;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.beacapp.FireEventListener;
import com.beacapp.JBCPException;
import com.beacapp.JBCPManager;
import com.beacapp.ShouldUpdateEventsListener;
import com.beacapp.UpdateEventsListener;

import org.json.JSONObject;

import java.util.Map;

public class MainActivity extends AppCompatActivity {

    private JBCPManager jbcpManager;

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String permissions[] = {
                    Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.BLUETOOTH,
                    Manifest.permission.BLUETOOTH_ADMIN,
            };
            boolean needPermission = false;
            for (String permission : permissions) {
                if (checkSelfPermission(permission) ==
                        PackageManager.PERMISSION_GRANTED) {
                } else {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                            permission)) {
                        needPermission = true;
                    } else {
                        needPermission = true;
                    }
                }
            }
            if (needPermission) {
                showExplanationDialog(permissions, 0);
            } else {
                activate();
            }
        }
    }
    private void activate()
    {
        //通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    jbcpManager = JBCPManager.getManager(MainActivity.this,
                            "アクティベーションキー",
                            "シークレットキー",
                            null);
                } catch (JBCPException e) {
                    return;
                }

                if (jbcpManager == null){
                    return;
                }
                // リスナーを登録
                jbcpManager.setUpdateEventsListener(updateEventsListener);
                jbcpManager.setShouldUpdateEventsListener(shouldUpdateEventsListener);
                jbcpManager.setFireEventListener(fireEventListener);


                // イベントを更新する
                jbcpManager.startUpdateEvents();
            }
        }).start();
    }
    private void showExplanationDialog(final String permissions[], final int requestCode) {
        ActivityCompat.requestPermissions(MainActivity.this,
                permissions,requestCode);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (0 == requestCode) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                activate();
            }
        }
    }

    // リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {
                //何もしない

        }

        @Override
        public void onFinished(JBCPException e) {
            if (e != null)
            {
                jbcpManager.startScan();
            }

        }
    };

    // リスナーを生成
    private ShouldUpdateEventsListener shouldUpdateEventsListener = new ShouldUpdateEventsListener() {
        @Override
        public boolean shouldUpdate(Map<String, Object> map) {
            return true;
        }
    };

    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {
            JSONObject action_data = jsonObject.optJSONObject("action_data");
            String action = action_data.optString("action");


            // URLの場合
            if(action.equals("jbcp_open_url"))
            {
                Log.d("DEBUG",action_data.optString("url"));
            }
            //画像の場合
            else if(action.equals("jbcp_open_image"))
            {
                Log.d("DEBUG",action_data.optString("image"));
            }
            //カスタムの場合
            else if(action.equals("jbcp_custom_key_value"))
            {
                Log.d("DEBUG",action_data.optString("key_values"));
            }
            //テキストの場合
            else if(action.equals("jbcp_open_text"))
            {
                Log.d("DEBUG",action_data.optString("text"));
            }

        }
    };  
}

これで、実機ビルドすれば、動くと思います。
実機での確認の際には、Beacon発信アプリを用意してますので使ってみてください。
Beacheck on the App Store - iTunes - Apple

よくあるエラー

アクティベーションに失敗する!

アクティベーション時に発生するよくあるエラーに、以下のものがあります。

Error Domain=BeacappDomain Code=1000 "ネットワークエラー(401)"

これが発生した場合は、以下のケースが想定されます。

・PackageNameが間違っている(AndroidManifest内に記載)
・アクティベーションキーが間違っている
・シークレットキーが間違っている

CMSに登録した内容とあっているか、確認してみてください。

Beaconを検知しない

デバッグ用として、BeaconEventListnerクラスを用意しています。
SCAN_MODEを2にして、リスナーを登録すると検知できます。
※あくまでもテスト用なので、リリース時には使用しないでください。

MainActivity.java
// リスナーを生成
    public BeaconEventListener beaconEventListener = new BeaconEventListener() {
        @Override
        public boolean targetBeaconDetected(BeaconEvent beaconEvent) {
            // CMSで検知対象に登録されているBeacon
            return false;
        }

        @Override
        public boolean nonTargetBeaconDetected(BeaconEvent beaconEvent) {
            // CMSで検知対象になっていないBeacon
            return false;
        }
    };
何も反応しない

Android,Beacon端末の設定を確認してください。
・位置情報はONになっているか
・位置情報は許可されているか
・Bluetoothは許可されているか
・端末の故障などで、電波が出ていない

nonTargetBeaconDetectedだけ反応している

・Beacappに登録したBeaconのUUIDに誤りがある

targetBeaconDetectedが反応する

設定は正しくできています。
端末から距離を離れてみたり、動いてみたり、少し待ってみてください。

その他

その他、お困りごとがあれば、お気軽にお問い合わせください。
Beacapp サポートチーム

今後もBeacappを使ったアプリ開発について、ご紹介させていただきます。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5