首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android NDK 開發:從 C/C++ 呼叫 Java 類別完整實戰指南

     編輯:Java 相關知識

場景與目標

在 Android NDK 開發中,常需從 Native 代碼 (C/C++) 呼叫 Java 層的類別與方法,例如利用 Java 的 Canvas 繪製文字生成 Bitmap,再傳回 OpenGL 紋理。本文完整記錄從 Java 類別設計、JNI 簽名獲取、到 Native 端實例化、呼叫方法、處理回傳值的全流程。

Java 端:TextManager 文字繪製工具類

package com.clouddevelop.cloudbox;

import android.graphics.*;

public class TextManager {
    public Bitmap create(String text, float size) {
        try {
            Paint paint = new Paint();
            paint.setColor(Color.WHITE);
            paint.setTextSize(size);
            paint.setAlpha(255);
            paint.setFlags(Paint.ANTI_ALIAS_FLAG);
            paint.setTextAlign(Paint.Align.LEFT);
            paint.setAntiAlias(true);
            
            float[] widths = new float[text.length()];
            paint.getTextWidths(text, widths);
            float width = 0;
            for (float w : widths) width += w;
            
            FontMetrics fm = paint.getFontMetrics();
            int height = (int) (Math.ceil(fm.descent - fm.top) + 2);
            
            Bitmap bitmap = Bitmap.createBitmap((int)width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            float y = (fm.ascent < 0) ? Math.abs(fm.ascent) : -fm.ascent;
            canvas.drawText(text, 0, y, paint);
            return bitmap;
        } catch (Exception e) {
            return null;
        }
    }
    
    public int getWidth(Bitmap bmp) { return bmp.getWidth(); }
    public int getHeight(Bitmap bmp) { return bmp.getHeight(); }
    
    public void getPixels(Bitmap bmp, int[] pixels) {
        bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
    }
    
    public void close(Bitmap bmp) { bmp.recycle(); }
}

關鍵步驟:獲取 JNI 方法簽名

使用 javap -s 指令輸出類別的方法簽名:

$ javap -s com.clouddevelop.cloudbox.TextManager
Compiled from "TextManager.java"
public class com.clouddevelop.cloudbox.TextManager {
  public com.clouddevelop.cloudbox.TextManager();
    descriptor: ()V
  public android.graphics.Bitmap create(java.lang.String, float);
    descriptor: (Ljava/lang/String;F)Landroid/graphics/Bitmap;
  public int getWidth(android.graphics.Bitmap);
    descriptor: (Landroid/graphics/Bitmap;)I
  public int getHeight(android.graphics.Bitmap);
    descriptor: (Landroid/graphics/Bitmap;)I
  public void getPixels(android.graphics.Bitmap, int[]);
    descriptor: (Landroid/graphics/Bitmap;[I)V
  public void close(android.graphics.Bitmap);
    descriptor: (Landroid/graphics/Bitmap;)V
}

簽名規則:()V 無參無回傳、(Ljava/lang/String;F)Landroid/graphics/Bitmap; 接受 String、float 回傳 Bitmap、[I 代表 int 陣列。

Native 端:JNI 實作細節

1. 全域變數快取 JNIEnv 與 jobject

JNIEnv* g_env = nullptr;
jobject g_textmgr = nullptr;

JNIEXPORT void JNICALL 
Java_com_clouddevelop_cloudbox_CloudRenderer_nativeTextInit(JNIEnv* env, jclass cls, jobject textManager) {
    g_env = env;
    g_textmgr = env->NewGlobalRef(textManager); // 必須升級為全域引用,防止 GC 回收
}

2. 在 Native 建立 Java 物件實例

jobject createJavaInstance(JNIEnv* env, jclass clazz) {
    jmethodID ctor = env->GetMethodID(clazz, "", "()V");
    return env->NewObject(clazz, ctor);
}

3. 完整流程:建立文字紋理

GLuint createText(const char* text, float size, float* rWidth, float* rHeight) {
    // 1. 尋找類別
    jclass clazz = g_env->FindClass("com/clouddevelop/cloudbox/TextManager");
    
    // 2. 建立實例 (或使用傳入的 g_textmgr)
    jobject textMgr = createJavaInstance(g_env, clazz);
    
    // 3. 獲取 create 方法 ID
    jmethodID midCreate = g_env->GetMethodID(clazz, "create", 
        "(Ljava/lang/String;F)Landroid/graphics/Bitmap;");
    
    // 4. 準備參數並呼叫
    jstring jText = g_env->NewStringUTF(text);
    jobject bitmap = g_env->CallObjectMethod(textMgr, midCreate, jText, size);
    g_env->DeleteLocalRef(jText);
    
    // 5. 獲取寬高
    jmethodID midW = g_env->GetMethodID(clazz, "getWidth", "(Landroid/graphics/Bitmap;)I");
    jmethodID midH = g_env->GetMethodID(clazz, "getHeight", "(Landroid/graphics/Bitmap;)I");
    int width = g_env->CallIntMethod(textMgr, midW, bitmap);
    int height = g_env->CallIntMethod(textMgr, midH, bitmap);
    *rWidth = width; *rHeight = height;
    
    // 6. 取得像素資料
    jintArray pixelsArray = g_env->NewIntArray(width * height);
    jmethodID midPixels = g_env->GetMethodID(clazz, "getPixels", "(Landroid/graphics/Bitmap;[I)V");
    g_env->CallVoidMethod(textMgr, midPixels, bitmap, pixelsArray);
    jint* pixels = g_env->GetIntArrayElements(pixelsArray, 0);
    
    // 7. 建立 OpenGL 紋理
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
    // 8. 清理資源
    g_env->ReleaseIntArrayElements(pixelsArray, pixels, 0);
    g_env->DeleteLocalRef(pixelsArray);
    
    jmethodID midClose = g_env->GetMethodID(clazz, "close", "(Landroid/graphics/Bitmap;)V");
    g_env->CallVoidMethod(textMgr, midClose, bitmap);
    g_env->DeleteLocalRef(bitmap);
    g_env->DeleteLocalRef(textMgr);
    
    return texture;
}

關鍵注意事項

  • 引用管理:NewGlobalRef / DeleteGlobalRef 配對使用,避免記憶體洩漏或過早回收。
  • 執行緒附著:若在非 Java 執行緒呼叫 JNI,需先 AttachCurrentThread 獲取有效 JNIEnv*。
  • 異常檢查:每次 JNI 呼叫後應檢查 ExceptionCheck(),防止 Java 異常傳播導致 Native 崩潰。
  • 效能優化:頻繁呼叫的方法 ID 應快取;大量像素傳輸考慮 GetPrimitiveArrayCritical (需謹慎使用)。

總結

NDK 呼叫 Java 類別的核心流程為:FindClass → GetMethodID → NewObject/CallMethod → 處理回傳值 → 釋放引用。掌握 javap -s 獲取簽名、區分區域/全域引用、正確管理 JNI 環境指標,是穩健開發混合應用的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved