2016年01月13日

Cocos2d-X、AndroidでAdMobの広告を表示する

概要

Cocos2d-x、AndroidでAdMobの広告を表示する方法についてのメモです。

次のような流れで書いていきます。AdMob側の設定は済んでいる事を前提としています。

  1. Google Repositoryをインストールする。
  2. AdMobのライブラリをバンドルする。
  3. パーミッションの設定を行う。
  4. メタデータの設定と広告アクティビティの設定を行う。
  5. 広告ユニットIDをリソースに追加する。
  6. バナー広告を作成する処理を作成する。
  7. バナー広告の表示、非表示を切り替える処理を作成する。
  8. インタースティシャル広告を表示する処理を作成する。
  9. C++からJavaを呼び出す処理を作成する。
  10. テストデバイスの設定を行う。

Google Repositoryをインストールする

Android StudioのSDK ManagerからGoogle Repositoryをインストールします。

AdMobのライブラリをバンドルする

build.gradleを編集して、AdMobのライブラリをバンドルします。

dependencies {
    ...
    compile 'com.google.android.gms:play-services-ads:8.4.0'
}

パーミッションの設定を行う

AndroidManifest.xmlのmanifestタグにパーミッションの設定を追記します。

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

メタデータの設定と広告アクティビティの設定を行う

AndroidManifest.xmlのapplicationタグにメタデータの設定と広告アクティビティの設定を追記します。

<meta-data android:name="com.google.android.gms.version"
           android:value="@integer/google_play_services_version" />

<activity
    android:name="com.google.android.gms.ads.AdActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
    android:theme="@android:style/Theme.Translucent" />

広告ユニットIDをリソースに追加する

AdMobから割り振られる広告ユニットIDをres/valuesに追加します。ここではbanneradunit_idという名前で追加しました。

<string name="banner_ad_unit_id">ca-app-pub-*****</string>

バナー広告を作成する処理を作成する

onCreateでバナー広告を作成する処理を作成します。

テストデバイスIDのところは後述します。

import android.widget.LinearLayout;
import android.view.Gravity;

    /** バナー広告 */
    private AdView mAdView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
        ...

        // 広告ビューのレイアウトパラメータを作成する
        LinearLayout.LayoutParams adParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        // 表示位置を左上に設定する
        adParams.gravity = (Gravity.TOP | Gravity.LEFT);

        // 広告ビューを作成する
        mAdView = new AdView(this);
        mAdView.setAdSize(AdSize.BANNER);
        mAdView.setAdUnitId(getString(R.string.banner_ad_unit_id));

        // テストデバイスのIDを指定してAdRequestを作成する
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                .addTestDevice(getString(R.string.test_device_id))
                .build();

        // 広告を読み込む
        mAdView.loadAd(adRequest);

        // 広告ビューをアクティビティに追加する
        addContentView(mAdView, adParams);
    }

バナー広告の表示、非表示を切り替える処理を作成する

ゲームのプレイ画面では広告を表示せずに、ポーズ画面などで表示させるような場合のために、表示・非表示を切り替えることができるようにします。

C++から呼べるようにstatic関数で作成します。

import android.view.View;

    /**
     * バナー広告を表示する。
     */
    public static void viewAdBanner() {

        // 広告表示切替のスレッドを開始する
        me.runOnUiThread(new Runnable() {

            @Override
            public void run() {

                // 広告が無効な場合は有効にする
                if (!me.mAdView.isEnabled()) {
                    me.mAdView.setEnabled(true);
                }

                // 広告が表示でない場合は表示にする
                if (me.mAdView.getVisibility() != View.VISIBLE) {
                    me.mAdView.setVisibility(View.VISIBLE);
                }
            }
        });
    }

    /**
     * バナー広告を非表示にする。
     */
    public static void hideAdBanner() {

        // 広告表示切替のスレッドを開始する
        me.runOnUiThread(new Runnable() {

            @Override
            public void run() {

                // 広告が有効な場合は無効にする
                if (me.mAdView.isEnabled()) {
                    me.mAdView.setEnabled(false);
                }

                // 広告が非表示でない場合は非表示にする
                if (me.mAdView.getVisibility() != View.INVISIBLE) {
                    me.mAdView.setVisibility(View.INVISIBLE);
                }
            }
        });
    }

インタースティシャル広告を表示する処理を作成する

インタースティシャル広告を表示する処理を作成します。onCreateで事前にインタースティシャル広告の要求をしておいて、表示する時にラグが発生しないようにします。

広告を閉じると次の広告を要求するようにします。

広告の表示はC++から呼べるようにstatic関数で作成します。

import com.google.android.gms.ads.InterstitialAd;

    /** インタースティシャル広告 */
    private InterstitialAd mInterstitialAd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        // インタースティシャル広告を作成する
        mInterstitialAd = new InterstitialAd(this);
        mInterstitialAd.setAdUnitId(getString(R.string.banner_ad_unit_id));
        requestNewInterstitial();

        // インタースティシャル広告を閉じた時に次の広告を読み込むためにリスナーを登録する
        mInterstitialAd.setAdListener(new AdListener() {

            @Override
            public void onAdClosed() {
                requestNewInterstitial();
            }
        });
    }
    
    /**
     * インタースティシャル広告を表示する。
     */
    public static void viewAdInterstitial() {

        // 広告表示のスレッドを開始する
        me.runOnUiThread(new Runnable() {

            @Override
            public void run() {

                // 広告が読み込まれている場合は広告を表示する
                if (me.mInterstitialAd.isLoaded()) {
                    me.mInterstitialAd.show();
                }
            }
        });
    }

    /**
     * インタースティシャル広告を要求する。
     */
    private void requestNewInterstitial() {

        // テストデバイスのIDを指定してAdRequestを作成する
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                .addTestDevice(me.getString(R.string.test_device_id))
                .build();

        // 広告を読み込む
        mInterstitialAd.loadAd(adRequest);
    }

C++からJavaを呼び出す処理を作成する

C++からJavaを呼び出す処理を作成します。

#include <assert.h>
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#define JNICLASSNAME "org/cocos2dx/cpp/AppActivity"

using cocos2d::JniMethodInfo;
using cocos2d::JniHelper;

// バナー広告表示
void Advertisement::viewBanner()
{
    // ネイティブコードのバナー広告表示関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "viewAdBanner", "()V"))
    {
        assert(false);
        return;
    }
    
    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
    
    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
    
// バナー広告消去
void Advertisement::hideBanner()
{
    // ネイティブコードのバナー広告表示関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "hideAdBanner", "()V"))
    {
        assert(false);
        return;
    }
    
    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
    
    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
    
// インタースティシャル広告表示
void Advertisement::viewInterstatial()
{
    // ネイティブコードのバナー広告表示関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "viewAdInterstitial", "()V"))
    {
        assert(false);
        return;
    }
    
    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
    
    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

テストデバイスの設定を行う

実行してログを確認するとデバイスIDが出力されるので、その内容をコピーしてリソースに追加します。

I Ads : Use AdRequest.Builder.addTestDevice("デバイスID") to get test ads on this device.

ラベル:android cocos2d AdMob
posted by かねだ at 09:51| Comment(0) | 開発方法メモ | このブログの読者になる | 更新情報をチェックする

2016年01月11日

Cocos2d-x、AndroidでGoogle Play Games ServicesのLeaderboardを使用する

概要

Cocos2d-xでGoogle Play Games ServicesのLeaderboardを使う方法についてのメモです。あんまりCocos2d-x関係ないですが。

以下のような流れで書いていきます。

  1. Google Play Developer Consoleからゲームサービスを登録する。
  2. Google Play services SDK、Google Repositoryをインストールする。
  3. build.gradleにGoogle Play Games Servicesを追加する。
  4. AndroidManifest.xmlにApp IDを指定する記述を追加する。
  5. Cocos2dxActivityの継承元クラスをFragmentActivityに変更する。
  6. Google APIに接続する処理を作成する。
  7. Leaderboardにスコアを送信する処理を作成する。
  8. Leaderboardを表示する処理を作成する。
  9. C++からJavaの関数を呼び出す処理を作成する。
  10. Signed APKを作成してインストールする。

Google Play Developer Consoleからアプリを登録する

Developerの登録とアプリの登録は済んでいることを前提に書いていきます。

まずGoogle Play Developer Consoleからゲームサービスを登録します。あまり悩むところも無いと思うので箇条書きで書いていきます。

  • 左側の"ゲームサービス"を選んで、"Google Play ゲーム サービスをセットアップ"のボタンをクリックします。
  • ゲームの名前と種類を入力するダイアログが表示されるので、入力して"次へ"をクリックします。
  • "リンク済みアプリ"を選んで、"Android"のボタンをクリックします。
  • アプリの名前とパッケージ名を入力して、"保存して次へ"をクリックします。
  • 今すぐアプリを承認"をクリックします。
  • 署名証明書フィンガープリントというものが表示されますが、気にせず"確認"をクリックします。
  • 次に"リーダーボード"を選択して、"リーダーボードを追加"をクリックします。
  • 名前を入力して、"保存"をクリックします。
  • リーダーボードのIDが表示されている下辺りにある"リソースを取得"をクリックします。
  • "Android"のタブを選択して、表示されるxmlをコピーして、コメントに書いてあるようにgames-ids.xmlというファイル名で保存して、プロジェクトのリソースに追加します。
  • Developerに登録しているアカウント以外でテストする場合には"テスト"を選択して、テスト用アカウントに登録します。

Google Play services SDK、Google Repositoryをインストールする

Android StudioのSDK Managerを使用して、Google Play services SDKとGoogle Repositoryをインストールします。

build.gradleにGoogle Play Games Servicesを追加する

proj.android-studio/app/build.gradleを編集して、Google Play Games Servicesをリンクするようにします。

dependencies {
    ...

    compile 'com.google.android.gms:play-services-games:8.4.0'

    ...
}

AndroidManifest.xmlにApp IDを指定する記述を追加する

AndroidManifest.xmlにApp IDを指定する記述を追加します。ここで指定するApp IDはゲームサービス登録して時に取得しておいたgames-ids.xmlから取ってきています。

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher">

    <meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="@string/app_id" />
    <meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

</application>

Cocos2dxActivityの継承元クラスをFragmentActivityに変更する

Leaderboardとは直接関係ないのですが、Googleのサンプルプログラムでエラーが発生した時のダイアログを出すのにFragmentを使用しているので、これを使えるようにCocos2dxActivityの継承元クラスをFragmentActivityに変更します。

Cocos2d-xのソースをいじるのは抵抗があるのですが、サンプルをそのまま使いたいので変更してしまいます。

import android.support.v4.app.FragmentActivity;

public abstract class Cocos2dxActivity extends FragmentActivity implements Cocos2dxHelperListener {

FragmentActivityを使えるようにするためにlibcocos2dx/build.gradleも変更します。

android {
   compileSdkVersion 23
   ...
}

dependencies {
    ...
    compile "com.android.support:support-v4:23.1.1"
}

Google APIに接続する処理を作成する

前準備が整ったので、Google APIにアクセスする処理を作成していきます。AppActivity.javaに処理を追加していきます。

まずはimportの追加から。

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.games.Games;

AppActivityをGoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListenerをimplementsするように変更します。

public class AppActivity extends Cocos2dxActivity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

onCreateにGoogleApiClientを作成する処理を追加します。

/** Google APIにアクセスするためのクライアント */
private GoogleApiClient mGoogleApiClient;

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Games.API).addScope(Games.SCOPE_GAMES)
                .build();

    ...

onConnected、onConnectionSuspendedを実装します。

    @Override
    public void onConnected(Bundle connectionHint) {

    }

    @Override
    public void onConnectionSuspended(int cause) {

        // Google APIへ接続する
        mGoogleApiClient.connect();
  }

onConnectionFailedとエラー処理を実装します。まずはエラーダイアログを表示する部分を作成します。

import android.app.Dialog;
import android.content.IntentSender.SendIntentException;
import android.content.DialogInterface;
import android.support.v4.app.DialogFragment;

public class AppActivity extends Cocos2dxActivity implements

GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    /** Unique tag for the error dialog fragment */
    private static final String DIALOG_ERROR = "dialog_error";

    /** Request code to use when launching the resolution activity */
    private static final int REQUEST_RESOLVE_ERROR = 1001;

    ...

    /**
     * Creates a dialog for an error message
     * @param errorCode エラーコード
     */
    private void showErrorDialog(int errorCode) {

        // Create a fragment for the error dialog
        ErrorDialogFragment dialogFragment = new ErrorDialogFragment();

        // Pass the error that should be displayed
        Bundle args = new Bundle();
        args.putInt(DIALOG_ERROR, errorCode);
        dialogFragment.setArguments(args);
        dialogFragment.show(getSupportFragmentManager(), "errordialog");
    }

    /**
     *  Called from ErrorDialogFragment when the dialog is dismissed.
     */
    public void onDialogDismissed() {
        mResolvingError = false;
    }

    /**
     *  A fragment to display an error dialog
     */
    public static class ErrorDialogFragment extends DialogFragment {

        /**
         * コンストラクタ。無処理。
         */
        public ErrorDialogFragment() {
        }

        /**
         * ダイアログ作成処理。
         * Google APIのエラーダイアログを作成する。
         * @param savedInstanceState 状態変数
         * @return エラーダイアログ
         */
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {

            // Get the error code and retrieve the appropriate dialog
            int errorCode = this.getArguments().getInt(DIALOG_ERROR);

            // エラーダイアログを作成する
            return GoogleApiAvailability.getInstance().getErrorDialog(
                    this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
        }

        /**
         * 終了時処理。
         * アクティビティのダイアログ終了処理を呼び出す。
         * @param dialog ダイアログ
         */
        @Override
        public void onDismiss(DialogInterface dialog) {

            // アクティビティのダイアログ終了処理を呼び出す
            ((AppActivity)getActivity()).onDialogDismissed();
        }
    }
}

onConnectionFailedの処理は以下の通りです。

    /** Google API接続エラーを解決中かどうか */
    private boolean mResolvingError = false;

    /**
     * Google APIへの接続に失敗した時の処理。
     * 解決方法がある場合は解決を試みる。
     * @param result
     */
    @Override
    public void onConnectionFailed(ConnectionResult result) {

        if (mResolvingError) {

            // すでにエラー解決中の場合は何もしない
        }
        else if (result.hasResolution()) {

            // 解決方法がある場合は解決を試みる
            try {

                // エラー解決中のフラグを立てる
                mResolvingError = true;

                // 解決を試みる
                result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);

            } catch (SendIntentException e) {

                // エラー解決中に例外が発生した場合は、再度接続を試みる
                mGoogleApiClient.connect();
            }
        }
        else {

            // 解決方法がない場合はエラーダイアログを表示する
            showErrorDialog(result.getErrorCode());
            mResolvingError = true;
        }
    }

onStartでGoogle APIへの接続、onStopでGoogle APIの切断の処理を追加します。

    /**
     * Activity開始時の処理。
     * Google APIへ接続する。
     */
    @Override
    protected void onStart() {

        // 基底クラスの処理を呼び出す
        super.onStart();

        // エラー解決中でない場合はGoogle APIへの接続を行う。
        // Googleへのサインインを行って戻る場合などもonStartが走るが、
        // この時にも接続を行おうとすると二重に接続することになるので、
        // これを防止するため。
        if (!mResolvingError) {
            mGoogleApiClient.connect();
        }
    }

    /**
     * Activity停止時の処理。
     * Google APIを切断する。
     */
    @Override
    protected void onStop() {

        // Google APIを切断する
        mGoogleApiClient.disconnect();

        // 基底クラスの処理を呼び出す
        super.onStop();
    }

Leaderboardにスコアを送信する処理を作成する

Leaderboardにスコアを送信する処理を作成します。JNIから呼び出すため、static関数とします。

R.string.leaderboard_scoreのところは、Google Developer Consoleで登録したLeaderboardの名前で変わってきますので、games-ids.xmlのLeaderboard IDを設定している所を参照してください。

    /**
     * Leaderboardにハイスコアを送信する。
     * @param score スコア
     */
    public static void postHighScore(int score) {

        if (me.mGoogleApiClient != null && me.mGoogleApiClient.isConnected()) {

            // Leaderboardにハイスコアを送信する
            Games.Leaderboards.submitScore(me.mGoogleApiClient, me.getString(R.string.leaderboard_score), score);
        }
    }

Leaderboardを表示する処理を作成する

Leaderboardを表示する処理を作成します。JNIから呼び出すため、static関数とします。

R.string.leaderboard_scoreのところは、Google Developer Consoleで登録したLeaderboardの名前で変わってきますので、games-ids.xmlのLeaderboard IDを設定している所を参照してください。

    /** Leaderboard表示のリクエストコード */
    private static final int REQUEST_LEADERBOARD = 1002;

    /**
     * Leaderboardの画面を表示する。
     */
    public static void openRanking() {

        if (me.mGoogleApiClient != null && me.mGoogleApiClient.isConnected()) {

            // Leaderboardのアクティビティを開始する
            me.startActivityForResult(
                    Games.Leaderboards.getLeaderboardIntent(me.mGoogleApiClient, me.getString(R.string.leaderboard_score)),
                    REQUEST_LEADERBOARD);
        }
    }

C++からJavaの関数を呼び出す処理を作成する

C++からJavaの関数を呼び出す処理を作成します。

#include <assert.h>
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#define JNICLASSNAME "org/cocos2dx/cpp/AppActivity"

using cocos2d::JniMethodInfo;
using cocos2d::JniHelper;

// ランキング画面表示
void OnlineScore::openRanking()
{
    // ネイティブコードのランキング画面表示関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "openRanking", "()V"))
    {
        assert(false);
        return;
    }

    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);

    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

// ハイスコア送信
void OnlineScore::postHighScore(int score)
{
    // ネイティブコードのハイスコア送信関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "postHighScore", "(I)V"))
    {
        assert(false);
        return;
    }

    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, score);

    // リソースを解放する
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

Signed APKを作成してインストールする

Signed APKでないとGoogle Playに接続することができないので、テストをするときにはSigned APKを作成してインストールするなど必要があります。

Signed APKの作成方法は次のサイトを参考にしました。

Android Studioで APK ファイルを作成する - Android Studioでアプリ開発!

作成したAPKファイルをAndroidにインストールするにはターミナルからコマンドを入力します。Signed APKをインストールする前に署名なしのものをインストールしている場合は先にアンインストールをする必要があります。

$ adb uninstall パッケージ名
$ adb -d install APKファイルのパス

あとはAndroidの画面からアプリを実行すればテストできます。

なお、Android Studio経由でなく、Androidの画面から実行したアプリのログを見る場合は次のようにターミナルからlogcatを実行します。オプションを何も付けなければ全部のログが表示されます。

$ adb logcat 見たいログのタグ名:V *:S
ラベル:android cocos2d
posted by かねだ at 11:53| Comment(0) | 開発方法メモ | このブログの読者になる | 更新情報をチェックする

2016年01月06日

Cocos2d-x、Androidでスクリーンショットを付けてTwitter投稿をする

概要

Cocos2d-xでスクリーンショットを付けてTwitter投稿をAndroid側で行う方法についてのメモです。

iOS側の処理についての記事はこちらです。

以下の様な流れで書いていきます。

  1. スクリーンショットを保存する。
  2. JNIでJavaの関数を呼び出すC++の関数を作成する。
  3. AppActivityのonCreateでstaticメンバにthisインスタンスを設定する。
  4. AppActivityにTwitter投稿関数を作成する。
  5. テキストのインテントを作成する処理を追加する。
  6. 画像を外部ストレージに保存する処理を追加する。
  7. 画像をインテントに追加する処理を追加する。
  8. AndroidManifest.xmlに外部ストレージ使用のパーミッションを追加する。
  9. Twitterアプリがインストールされていない場合のエラー処理を追加する。

スクリーンショットを保存する

スクリーンショットを作成する処理はC++の部分で行っているので、iOSと共通です。画像の保存までは少し時間がかかるので、私の場合はちょっと間を置いてからツイートを行う処理を行いました。普通はコールバック関数で終了を受け取ってから処理を行うと思います。

// スクリーンショット用テクスチャを作成する
RenderTexture *texture = RenderTexture::create(width, height);
texture->setPosition(cocos2d::Vector2(width / 2, height / 2));

// スクリーンショットを撮り始める
texture->begin();

// 画面の描画
this->visit();

// スクリーンショットを撮り終える
texture->end();

// スクリーンショットを保存する
texture->saveToFile("screenshot.png", cocos2d::Image::Format::PNG);

JNIでJavaの関数を呼び出すC++の関数を作成する

JNIを使用してJavaの関数を呼び出すC++の関数を作成します。ここではTwitterクラスを作ってstaticメンバ関数としてpostを用意しました。

#include "Twitter.h"
#include <assert.h>
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#define JNICLASSNAME "org/cocos2dx/cpp/AppActivity"

using cocos2d::JniMethodInfo;
using cocos2d::JniHelper;

// 投稿ビュー表示
void Twitter::post(const char *message, const char *imagepath)
{
    // ネイティブコードのツイート関数を取得する
    JniMethodInfo methodInfo;
    if (!JniHelper::getStaticMethodInfo(methodInfo, JNICLASSNAME, "postTweet", "(Ljava/lang/String;Ljava/lang/String;)V"))
    {
        assert(false);
        return;
    }

    // 文字列をJava Stringに変換する
    jstring message_jstr = methodInfo.env->NewStringUTF(message);
    jstring imagepath_jstr = methodInfo.env->NewStringUTF(imagepath);

    // 関数を呼び出す
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID , methodInfo.methodID , message_jstr, imagepath_jstr);

    // リソースを解放する
    methodInfo.env->DeleteLocalRef(message_jstr);
    methodInfo.env->DeleteLocalRef(imagepath_jstr);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);
}

getStaticMethodInfoの引数でJavaの関数名と型を指定します。"(Ljava/lang/String;Ljava/lang/String;)V"の部分はシグネチャというもので、String型の引数が2つあり、戻り値はvoidであることを表すそうです。

なお、このソースをXcodeで作成した場合は、iOSで使用されないようにTarget Membershipから外す必要があります。

AppActivityのonCreateでstaticメンバにthisインスタンスを設定する

ここからはJavaの処理になります。

JNIで使用できるのはstatic関数だけなので、AppActivityのメンバにアクセスするためにstaticメンバ変数にthisインスタンスを入れておきます。

public class AppActivity extends Cocos2dxActivity {

    /** static関数からメンバ関数呼び出すためのthisインスタンス */
    private static AppActivity me = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // 基底クラスの処理を呼び出す
        super.onCreate(savedInstanceState);

        // staticメンバにthisインスタンスを設定する
        me = this;
    }

    ...

AppActivityにTwitter投稿関数を作成する

AppActivityにTwitter投稿用の関数を用意します。public staticにして、関数名と引数はgetStaticMethodInfoで指定したものと同じにします。

    /**
     * Twitter投稿用のインテントを作成し、アクティビティを呼び出す。
     * @param message ツイート内容
     * @param imagePath 画像パス
     */
    public static void postTweet(String message, String imagePath) {

    }

テキストのインテントを作成する処理を追加する

まずはテキストを投稿する部分の処理を作成します。

先ほどのpostTweetに以下の処理を追加します。

        // インテントを作成する。
        Intent intent = new Intent(Intent.ACTION_SEND);

        // インテントにメッセージを追加する
        intent.putExtra(Intent.EXTRA_TEXT, message);

        // パッケージタイプをTwitterにする
        intent.setPackage("com.twitter.android");

        // アクティビティを呼び出す。
        me.startActivity(intent);

画像を外部ストレージに保存する処理を追加する

すでにCocos2d-x側の処理でスクリーンショットを保存しているのですが、この画像ファイルを他のアプリからもアクセスできるように外部ストレージへとコピーします。

    /**
     * パスからファイル名を取得する。
     * @param imagePath 画像ファイルのパス
     * @return 画像ファイルのファイル名
     */
    private static String getFilename(Uri imagePath) {

        // URLの最後のセグメントを返す
        return imagePath.getLastPathSegment();
    }

    /**
     * ファイルを外部ストレージへ保存する。
     * @param imageUri 元画像のURI
     * @return 外部ストレージのURI
     */
    private static Uri saveImageToExternalDirectory(Uri imageUri) {

        // コピー先ファイルのFileインスタンスを作成する
        File dst = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), getFilename(imageUri));

        // コピー元ファイルのFileインスタンスを作成する
        File src = new File(imageUri.getPath());

        try {

            // ファイルをコピーする
            copyFile(src, dst);

        } catch (Exception e) {

            return null;
        }

        // コピー先ファイルからURIを作成して返す
        return Uri.fromFile(dst);
    }

    /**
     * ファイルをコピーする。
     * @param src コピー元ファイル
     * @param dst コピー先ファイル
     * @throws Exception ファイルアクセス異常
     */
    private static void copyFile(File src, File dst) throws Exception {

        // コピー元ファイルの入力ストリームを作成する
        InputStream in = new FileInputStream(src);

        // コピー先ファイルの出力ストリームを作成数r
        OutputStream out = new FileOutputStream(dst);

        // コピー元ファイルの内容を読み込み、コピー先ファイルへ書き込んでいく
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }

        // 入出力ストリームを閉じる
        in.close();
        out.close();
    }

