DXライブラリ Android版を使用した Androidアプリで Java のコードで HTTP通信を行う


 すべての処理を C++ で実行できれば良いのですが、Android の基本言語が Java である関係で、
今のところ Java を使用しないと HTTP通信を行うことができません。( ソケットによる HTTP通信を
行う場合は恐らく C++ のみで実現可能ですが… )

 なので、ここでは『DXライブラリ Android版』には無い HTTP通信処理を Java を使用して
行うための手順を記します。


 1.『AndroidManifest.xml』の内容を インターネットの使用と Java のコードを実行できるように変更する

 2.プロジェクトに Java のソースファイルを追加する

 3.Java のコードを入力する

 4.Java の情報を取得する C++ のコードを入力する



1.プロジェクトの設定と『『AndroidManifest.xml』の内容を インターネットの使用と Java のコードを実行できるように変更する

  VisualStudio Community 2019 ( 又は 2017 )『DXライブラリ Android版』を使用する Androidアプリのプロジェクトを開き
 『ソリューションエクスプローラー』と書かれているリストに表示されている『AndroidManifest.xml』
 クリックして、内容を表示します。

  『AndroidManifest.xml』の内容は、使い方ページに沿って編集した場合は以下のようになっていると思いますが、
<?xml version="1.0" encoding="utf-8"?> <!-- Changes made to Package Name should also be reflected in the Debugging - Package Name property, in the Property Pages --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.$(ApplicationName)" android:versionCode="1" android:versionName="1.0"> <!-- This is the platform API where NativeActivity was introduced. --> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/> <!-- This .apk has no Java code itself, so set hasCode to false. --> <application android:label="@string/app_name" android:hasCode="false" android:theme="@android:style/Theme.NoTitleBar"> <!-- Our activity is the built-in NativeActivity framework class. This will take care of integrating with our NDK code. --> <activity android:name="android.app.NativeActivity" android:label="@string/app_name" android:configChanges="orientation|screenSize" android:launchMode="singleInstance"> <!-- Tell NativeActivity the name of our .so --> <meta-data android:name="android.app.lib_name" android:value="$(AndroidAppLibName)"/> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>

  この中の以下のように変更します。(色が緑や赤の部分が変更箇所や追加箇所で、6箇所です)
<?xml version="1.0" encoding="utf-8"?> <!-- Changes made to Package Name should also be reflected in the Debugging - Package Name property, in the Property Pages --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.$(ApplicationName)" android:versionCode="1" android:versionName="1.0"> <!-- This is the platform API where NativeActivity was introduced. --> <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23"/> <uses-permission android:name="android.permission.INTERNET"/> <!-- This .apk has no Java code itself, so set hasCode to false. --> <application android:label="@string/app_name" android:hasCode="true" android:theme="@android:style/Theme.NoTitleBar"> <!-- Our activity is the built-in NativeActivity framework class. This will take care of integrating with our NDK code. --> <activity android:name="com.(プロジェクト名).Packaging.(プロジェクト名)" android:label="@string/app_name" android:configChanges="orientation|screenSize" android:launchMode="singleInstance"> <!-- Tell NativeActivity the name of our .so --> <meta-data android:name="android.app.lib_name" android:value="$(AndroidAppLibName)"/> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
 ( (プロジェクト名)となっている箇所は、お手元のプロジェクトの名前を入力してください )



