顯示具有 jni 標籤的文章。 顯示所有文章
顯示具有 jni 標籤的文章。 顯示所有文章

2021年2月19日 星期五

hello_jni

環境: android_studio 4.1.2

Tree結構:
project_name(testJni)/app/src/main
$ tree
.
├── AndroidManifest.xml
├── java
│   └── com
│       └── example
│           └── testjni
│               ├── FirstFragment.java
│               ├── MainActivity.java
│               ├── SecondFragment.java
│               └── TestJni.java
├── jni (必要,自行產生)
│   ├── Android.mk
│   ├── Application.mk
│   ├── hello_jni.c
│   └── hello_jni.h
├── jniLibs (必要, 從 libs拷貝資料夾過來)
│   ├── x86
│   │   └── libhello_jni.so
│   └── x86_64
│       └── libhello_jni.so
├── libs (ndk-build產生)
│   ├── arm64-v8a
│   │   └── libhello_jni.so
│   ├── x86
│   │   ├── gdbserver
│   │   ├── gdb.setup
│   │   └── libhello_jni.so
│   └── x86_64
│       ├── gdbserver
│       ├── gdb.setup
│       └── libhello_jni.so
...
...
...
...


project_name(testJni)/
$ tree
├── gradle.properties
├── local.properties


local.properties
sdk.dir=/opt/android_studio/sdk
ndk.dir=/opt/android_studio/sdk/ndk/20.1.5948944/ (官方建議寫在 build.gradle)


build.gradle
android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'
    ...
    ...
    ndkVersion "20.1.5948944"
}


app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testjni"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="26" />


app/src/main/jni/hello_jni.c

#include <jni.h>
#include <stdio.h>
#include <android/log.h>
#include "hello_jni.h"

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "TAG", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "TAG", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "TAG", __VA_ARGS__);
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "TAG", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "TAG", __VA_ARGS__);

//                        Java_pacakgeName_className_functionName
JNIEXPORT jstring JNICALL Java_com_example_testjni_TestJni_getString(JNIEnv * env, jobject obj, jstring s)
{
    char * st = (char *) (*env)->GetStringUTFChars(env, s, 0);
    LOGI("\n c-string:  %s", st);

    char * str = "hello_world";
    jstring rtn;
    rtn = (*env)->NewStringUTF(env, str);
    return rtn;
}


app/src/main/jni/hello_jni.h
#ifndef TESTJNI_HELLO_JNI_H
#define TESTJNI_HELLO_JNI_H
JNIEXPORT jstring JNICALL Java_com_example_testjni_TestJni_getString(JNIEnv * env, jobject obj, jstring s);
#endif //TESTJNI_HELLO_JNI_H


app/src/main/jni/Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := hello_jni

LOCAL_SRC_FILES := hello_jni.c

LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog

include $(BUILD_SHARED_LIBRARY)



app/src/main/jni/Application.mk.mk
APP_ABI := x86 (x86電腦的emulator用。本來用x86_64,但編譯時說找不到@@)
APP_PLATFORM := android-16
APP_STL := c++_shared
app_abi

Compiler:
$ cd path/testJni/app/src/main/jni
$ ndk-build

在/usr/local/bin產生個ndk-build的script
#!/bin/sh
#DIR="$(cd "$(dirname "$0")" && pwd)"
DIR="/opt/android_studio/sdk/ndk/20.1.5948944/"
$DIR/build/ndk-build "$@"


成功後,會產生libs和obj資料夾
將 libs/abi 資料夾拷貝到 jniLibs


app/src/main/TestJni.java
package com.example.testjni;

public class TestJni {
    public native String getString(String str);

    static {
        System.loadLibrary("hello_jni");
    }
}

app/src/main/MainActivity.java
package com.example.testjni;

import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.util.Log;
import android.view.View;

import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    TestJni tj = new TestJni();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        Log.i("test_hello", "Start jni");
        Log.i("test_nello", "jni="+tj.getString("hello_jni"));
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();

            }
        });
    }
...
...
...


這樣應該可以在emulator上執行了,開啟logcat應該看得到jni的輸出


bsp code (system/core/rootdir/etc/public.libraries.android.txt)
...
...
libstdc++.so 
libsync.so 
libvulkan.so 
libwebviewchromium_plat_support.so 
libz.so
libhello_jni.so 添加


ref:
內置應用so庫無法打開問題解決
AndroidStudio NDK開發【dlopen failed: library "libc++_shared.so" not found】報錯解決

安裝android_studio

環境: android_studio 4.1.2

  • jre 和 jdk (應該是用apt)