画像をインテントに追加する処理を追加する

先ほどのpostTweet関数に、画像のコピーとインテントへの追加を行う処理を追加します。

    public static void postTweet(String message, String imagePath) {

        ...

        // 画像パスが指定されている場合は画像の処理を行う
        if (!imagePath.equals("")) {

            try {

                // 画像パスからURIを作成する
                Uri originalImageUri = Uri.parse(imagePath);

                // 画像の拡張子を取得する
                String ext = getExtension(originalImageUri);

                // 他のアプリから読み込めるように外部ストレージへと画像をコピーする
                Uri readableFileUri = saveImageToExternalDirectory(originalImageUri);

                // 画像のコピーに成功した場合はインテントに格納する
                if (readableFileUri != null) {
                    intent.setType("image/" + ext);
                    intent.putExtra(Intent.EXTRA_STREAM, readableFileUri);
                }

            } catch (Exception e) {

                // 画像のコピーに失敗した場合は画像なしでツイートを行う
                // ここでは何もしない
            }
        }

        ...

    }

    /**
     * パスからファイルの拡張子を取得する。
     * @param imagePath 画像ファイルのパス
     * @return 画像ファイルの拡張子
     */
    private static String getExtension(Uri imagePath) {

        // URIからパス文字列を取得する
        String pathString = imagePath.getPath();

        // 一番後ろの"."以降の文字列を切り出して返す
        return pathString.substring(pathString.lastIndexOf(".") + 1);
    }