2.プロジェクトに Java のソースファイルを追加する

  次に Java のソースファイルを以下の手順でプロジェクトに追加します。

  ① 『ソリューションエクスプローラー』と書かれているリストに表示されている『(プロジェクト名).Packaging』
    を右クリックして、表示されるプルダウンメニューから『追加(D)』→『新しいフォルダー(D)』を選択して、
    『新しいフォルダー』を追加して、名前を『src』としてください。

  ② 次に追加した『src』フォルダーを右クリックして、再びプルダウンメニューから『追加(D)』→『新しいフォルダー(D)』を選択して、
    『新しいフォルダー』を追加して、今度は名前を『com』としてください。

  ③ 次に追加した『com』フォルダーを右クリックして、再びプルダウンメニューから『追加(D)』→『新しいフォルダー(D)』を選択して、
    『新しいフォルダー』を追加して、今度は名前を『(プロジェクト名)』( (プロジェクト名) はお手元のプロジェクトの名前に置き換えてください )としてください。

  ④ 次に追加した『(プロジェクト名)』フォルダーを右クリックして、再びプルダウンメニューから『追加(D)』→『新しいフォルダー(D)』を選択して、
    『新しいフォルダー』を追加して、今度は名前を『Packaging』としてください。

    と、要は『(プロジェクト名).Packaging』以下に『src』→『com』→『(プロジェクト名)』→『Packaging』というフォルダ
    を作ります。次に漸く Java ファイルの追加です。

  ⑤ 追加した『Packaging』フォルダーを右クリックして、今度は『新しい項目(W)...』を選択して『新しい項目の追加』ダイアログを表示します。

  ⑥ ダイアログ左側のリストから『インストール済み』→『Cross Platform』→『Android』を選び、
    右側のリストから『Java』を選びます。

  ⑦ ダイアログ下側の『名前(N):』の項目に『(プロジェクト名).java』と入力して『追加(A)』ボタンを押します。

  ⑧ 次に追加された『(プロジェクト名).java』を開いた状態でメニューから『ファイル(F)』→『名前を付けて(プロジェクト名).javaを保存(A)...』を
    選択して、『名前を付けてファイルを保存』ダイアログを表示します。
    ( VisualStudio 2015 の場合はメニューから『ファイル(F)』→『保存オプションの詳細設定(V)...』を選択して、『保存オプションの詳細設定』ダイアログを表示します )

  ⑨ 『名前を付けてファイルを保存』ダイアログの右下にある『上書き保存(S)』の項目を『エンコード付きで保存(V)...』
    に変更します。すると変更した瞬間に『名前を付けて保存の確認』ダイアログが表示されますので『はい(Y)』を選択してください。
    ( VisualStudio 2015 の場合はこの工程が無いので、手順⑩に進んでください )

  ⑩ 次に『保存オプションの詳細設定』ダイアログの『エンコード(E):』の項目を『日本語 (シフト JIS) - コードページ 932』から
    『Unicode (UTF-8 シグネチャなし) - コードページ 65001』に変更して『OK』ボタンを押します。
    ( 『Unicode (UTF-8 シグネチャ付き) - コードページ 65001』と間違えないようにしてください、
     『Unicode (UTF-8 シグネチャなし) - コードページ 65001』はリストのかなり下の方にあります )


 ( (プロジェクト名)となっている箇所は、お手元のプロジェクトの名前を入力してください )


  これで java ファイルの追加は完了です。



3.Java のコードを入力する

  追加した『(プロジェクト名).java』には最初から数行書かれていますが、それを全部消して、代わりに
 今回の『Java による HTTP通信』を行うための以下のコードを入力します。
package com.(プロジェクト名).Packaging; import android.app.NativeActivity; import java.net.URL; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.io.InputStream; import java.io.IOException; import java.lang.Thread; public class (プロジェクト名) extends NativeActivity { // 接続 URL 受け渡し用変数 static String HTTP_URLString ; // メソッドタイプ受け渡し用変数 static int HTTP_MethodType ; // 通信処理の終了確認用変数 static int HTTP_Connect_Finish ; // HTTPレスポンスコードを代入する変数 static int HTTP_ResponseCode ; // 例外発生時のメッセージを代入する変数 static String HTTP_ExceptionMessage ; // 通信結果のデータを格納するバッファ関連の変数 static boolean HTTP_DataBuffer_Initialize = false ; static byte[] HTTP_DataBuffer ; static int HTTP_DataBufferBytes = 8 * 1024 * 1024 ; // 通信結果を代入するバッファのサイズは 8MB( 足りない場合は増やしてください ) static int HTTP_DataBufferEnableBytes ; // 非同期で HTTP 通信するためのクラス public class HTTP_ConnectTask extends Thread { public void run() { HttpURLConnection HTTPCon = null ; URL url = null ; try { // URLの作成 url = new URL( HTTP_URLString ) ; // 接続用HttpURLConnectionオブジェクト作成 HTTPCon = ( HttpURLConnection )url.openConnection() ; // メソッドタイプによって処理を分岐 switch( HTTP_MethodType ) { case 0: // GET HTTPCon.setRequestMethod( "GET" ) ; HTTPCon.setDoOutput( false ) ; break ; case 1: // POST HTTPCon.setRequestMethod( "POST" ) ; HTTPCon.setDoOutput( true ) ; break ; case 2: // PUT HTTPCon.setRequestMethod( "PUT" ) ; HTTPCon.setDoOutput( true ) ; break ; } HTTPCon.setDoInput( true ) ; // 接続 HTTPCon.connect() ; // レスポンスコードの取得 HTTP_ResponseCode = HTTPCon.getResponseCode(); // 通信結果のデータを格納する配列がまだ未初期化の場合はここで初期化 if( HTTP_DataBuffer_Initialize == false ) { HTTP_DataBuffer_Initialize = true; HTTP_DataBuffer = new byte[ HTTP_DataBufferBytes ] ; } // 有効なデータのサイズを0にセット HTTP_DataBufferEnableBytes = 0 ; // 通信結果のデータを取得 InputStream in = HTTPCon.getInputStream() ; int ReadSize = in.read( HTTP_DataBuffer ) ; while( ReadSize > 0 ) { HTTP_DataBufferEnableBytes += ReadSize ; ReadSize = in.read( HTTP_DataBuffer, HTTP_DataBufferEnableBytes, HTTP_DataBufferBytes - HTTP_DataBufferEnableBytes ) ; } in.close() ; } catch ( MalformedURLException e ) { // MalformedURL例外が発生したらレスポンスコードを代入する変数に -3 を代入 HTTP_ResponseCode = -3 ; // 例外のメッセージを取得する HTTP_ExceptionMessage = e.toString() ; } catch ( IOException e ) { // IO例外が発生したらレスポンスコードを代入する変数に -2 を代入 HTTP_ResponseCode = -2 ; // 例外のメッセージを取得する HTTP_ExceptionMessage = e.toString() ; } // HTTP通信が完了したので 1 を代入する HTTP_Connect_Finish = 1 ; } } // HTTP通信を行う Java の関数 public void HTTP_Connect( String URLString, int Method ) { // HTTP通信の完了を確認するための変数を初期化 HTTP_Connect_Finish = 0 ; // 有効な取得データのサイズを初期化 HTTP_DataBufferEnableBytes = 0 ; // レスポンスコードを初期化 HTTP_ResponseCode = -1 ; // 非同期処理を行うクラスが参照できるようにグローバル変数に引数を代入 HTTP_URLString = URLString ; HTTP_MethodType = Method ; // HTTP通信を非同期で行う為のクラスを作成・実行 HTTP_ConnectTask HTTPTask = new HTTP_ConnectTask() ; HTTPTask.start() ; } }
-->  ( (プロジェクト名)となっている箇所は、お手元のプロジェクトの名前を入力してください )



4.Java の情報を取得する C++ のコードを入力する

  次に Java の HTTP通信の処理を開始する関数を呼び出したり、HTTP通信の結果を取得したり通信結果を画面に表示したりする C++ 側のプログラムです。
  『DXライブラリ Android版』でのみ使用できる GetNativeActivity という関数を使用しています。
#include "DxLib.h" #include <string.h> // データ取得用バッファ 8MB( 足りない場合は増やしてください ) BYTE HTTP_DataBuffer[ 8 * 1024 * 1024 ] ; // HTTP通信を行う // URL : 接続するパス // Method : "GET", "POST", "PUT" の何れか // 戻り値 : 0:成功 -1:エラー int Android_HTTP_Connect( const char *URL, const char *Method ) { JNIEnv *env ; const ANativeActivity *NativeActivity ; jstring jstring_URL ; int MethodType = 0 ; // アプリの NativeActivity を取得しておく NativeActivity = GetNativeActivity() ; // JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 ) if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK ) { return -1 ; } // URL から jstring を作成 jstring_URL = env->NewStringUTF( URL ) ; // メソッド名からメソッドタイプの数値をセット if( strcmpDx( "GET", Method ) == 0 ) { MethodType = 0 ; } else if( strcmpDx( "POST", Method ) == 0 ) { MethodType = 1 ; } else if( strcmpDx( "PUT", Method ) == 0 ) { MethodType = 2 ; } // Java のクラス (プロジェクト名) を取得 jclass jclass_(プロジェクト名) = env->GetObjectClass( NativeActivity->clazz ) ; // Java のクラス (プロジェクト名) のメンバー関数 HTTP_Connect の ID を取得 jmethodID jmethodID_HTTP_Connect = env->GetMethodID( jclass_(プロジェクト名), "HTTP_Connect", "(Ljava/lang/String;I)V" ) ; // Java のクラス (プロジェクト名) のメンバー関数 HTTP_Connect の呼び出し env->CallVoidMethod( NativeActivity->clazz, jmethodID_HTTP_Connect, jstring_URL, MethodType ) ; // Java の参照を削除 env->DeleteLocalRef( jclass_(プロジェクト名) ) ; env->DeleteLocalRef( jstring_URL ) ; // JavaVM とソフト実行用スレッドの関連付け終了 NativeActivity->vm->DetachCurrentThread() ; // 正常終了 return 0 ; } // HTTP通信の結果を取得する // 戻り値 : 通信の結果( 0:まだ通信中 1以上:HTTPレスポンスコード -2:IO例外発生 -3:MalformedURL例外発生 ) int Android_HTTP_GetResponseCode( void ) { JNIEnv *env ; const ANativeActivity *NativeActivity ; int Result = -1 ; // アプリの NativeActivity を取得しておく NativeActivity = GetNativeActivity() ; // JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 ) if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK ) { return -1 ; } // Java のクラス (プロジェクト名) を取得 jclass jclass_(プロジェクト名) = env->GetObjectClass( NativeActivity->clazz ) ; // Java のクラス (プロジェクト名) のメンバー変数の ID を取得 jfieldID jfieldID_HTTP_ResponseCode = env->GetStaticFieldID( jclass_(プロジェクト名), "HTTP_ResponseCode", "I" ) ; jfieldID jfieldID_HTTP_Connect_Finish = env->GetStaticFieldID( jclass_(プロジェクト名), "HTTP_Connect_Finish", "I" ) ; // Java のクラス (プロジェクト名) のメンバー変数の値を取得 jint jint_HTTP_ResponseCode = env->GetStaticIntField( jclass_(プロジェクト名), jfieldID_HTTP_ResponseCode ) ; jint jint_HTTP_Connect_Finish = env->GetStaticIntField( jclass_(プロジェクト名), jfieldID_HTTP_Connect_Finish ) ; // まだ通信処理が終わっていなかったら 0 を返す if( jint_HTTP_Connect_Finish == 0 ) { Result = 0 ; } else { // 終わっていたらレスポンスコードを返す Result = jint_HTTP_ResponseCode ; } // JavaVM とソフト実行用スレッドの関連付け終了 NativeActivity->vm->DetachCurrentThread() ; // 戻り値を返す return Result ; } // HTTP通信の例外メッセージを所得する // 戻り値 : 例外のメッセージ文字列のポインタ char *Android_HTTP_GetExceptionMessage( void ) { JNIEnv *env ; const ANativeActivity *NativeActivity ; // アプリの NativeActivity を取得しておく NativeActivity = GetNativeActivity() ; // JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 ) if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK ) { return NULL ; } // Java のクラス DxLibTest_Android_VS2017 を取得 jclass jclass_DxLibTest_Android_VS2017 = env->GetObjectClass( NativeActivity->clazz ) ; // Java のクラス DxLibTest_Android_VS2017 のメンバー変数の ID を取得 jfieldID jfieldID_HTTP_ExceptionMessage = env->GetStaticFieldID( jclass_DxLibTest_Android_VS2017, "HTTP_ExceptionMessage", "Ljava/lang/String;" ) ; // Java のクラス DxLibTest_Android_VS2017 のメンバー変数の値を取得 jstring jstring_HTTP_ExceptionMessage = ( jstring )env->GetStaticObjectField( jclass_DxLibTest_Android_VS2017, jfieldID_HTTP_ExceptionMessage ) ; // jstring の文字列をグローバル変数にコピー const char *utf8_HTTP_ExceptionMessage = env->GetStringUTFChars( jstring_HTTP_ExceptionMessage, NULL ) ; if( utf8_HTTP_ExceptionMessage != NULL ) { strcpyDx( HTTP_ExceptionMessage, utf8_HTTP_ExceptionMessage ) ; } else { HTTP_ExceptionMessage[ 0 ] = '\0' ; } env->ReleaseStringUTFChars( jstring_HTTP_ExceptionMessage, utf8_HTTP_ExceptionMessage ) ; // JavaVM とソフト実行用スレッドの関連付け終了 NativeActivity->vm->DetachCurrentThread() ; // メッセージ文字列を返す return HTTP_ExceptionMessage ; } // HTTP通信の結果得られたデータのバッファを取得する // BufferBytes : バッファに格納されているデータのサイズを保存する変数のアドレス // 戻り値 : データのバッファの先頭アドレス void *Android_HTTP_GetBuffer( size_t *BufferBytes ) { JNIEnv *env ; const ANativeActivity *NativeActivity ; void *Result = NULL ; // アプリの NativeActivity を取得しておく NativeActivity = GetNativeActivity() ; // JavaVM とソフト実行用スレッドを関連付け( C++ から Java の機能を使用するために必要 ) if( NativeActivity->vm->AttachCurrentThreadAsDaemon( &env, NULL ) != JNI_OK ) { return NULL ; } // Java のクラス (プロジェクト名) を取得 jclass jclass_(プロジェクト名) = env->GetObjectClass( NativeActivity->clazz ) ; // Java のクラス (プロジェクト名) のメンバー変数の ID を取得 jfieldID jfieldID_HTTP_DataBuffer = env->GetStaticFieldID( jclass_(プロジェクト名), "HTTP_DataBuffer", "[B" ) ; jfieldID jfieldID_HTTP_DataBufferEnableBytes = env->GetStaticFieldID( jclass_(プロジェクト名), "HTTP_DataBufferEnableBytes", "I" ) ; // Java のクラス (プロジェクト名) のメンバー変数の内容を取得 jint jint_HTTP_DataBufferEnableBytes = env->GetStaticIntField( jclass_(プロジェクト名), jfieldID_HTTP_DataBufferEnableBytes ) ; // 有効なデータが 1byte でもあれば、バッファの内容を取得する if( jint_HTTP_DataBufferEnableBytes > 0 ) { // Java のクラス (プロジェクト名) のメンバー変数 HTTP_DataBuffer の内容を取得 jbyteArray jbyteArray_HTTP_DataBuffer = ( jbyteArray )env->GetStaticObjectField( jclass_(プロジェクト名), jfieldID_HTTP_DataBuffer ) ; jbyte *jbyte_Elements = env->GetByteArrayElements( jbyteArray_HTTP_DataBuffer, NULL ) ; memcpy( HTTP_DataBuffer, jbyte_Elements, jint_HTTP_DataBufferEnableBytes ) ; env->ReleaseByteArrayElements( jbyteArray_HTTP_DataBuffer, jbyte_Elements, 0 ) ; env->DeleteLocalRef( jbyteArray_HTTP_DataBuffer ) ; Result = HTTP_DataBuffer ; } // バッファの有効なデータのサイズを保存 *BufferBytes = jint_HTTP_DataBufferEnableBytes ; // JavaVM とソフト実行用スレッドの関連付け終了 NativeActivity->vm->DetachCurrentThread() ; // バッファのアドレスを返す return Result ; } int android_main( void ) { // DXライブラリの初期化 if( DxLib_Init() < 0 ) return -1 ; // 描画先を裏画面にする SetDrawScreen( DX_SCREEN_BACK ) ; // HTTP通信開始 Android_HTTP_Connect( "https://dxlib.xsrv.jp/index.html", "GET" ) ; // メインループ while( ProcessMessage() == 0 ) { // 画面のクリア ClearDrawScreen() ; // HTTP通信の結果によって処理を分岐 int ResponseCode = Android_HTTP_GetResponseCode() ; switch( ResponseCode ) { case 0 : // 通信が終了していない場合は『通信中』を表示する DrawString( 0, 0, "通信中", GetColor( 255,255,255 ) ) ; break ; case -2 : // IO例外が発生した場合はメッセージを表示する DrawFormatString( 0, 0, GetColor( 255,255,255 ), "IO例外発生 : %s", Android_HTTP_GetExceptionMessage() ) ; break ; case -3 : // MalformedURL例外が発生した場合はメッセージを表示する DrawFormatString( 0, 0, GetColor( 255,255,255 ), "MalformedURL例外発生 : %s", Android_HTTP_GetExceptionMessage() ) ; break ; default : // それ以外の場合は HTTPレスポンスコード // データのサイズとアドレスを取得 size_t DataBytes ; void *Buffer = Android_HTTP_GetBuffer( &DataBytes ) ; DrawFormatString( 0, 16 * 0, GetColor( 255,255,255 ), "HTTPレスポンスコード : %d", ResponseCode ) ; DrawFormatString( 0, 16 * 1, GetColor( 255,255,255 ), "データのサイズ : %d bytes", ( int )DataBytes ) ; DrawFormatString( 0, 16 * 2, GetColor( 255,255,255 ), "データ : %s", Buffer != NULL ? ( char * )Buffer : "None" ) ; break ; } // 裏画面の内容を表画面に反映 ScreenFlip() ; } // DXライブラリの後始末 DxLib_End() ; // ソフトの終了 return 0 ; }
 ( (プロジェクト名)となっている箇所は、お手元のプロジェクトの名前を入力してください )

  以上です。
  これでプロジェクトを実行すると、『DXライブラリ置き場』のトップページを HTTP通信でダウンロードする処理が実行され、
  画面に HTML のテキストが表示されます。( 通信が成功すれば、ですが… )

  詳細は割愛しますが、C++ から Java にアクセスする基本的な流れは。

   ① AttachCurrentThreadAsDaemon でソフト実行用スレッドと JavaVM を関連付け、JNIEnv も取得。

   ② GetObjectClass でアプリの Java クラスを取得。

   ③ GetMethodID や GetFieldID で Java の関数や変数の ID を取得。( static な変数の場合は GetStaticFieldID )

   ④ Call???Method や Get???Field で Java の関数の呼び出しや変数の値を取得。

   ⑤ DeleteLocalRef で Get???Field で取得したオブジェクト系の変数の参照を削除。

   ⑥ DeleteLocalRef で GetObjectClass で取得したアプリの Java クラスの参照を削除。

   ⑦ DetachCurrentThread でソフト実行用スレッドと JavaVM の関連付けを終了。

  となります。
  C++ と Java でのやり取りを行う機能( JNI( Java Native Interface ) ) の詳しい扱い方については、検索サイトで
 『Android Java JNI』などのキーワードで検索して調べてみてください。





戻る