このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com ロゴ
作成 February 17, 2020
トップページ > Android 開発トップ > Java クラス関数の作成と呼び出し
line
Android 開発
line

ここでは、ネイティブアプリから Java 関数を呼び出す方法について、書いています。

C/C++ で書かれた Android ネイティブアプリに、Java のコード(クラス)を追加し、 その関数を呼び出して、クリップボードに文字列をコピーする方法について、書いています。

開発環境は Win10 Pro + Visual Studio Community 2019 です。

Visual Studio Community 2019 は、マイクロソフト・アカウントがあれば、個人での開発に無償で利用できるバージョンです。 Visual Studio Community 2019 のインストールについては、 いずれも旧バージョンとの共存インストールで、 Cドライブにすべてインストールする(標準的な) 「VS Community 2019 を共存インストールする」(ノート PC)と、 Cドライブの SSD に最小限にインストールし、あとはDドライブの HDD を指定している 「VS Community 2019 を共存インストールする(デスクトップ)」 に書いています。

ここでは、とりあえず、目的を「クリップボードに文字列をコピーする」としていますが、 主題はネイティブアプリから JNI で Java クラスに定義した関数を呼び出す基礎となる手順について、です。

ここでは、Visual C++ 2019 で作成した Android ネイティブのコードから、 Java の関数を呼び出す手順について、まとめて書いています。

このページ、および開発関連ページは、主に PC 向けデザインとなっております。 画面サイズの小さいスマホでは、快適な表示が得られませんので、ご了承ください。

ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。

▼ セクション一覧

やりたかったこと
Java コードの準備
Java 関数の呼び出し手順
非 static 関数を呼び出す場合
メモランダム


やりたかったこと

投稿 February 17, 2020

まず、目的としては、決まった長い文字列(メールアドレス等)をしょっちゅう入力するのが面倒なので、 自分のアプリでクリップボードに好きなテキストを設定できるものができたらいいな、ということでした。

本当は、そういう目的ではネイティブアプリを作成する必要は全くありません。 ネイティブコードは必要ありませんので、Java ベースでプロジェクトを作成するほうが正解です。

ですが、勉強を兼ねて、Java 関数は呼び出さず、単純に次のような Java コードを FindClass 関数やら GetMethodID 関数やらを使って実行しようとしていました。 しかし、クリップボード・マネージャーを取得するための getSystemService 関数をうまく取得できず、 すべてネイティブで書くのをあきらめて Java 関数を呼び出すことにし、無事実現できました。

ClipboardManager clipboard = (ClipboardManager)app.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("simple text", "text to be stored");
clipboard.setPrimaryClip(clip);

ここでは、このコードを Java のクラスに定義し、C/C++ のネイティブコードから呼び出す手順をまとめ、書いています。

▲ページ先頭へ

Java コードの準備

投稿 February 17, 2020

まずは、Java のコードを用意します。

ソリューション エクスプローラーで、<プロジェクト名>.Packaging に、src フォルダを作成します。 .Packaging を右クリック、追加新規フォルダーと選択していきます。

src フォルダ内に、Java ソースファイルを作成します。 src を右クリックして、追加新しい項目と選択すると Java ファイルを作成できますので、例えば ClipBrd.java を作成します。

Packaging に Java コードを追加します。

すると、
package com.mypackage;
で始まり、ファイル名がクラス名になっている Java ファイルが生成されます。

package com.mypackage;

class ClipBrd
{
}

パッケージ名 package は、 AndroidManifest.xml の manifest 要素の package 属性で指定される値と一致するように書き換えます。 AndroidManifest.xml で
package="com.straightapps.$(ApplicationName)"
と記述するなら、Java ファイル内では、
package com.straightapps.testAny;
のように書き換えます。 ここで、testAny と書かれている部分は $(ApplicationName) に相当するもので、プロジェクト名と同一です。

とりあえず、 ClipBrd クラスの定義 ( class ClipBrd の次にある中カッコの間です。 ) に、以下のようにコンストラクタと文字列のコピー関数を定義します。

public ClipBrd()
{
}
public static void copyToClipboard(NativeActivity app, String text)
{
	ClipboardManager clipboard = (ClipboardManager)app.getSystemService(Context.CLIPBOARD_SERVICE);
	ClipData clip = ClipData.newPlainText("simple text", text);
	clipboard.setPrimaryClip(clip);
}

すると、NativeActivity、ClipboardManager、Context、ClipData に赤い波線が付きます。 つまり、型の定義がないのでエラーになる、ということです。

package のあと、class Blipbord の前に、以下を追加することで、参照できるようになります。

import android.app.NativeActivity;
import android.content.Context;
import android.content.ClipboardManager;
import android.content.ClipData;

これでエラーはなくなりますので、Java コードの準備は完了です。

AndroidManifest.xml を開き、application 要素の android:hasCode="false" を、 android:hasCode="true" に書き換えておかないと、Java コードは認識されません。 忘れずに書き換えます。

▲ページ先頭へ

Java 関数の呼び出し手順

投稿 February 17, 2020

まずは、呼び出すタイミングを決めなくてはいけません。 今は簡単に、画面のどこかがタッチされたら、としますので、 main.cpp にある engine_handle_input 関数の、
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION){
}

の中にコードを置くこととします。

最初に、 JNI ( Java Native Interface。Java と他言語を連携させるための仕組み。 ) を使うときにいつも行う、 JavaVM ( Java Virtual Machine。仮想的な Java 端末。 ) の用意を行います。 app は engine_handle_input 関数の引数で、struct android_app* です。

JNIEnv* env;
JavaVM* vm = app->activity->vm;
vm->AttachCurrentThread(&env, NULL);