$ dpkg -l | grep -e jdk -e jre
ii  openjdk-8-jdk:amd64                        8u282-b08-0ubuntu1~18.04                         amd64        OpenJDK Development Kit (JDK)
ii  openjdk-8-jdk-headless:amd64               8u282-b08-0ubuntu1~18.04                         amd64        OpenJDK Development Kit (JDK) (headless)
ii  openjdk-8-jre:amd64                        8u282-b08-0ubuntu1~18.04                         amd64        OpenJDK Java runtime, using Hotspot JIT
ii  openjdk-8-jre-headless:amd64               8u282-b08-0ubuntu1~18.04                         amd64        OpenJDK Java runtime, using Hotspot JIT (headless)
  • 自已的/opt設定
4.1.2
jre -> /usr/lib/jvm/java-8-openjdk-amd64/
jre8 -> /usr/lib/jvm/java-8-openjdk-amd64/jre
sdk (裝android_studio時,會把相關需要的東西裝在這)
  •  自已的.bashrc設定
...
export PATH=/opt/java/jdk-11.0.8/bin:$PATH
export JAVA_HOME=/opt/java/jdk-11.0.8
export JRE_HOME=$JAVA_HOME
...
export GRAALVM_HOME=/opt/graalVM/graalvm-ce-java11-21.0.0



2021啟動jni環境設定碰到的問題

環境: android_studio 4.1.2

  • 問題: APP_PLATFORM android-16 is higher than android:minSdkVersion 1
在AndroidManifest.xml加上
<uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="26">
ref : Here

試試看用cmd line
1. $ cd /opt/android_studio/sdk/tools/emlator
2. $ ./emulator -list-avds
3. $ ./emulator Pixel_2_API_28 (應該會碰到下面的問題)
   or
   $ ./emulator -avd Pixel_2_API_28 -netspeed full -netdelay none (應該會碰到下面的問題)



  • 問題: PANIC: Missing emulator engine program for 'x86' CPU
預設是用 path/android_studio/sdk/tools/emulator
需要改路徑:
a. 將 path/android_studio/sdk/tools/emulator 備份, 此為 binary file
b. 將 emulator 改成 script,
#!/bin/sh
DIR="/opt/android_studio/sdk/emulator"
$DIR/emulator "$@"

2015年5月26日 星期二

Android Stdio JNI & JAR設定

Android Stdio version : 1.2.11

在eclipse可以跑的程式,移來Android Stdio卻發生點問題,
在compiler時,出現
Error:Execution failed for task ':app:compileDebugNdk'.
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command '/opt/android-ndk-r10b/ndk-build'' finished with non-zero exit value 2
花了些時間查尋才解決,
(先確定,*.c這些程式編譯沒問題)
在build.gradle加入
android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"
    之前查到資料,有人建議這兩個值版本設定一樣的,比較不會打牆
    defaultConfig {
        applicationId "com.main"
        minSdkVersion 19
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
        ndk {
            moduleName "serversocket"
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86" 
        }

        // Studio Version 2.1.2
        //第三方 *.so時。注意:同這個方法,然後package name要與*.so的相同@@
        //在 /data/app-libs/com.apkName-* 下(需root權限),會看到 *.so,成功的話
        sourceSets {
            main {
                jniLibs.srcDir(['libs'])
           }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    // for jar
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/LICENSE.txt'
    }

    sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.1'
    
    // for jar
    compile files('libs/filename.jar') 
}
在local.properties上記得加入NDK路徑
sdk.dir=/opt/android-sdk-linux
ndk.dir=/opt/android-ndk-r10b

ref : Sodino的专栏
ref: Stack Overflow

而當第三方的source code(在src/main/jni/下)包含Android.mk & Application.mk時:
(第三的source code裡有*.so的話,放到src/main/jniLibs/ABI下的就可以了,
ABI=armeabi, armeabi-v7a, x86, x86_64看*.so的種類)
環境:build.gradle - gradle:2.1.2
apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "23.0.1"

    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
        jniLibs.srcDir 'src/main/jniLibs'  //This is not necessary unless you have precompiled libraries in your project.
    }

    defaultConfig {
        applicationId "com.example.test"
        minSdkVersion 19
        targetSdkVersion 21

        ndk {
            moduleName "ndkTestSample"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        def ndkDir = android.ndkDirectory
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                '-j', Runtime.runtime.availableProcessors(),
                'all',
                'NDK_DEBUG=1'
    }

    task cleanNative(type: Exec, description: 'Clean JNI object files') {
        def ndkDir = android.ndkDirectory
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                'clean'
    }

    clean.dependsOn 'cleanNative'

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
    }

}

dependencies {
    compile 'com.google.code.gson:gson:2.1'
}
ref : stackoverflow, stackoverflow

Application.mk
APP_PROJECT_PATH := $(call my-dir)/..
APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/jni/Android.mk
#APP_OPTIM := debug
APP_OPTIM := release
APP_ABI := armeabi-v7a
APP_STL := gnustl_static

2015年5月12日 星期二

debug訊息 in logcat

*.c
#include <android/log.h>

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "TAG", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,    "TAG", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,    "TAG", __VA_ARGS__);
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,   "TAG", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,   "TAG", __VA_ARGS__);
Android.mk (for jni)
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
ref: jjunjoe的专栏

jni 數據轉換

比較麻煩的事,
string 傳進 jni裡要多一道手續,return時也一樣
【轉貼 黑夜的博客
用GetStringUTFChars問題多多,還是用Jstring2CStr這個function好 ref: Here
char *Jstring2CStr(JNIEnv *env, jstring jstr)
{
     char *rtn = NULL;
     jclass clsstring = (*env)->FindClass(env,"java/lang/String");
     jstring strencode = (*env)->NewStringUTF(env,"UTF8");
     jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");  
     jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);
     jsize alen = (*env)->GetArrayLength(env,barr);
     jbyte *ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
     if(alen > 0)  
     {
      rtn = (char*)malloc(alen+1); //"\0"  
      memcpy(rtn,ba,alen);  
      rtn[alen]=0;  
     }
     (*env)->ReleaseByteArrayElements(env,barr,ba,0);
     return rtn;
}


//获取,返回 string  
JNIEXPORT jstring JNICALL Java_com_test_Test_getString(JNIEnv * env,  
  jobject obj, jstring s) {  
 char * st = (char *) (*env)->GetStringUTFChars(env, s, 0);  
 printf("\n c-string:  %s", st);  
  
 char * str = "hello wang wu!";  
 jstring rtn;  
 rtn = (*env)->NewStringUTF(env, str);  
 return rtn;  
}

//获取,返回 string []  
JNIEXPORT jobjectArray JNICALL Java_com_test_Test_getStringArray(JNIEnv * env,  
  jobject obj, jobjectArray arr) {  
 int len = (*env)->GetArrayLength(env, arr);  
 printf("\n c-stringArray :");  
 int i = 0;  
 for (i = 0; i < len; i++) {  
  jobject obj = (*env)->GetObjectArrayElement(env, arr, i);  
  jstring str = (jstring) obj;  
  const char * szStr = (*env)->GetStringUTFChars(env, str, 0);  
  printf(" %d-%s ", i, szStr);  
  (*env)->ReleaseStringChars(env, str, szStr);  
 }  
 //-----返回----  
 jstring str;  
 jobjectArray args = 0;  
 jsize size = 5;  
 char* sa[] = { "Hello,", "world!", "zhang", "san", "yuang" };  
 int j = 0;  
 jclass objClass = (*env)->FindClass(env, "java/lang/String");  
 args = (*env)->NewObjectArray(env, size, objClass, 0);  
 for (j = 0; j < size; j++) {  
  str = (*env)->NewStringUTF(env, sa[j]);  
  (*env)->SetObjectArrayElement(env, args, j, str);  
 }  
 return args;  
}
其它的寫法一樣

另外,Android Jni中使用线程及回调更新UI 這個方法很重要

ref :
黑夜的博客
铁彬的博客

jni 碰到的問題

  1. 用javah產生 *.h 檔時:
    /opt/java/jdk1.7.0_67/bin/javah -classpath /opt/eclipse/android-sdk-linux/platforms/android-17/android.jar:../bin/classes  ManiActivity
    在開發app時需注意 android:minSdkVersion="19" 要符合上述的 level (android-17)
    這裡一個寫17,一個寫19,會有問題
  2. 碰到JNIEnv cannot be resolved,擅用 Build Project 可以解決 (專案滑鼠右鍵)
  3. 專案 > 滑鼠右鍵 > Properties > C/C++ General > Paths and Symbols > Include 下
    確定有 android:minSdkVersion 和 android:maxSdkVersion 有對應的ndk path
    /opt/android-ndk-r10b/platforms/android-17/arch-arm/usr/include
    /opt/android-ndk-r10b/platforms/android-19/arch-arm/usr/include
  4. error: parameter name omitted
    JNIEXPORT jint JNICALL Java_com_uart_NativeUART_sendinfo
      (JNIEnv *env, jobject obj, jint  i, jstring  str);
    
    從 *.h 拷貝到 *.c 時,參數要記得填上 ( c++ 好像可以不用填)
  5. Type 'JNICALL' could not be resolved
    專案目錄下 > vim  .project > 把 ccnature 整行拿掉
    <natures>
     <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
     <nature>org.eclipse.jdt.core.javanature</nature>
     <nature>org.eclipse.cdt.core.cnature</nature>
     <nature>org.eclipse.cdt.core.ccnature</nature>
     <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
     <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
     <nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>
    </natures>

ref:
/*nice*/