AndroidManifest.xmlに外部ストレージ使用のパーミッションを追加する

外部ストレージに画像をコピーするので、以下のようにAndroidManifest.xmlにパーミッションの記述を追加します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.monochromesoft.toritoma2"
    android:installLocation="auto">

    ....

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

    ...

Twitterアプリがインストールされていない場合のエラー処理を追加する

Twitterアプリが何も入っていない状態の端末で実行するとstartActivityで例外が発生してしまったので、その場合にはTwitterのURLを指定してブラウザを起動するようにインテントを作り直します。

対応するアプリがあるかを調べることができれば良いのですが、やり方がわからなかったのでtry catchで処理しています。

最終的なpostTweet関数は以下の通りです。

    /**
     * Twitter投稿用のインテントを作成し、アクティビティを呼び出す。
     * @param message ツイート内容
     * @param imagePath 画像パス
     */
    public static void postTweet(String message, String imagePath) {

        // インテントを作成する。
        Intent intent = new Intent(Intent.ACTION_SEND);

        // インテントにメッセージを追加する
        intent.putExtra(Intent.EXTRA_TEXT, message);

        // パッケージタイプをTwitterにする
        intent.setPackage(TWITTER_PACKAGE_NAME);

        // 画像パスが指定されている場合は画像の処理を行う
        if (!imagePath.equals("")) {

            try {

                // 画像パスからURIを作成する
                Uri originalImageUri = Uri.parse(imagePath);

                // 画像の拡張子を取得する
                String ext = getExtension(originalImageUri);

                // 他のアプリから読み込めるように外部ストレージへと画像をコピーする
                Uri readableFileUri = saveImageToExternalDirectory(originalImageUri);

                // 画像のコピーに成功した場合はインテントに格納する
                if (readableFileUri != null) {
                    intent.setType("image/" + ext);
                    intent.putExtra(Intent.EXTRA_STREAM, readableFileUri);
                }

            } catch (Exception e) {

                // 画像のコピーに失敗した場合は画像なしでツイートを行う
                // ここでは何もしない
            }
        }

        try {

            // アクティビティを呼び出す。
            me.startActivity(intent);

        } catch (ActivityNotFoundException e) {

            // Twitterクライアントがない場合は代わりにブラウザでTwitterのサイトを開くようにする。
            // 画像を付けることはできないため、テキストメッセージだけを付ける。

            // Twitter投稿用インテントに設定するURLを作成する。
            // TwitterのURLのパラメータにメッセージを設定する。
            // ハッシュタグの#の文字はそのままでは処理できないため、URLエンコードの%23に置換する。
            String twitterUrl = String.format("http://twitter.com/share?text=%s", message).replaceAll("#", "%23");

            // ブラウザ表示用のインテントを作成する
            Intent intentForBrowser = new Intent(Intent.ACTION_VIEW, Uri.parse(twitterUrl));

            // アクティビティを呼び出す。
            me.startActivity(intentForBrowser);
        }
    }

参考Webサイト

cocos2d-x向けの究極のソーシャル連携プラグイン作った - 5.1さらうどん

ラベル:cocos2d android
posted by かねだ at 06:29| Comment(0) | 開発方法メモ | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。