NativeActivity クラスを取得します。 app->activity->clazz は activity を意味していますが、名前付けにミスがあり、こうなってしまっている、ということです。 本当は activity で、このまま Context の代用として使えるようです。 ここで取得している activityClass は app->activity->clazz のクラス、すなわち NativeActivity です。 activityClass が 0 のとき、取得に失敗していますので、このあとでは利用できません。

// getSystemService 関数等を持つ NativeActivity クラスを取得します。
jclass activityClass = env->GetObjectClass(app->activity->clazz);
if (activityClass == 0) {
	// activityClass を取得できませんでした。
}

activityClass は、次のように取得しても同じように動作するようです。

jclass activityClass = env->FindClass("android/app/NativeActivity");

ClassLoader クラスに loadClass 関数 が定義されているので、まずはクラスを取得し、続けて loadClass 関数を取得します。

// ClassLoader クラスを取得
jclass classLoader = env->FindClass("java/lang/ClassLoader");
if (classLoader == 0) {
	// ClassLoader クラスを取得できませんでした。
}
// ClassLoader の loadClass 関数を取得
jmethodID loadClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
if (loadClass == 0) {
	// loadClass 関数を取得できませんでした。
}

ClassLoader インスタンスを取得するため、 NativeActivity クラスにある getClassLoader 関数を取得します。 引数はなく、戻り値は java.lang.ClassLoader 型のオブジェクトです。

// NativeActivity クラスの getClassLoader 関数を取得します。
jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
if (getClassLoader == 0) {
	// getClassLoader 関数を取得できませんでした。
}

取得した getClassLoader 関数を呼び出し、戻り値として ClassLoader インスタンスを取得します。

// getClassLoader 関数を呼び出し、ClassLoader のインスタンスを取得します。
jobject classLoaderInstance = env->CallObjectMethod(app->activity->clazz, getClassLoader);
if (classLoaderInstance == 0) {
	// classLoaderInstance 関数を取得できませんでした。
}

いよいよ ClassLoader インスタンスの loadClass 関数を呼び出して、クラスをロードします。 読み込むクラス名は、.java ファイルの先頭の package で指定したものと、定義したクラス名をつなげたものです。

// com.straightapps.testAny パッケージの ClipBrd クラスを取得します。
jstring strClassName = env->NewStringUTF("com/straightapps/testAny/ClipBrd");
jclass classClipBrd = (jclass)env->CallObjectMethod(classLoaderInstance, loadClass, strClassName);
if (classClipBrd == 0) {
	// classClipBrd クラスを取得できませんでした。
}

ClipBrd クラスに static で定義した copyToClipboard 関数を呼び出します。 static で定義されているため、GetStaticMethodID 関数で copyToClipboard 関数を取得し、 CallStaticCoidMethod 関数で呼び出します。 static 関数の呼び出しですので、インスタンス化する必要はありません。

// クリップボードに文字列をコピーします。
jmethodID copyToClipboardMethod = env->GetStaticMethodID(classClipBrd, "copyToClipboard", "(Landroid/app/NativeActivity;Ljava/lang/String;)V");
jstring textString;
textString = env->NewStringUTF("text to be copied");
env->CallStaticVoidMethod(classClipBrd, copyToClipboardMethod, app->activity->clazz, textString);

JavaVM をデタッチして、終了です。

// カレントスレッドからデタッチしておきます。
vm->DetachCurrentThread();
▲ページ先頭へ

非 static 関数を呼び出す場合

投稿 February 17, 2020

static で定義された関数は、クラスの定義だけ取得できれば、呼び出すことができます。

jclass classX = env->FindClass("java/lang/classX");
jmethodID jmID = env->GetStaticMethodID(classX, "methodX", "()Z");
jboolean jb = env->CallStaticBooleanMethod(classX, jmID);

のように、呼び出しやすいです。

static でない関数を呼び出すには、インスタンスを作成する必要があります。

コンストラクタは次のように取得します。 引数なし、戻り値なしの、ただ作成するだけのコンストラクタです。

jmethodID classClipBrdInitMethod = env->GetMethodID(classClipBrd, "<init>", "()V");
if (classClipBrdInitMethod == 0) {
	// classClipBrd クラスのコンストラクタを取得できませんでした。
}

取得したコンストラクタを呼び出すと、classClipBrd が表すクラスのインスタンス(オブジェクト) objClipBrd を得られます。

jobject objClipBrd = env->NewObject(classClipBrd, classClipBrdInitMethod);
if (objClipBrd == 0) {
	// objClipBrd クラスのインスタンスを作成できませんでした。
}
▲ページ先頭へ

メモランダム

投稿 February 17, 2020

同じように、画面のタッチを受けて、Toast クラスを試してみましたが、アプリが動作を停止してしまい、うまくいきませんでした。

Context context = app.getApplicationContext();
Toast toast = Toast.makeText(context, "my message", Toast.LENGTH_LONG);
toast.show();

2行目の makeText のところでうまくいきません。 static でも非 static でも、うまくいっていません。

また、TextView を使って文字を描画できないか試しましたが、動作しませんでした。 タッチを受けて、その場で呼んでいるからかも知れません。

TextView  tv = new TextView(app.getApplicationContext());
tv.setText("Hello, World!");
tv.setTextSize(64.0f); // pixel
app.setContentView(tv);
▲ページ先頭へ
line
関連トピックス
line

JNI によるパブリックなパスの取得

JNI を利用して、Download などのパスを取得する方法を検討しています。

C/C++ による現在日時の取得

C/C++ で現在時刻を取得する方法を検討しています。

line
その他のおすすめ
line

クリップボードにテキストや画像を入れる

Windows プログラムで、クリップボードに文字列や画像をコピーする方法について、書いています。

JavaScriptが無効です
▲ページ先頭へ


© 2017-2020 StraightApps.com 無断転載を禁じます。No reproduction without permission.