Monday, October 23, 2017

C語言指標與資料型態

&:取得變數的位址。
*:間接參考,指使用指標取得某個記憶體的內容

#include
//指標與指標資料型態 
int main()
{

 int x=100;
 int y=0;
 int *p;//定義一個可以存放整數變數指標 (位址) 的變數p
 p=&x;//x的記憶體位置設定給指標p 
 printf("P 的Memory address=%p,P值=%d\n",&p,&p);
 printf("X 的Memory address=%p,X值=%d\n",&x,x);
 system("pause");
 return 0;
}



error:
發現原本p address應該等於x address,code其實沒有錯,但*p也是需要一個address,是printf裡不需要&
#include
#include
//指標與指標資料型態 
int main()
{

 int x=100;
 int y=0;
 int *p;//定義一個可以存放整數變數指標 (位址) 的變數p
 p=&x;//x的記憶體位置設定給指標p 
 printf("P 的Memory address=%p,P值=%d\n",p,*p);
 printf("X 的Memory address=%p,X值=%d\n",&x,x);
 system("pause");
 return 0;
}
#include 
int main() {
    int x;
    int *p;
    p = 0; // 這讓p指到不合法的地方去了
    *p = 0; // 透過p去設定不合法的記憶體, 會產生嚴重錯誤
    x = *p; // 這也是錯的
}
在上面的例子中,*p=0的指令就足夠讓你的C程式一團混亂了。

Sunday, December 4, 2016

Android 使用NDK C++ 圖片轉灰階

經過前幾篇設定將不在贅述

創建GrayFun的class類別

GrayFun.java

package com.example.cheng.gray;

/**
* Created by Cheng on 2016/12/4.
*/

public class GrayFun {
static {
System.loadLibrary("GrayFun");
}

public static native int[] GrayFun(int[] buf, int w, int h);
}

Monday, November 28, 2016

Android 使用FFMPEG編出的.so檔 建立第一個專案

建立好NDK環境後

移植到Android平台

接下來寫在Android studio寫一個示例,調用ffmpeg中方法
建一個工程:在src/main下建一個jni目錄

將lib文件夾中的pkgconfig目錄刪除只保留so文件,然後將include和lib兩個目錄一起copy到你的 jni資料夾下去編譯,下圖因為我已經編譯完成會有其他檔案


建置好支援JNI的Android專案後,先別急著開始寫C/C++程式,應該要先規劃好Java程式中哪些地方需要使用到由C/C++程式語言實作的方法或是函數,這些方法都需要先使用native修飾子來進行宣告,並且使用System類別下的loadLibrary方法來讀入指定名稱的.so函式庫。用以下程式為例:

MainActivity.java


import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView infoText = (TextView)findViewById(R.id.text_libinfo);
infoText.setMovementMethod(ScrollingMovementMethod.getInstance());
Button button = (Button)this.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){

@Override
public void onClick(View view) {
infoText.setText(avcodecinfo());
}
});

}
//JNI
public native String avcodecinfo();
static{
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("avfilter-6");
System.loadLibrary("sffhelloworld");

}
}

.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.cheng.myapplication.MainActivity">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/textView" />

<TextView
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"
android:layout_alignParentStart="true"
android:layout_marginTop="122dp"
android:id="@+id/text_libinfo" />

<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"
android:layout_alignParentStart="true"
android:layout_marginTop="13dp"
android:id="@+id/button" />
</RelativeLayout>

接著再使用JDK提供的「javah」工具,來將指定類別轉換成JNI使用的標頭檔
MainActivity.java按右鍵 -> External Tools -> Javah
執行完畢後,在jni ([c])目錄中會出現 標頭檔.h
將使用javah產生出來的.h檔案移動到「jni」目錄下,然後在別的.c或是.cpp檔案中include,並實作出其中宣告的函數

simplest_ffmpeg_helloworld.c


//
// Created by cheng on 2016/11/29.
//
#include "com_example_cheng_myapplication_MainActivity.h"
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"

#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...) printf("(>_<) " format "\n", ##__VA_ARGS__)
#endif
JNIEXPORT jstring Java_com_example_cheng_myapplication_MainActivity_avcodecinfo(JNIEnv *env, jobject obj){

char info[40000]={0};
av_register_all();

AVCodec *c_temp = av_codec_next(NULL);

while (c_temp != NULL){
if(c_temp != NULL){
sprintf(info, "%s[Dec]\n", info);
}else{
sprintf(info, "%s[Enc]\n", info);
}
switch(c_temp->type){
case AVMEDIA_TYPE_VIDEO:
sprintf(info, "%s[Video]\n", info);
break;
case AVMEDIA_TYPE_AUDIO:
sprintf(info, "%s[Audio]\n", info);
break;
default:
sprintf(info, "%s[Other]\n", info);
break;
}
sprintf(info, "%s[%10s]\n", info,c_temp->name);
c_temp = c_temp->next;
}

return (*env).NewStringUTF(info);
}
Android.mk 


LOCAL_PATH := $(call my-dir)

# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-57.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-6.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-57.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-55.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-2.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-4.so
include $(PREBUILT_SHARED_LIBRARY)

# Program
include $(CLEAR_VARS)
LOCAL_MODULE := libsffhelloworld
LOCAL_SRC_FILES := simplest_ffmpeg_helloworld.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE = "xxxx"就是System.loadLibrary的名子

預設的情況下,NDK只會編譯出armeabi架構的.so檔案,可以用在任何ARM架構的Android裝置上。如果要針對ARMv7架構優化,或是使.so檔能在x86架構或是MIPS架構上執行的話,可以在「jni」目錄下,增加「Application.mk」檔案,並且設定「APP_ABI」參數的值。
例如要編譯出ARM與x86的.so檔案,可以這樣設定:
Application.mk
APP_ABI := armeabi
開始編譯目標so,在Studio中的Terminal面板中:執行ndk-build (在此之前記得要在local.properties下配置ndk目錄)


之後編譯有問題

建立build.gradle(app)
    defaultConfig {
applicationId "com.example.cheng.myapplication"
minSdkVersion 19
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk{
moduleName "libsffhelloworld"
}

sourceSets.main{
jni.srcDirs=[]
jniLibs.srcDir "src/main/jniLibs"
}
}
在gradle.properties
android.useDeprecatedNdk=true

在 libavutil\common.h 加入

#define __STDC_CONSTANT_MACROS
#define inline __inline

#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif

接這又出現錯誤= =

要再.h檔加入
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#endif


再編譯一次成功的話就燒入吧



Reference:

http://blog.csdn.net/hejjunlin/article/details/52661331
https://magiclen.org/android-jni/
http://jojosula001.pixnet.net/blog/post/173475381-%5Bwindows%5D-using-ffmpeg-in-visual-c%2B%2B
https://github.com/dxjia/ffmpeg-compile-shared-library-for-android
http://blog.csdn.net/leixiaohua1020/article/details/47008825

編譯 FFmpeg 並移植Android Studio

架設環境

  • Ubuntu16.04
  • FFMPEG 3.2.1
  • android-ndk-r13b 

FFmpeg是功能強大的多媒體編解碼庫,廣泛應用於各個平台的主流播放器、轉碼等軟件。在Android框架對視頻播放、編解碼的支持沒有那麼強大時,使用ffmpeg也是不二的選擇。本文介紹使用ndk編譯ffmpeg的過程。
在看了很多人寫的編譯方法,嘗試了很多方案後,發現只有下面這個方法能夠一次成功,其他都會出各種各樣的問題。



上圖中的流程可以分為“編譯FFmpeg類庫”、“編寫Java端代碼”、“編寫C語言端代碼”三個步驟。

  • 下載安裝NDK
    • 下載NDK之後直接解壓縮就可以使用了。在Windows下使用的時候需要用到Cygwin。在這裡我自己使用Linux編譯
  • 下載FFMPEG
    • 解壓縮後首先需要對源代碼中的configure文件進行修改。由於編譯出來的動態庫文件名的版本號在.so之後(例如“libavcodec.so.5.100.1”),而android平台不能識別這樣文件名,所以需要修改這種文件名。在configure文件中找到下面幾行代碼:

找到以下幾行:
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'


並修改為下列:
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

在ffmpeg目錄下添加編譯腳本build_android.sh,腳本中寫入以下代碼。腳本中主要是執行configure,然後執行make。
build_android.sh
#!/bin/bash
NDK=/home/cheng/Android/android-ndk-r13b
SYSROOT=$NDK/platforms/android-9/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one

大家記得修改前3個參數為自己電腦的環境
其中configure的參數加瞭如下一些:
prefix 指定了編譯結果的目錄;
enable 和disable 指定了需要編譯的項;
cross-prefix指定了交叉編譯的工具鏈中gcc文件;
target-os 不用說,是目標操作系統;
arch cpu類型;
sysroot androdlib目錄。
更多conigure參數可參考./configure --help。
然後修改build_android.sh文件的權限

sudo chmod +x build_android.sh

開始編譯喝杯咖啡等個10~20分鐘吧
有完成的話FFMPEG會生成android的資料夾



android/arm/lib 會有很多.so檔

編譯不出來的話這裡有編好的直接用吧


 Reference:
http://blog.csdn.net/leixiaohua1020/article/details/47008825
http://www.jianshu.com/p/313f378f9922
http://blog.csdn.net/hejjunlin/article/details/52661331
http://www.itread01.com/articles/1475294828.html


Wednesday, November 23, 2016

Android中使用JNI調用Opencv本地代碼配置方式邊緣檢測

在Android中使用Opencv有兩種方式,一種是使用opencv的Java版本的API,但是這種方式不是通過本地調用實現的,全部都是java代碼,所以這裡先不講,另外一種方式就是使用opencv的c++版本的API,將本地c++代碼編譯成.so鏈接庫,然後在安卓開發中進行調用,本地cpp代碼使用NDK進行編譯。

下面給出一個使用Canny算子檢測邊緣的本地代碼調用的使用方式。
新建Android Prject,這裡我的項目名稱為HaveImgFun

然後修改界面控製文件res->layout->activity_have_img_fun.xml

activity_have_img_fun.xml code

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="@+id/btnNDK"
android:text="使用C++ OpenCV进行处理" />
<Button android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="@+id/btnRestore"
android:text="还原" />

<TextView
android:text="TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView2" />

<ImageView android:id="@+id/ImageView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>


在文件夾src下的com.XXX.haveimgfun包中新建一個類用於包裝使用了opencv c++代碼的動態庫的導出函數,類名為LibImgFun。 Android Studio會為你創建一個新的文件LibImgFun.java,將裡面的內容改為:

package com.example.cheng.haveimgfun;

/**
* Created by Cheng on 2016/11/23.
*/

public class LibImgFun {
static {
System.loadLibrary("ImgFun");
}

public static native int[] ImgFun(int[] buf, int w, int h);
public native String getMycstring();
}
從上面的代碼可以得知,我們的動態庫名字應該為“libImgFun.so”。
注意"public static native int[] ImgFun(int[] buf, int w, int h)"中的native關鍵字,表明這個函數來自native code。static表示這是一個靜態函數,這樣就可以直接用類名去調用

建立C++代碼


在項目中新建一個jni文件,用於放置該項目的所有cpp代碼。
在jni文件夾下建立一個"ImgFun.cpp"的文件,內容改為下面所示:

//
// Created by Cheng on 2016/11/23.
//
//
// Created by Cheng on 2016/11/23.
//
#include <stdio.h>
#include <stdlib.h>
#include <opencv2/opencv.hpp>
#include "com_example_cheng_haveimgfun_LibImgFun.h"
JNIEXPORT jstring JNICALL Java_com_example_cheng_haveimgfun_LibImgFun_getMycstring
(JNIEnv * evn, jobject obj) {
return ( * evn).NewStringUTF("Hello NKD demo");
}
using namespace cv;
IplImage * change4channelTo3InIplImage(IplImage * src);

extern "C" {

JNIEXPORT jintArray JNICALL Java_com_example_cheng_haveimgfun_LibImgFun_ImgFun
(JNIEnv * env, jobject obj, jintArray buf, jint w, jint h) {

jint * cbuf;
cbuf = env - > GetIntArrayElements(buf, NULL);
printf("%d",cbuf);
if (cbuf == NULL) {
return 0;
}

Mat myimg(h, w, CV_8UC4, (unsigned char * ) cbuf);
IplImage image = IplImage(myimg);
IplImage * image3channel = change4channelTo3InIplImage( & image);
IplImage * pCannyImage = cvCreateImage(cvGetSize(image3channel), IPL_DEPTH_8U, 1);
cvCanny(image3channel, pCannyImage, 50, 150, 3);

int * outImage = new int[w * h];
for (int i = 0; i < w * h; i++) {
outImage[i] = (int) pCannyImage - > imageData[i];
}

int size = w * h;
jintArray result = env - > NewIntArray(size);
env - > SetIntArrayRegion(result, 0, size, outImage);
env - > ReleaseIntArrayElements(buf, cbuf, 0);
return result;
}
}

IplImage * change4channelTo3InIplImage(IplImage * src) {
if (src - > nChannels != 4) {
return NULL;
}

IplImage * destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
for (int row = 0; row < src - > height; row++) {
for (int col = 0; col < src - > width; col++) {
CvScalar s = cvGet2D(src, row, col);
cvSet2D(destImg, row, col, s);
}
}
return destImg;
}

配置文件

然後再在jni下新建兩個文件"Android.mk"文件和"Application.mk"文件,這兩個文件事實上就是簡單的Makefile文件。

使用NDK進行編譯的時候,需要使用 Android.mk Application.mk 兩個文件。

Android.mk
LOCAL_PATH := $(call my-dir)    
include $(CLEAR_VARS)
OPENCV_LIB_TYPE:=STATIC
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#try to load OpenCV.mk from default install location
include D:\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := ImgFun
LOCAL_SRC_FILES := ImgFun.cpp
include $(BUILD_SHARED_LIBRARY)
在Android.mk文件中,需要主要修改的代碼是如下一行:
include D:\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
Application.mk 
APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions
APP_ABI := all
Reference:
http://blog.csdn.net/watkinsong/article/details/8829235
http://blog.csdn.net/watkinsong/article/details/9849973

安裝環境

Tuesday, November 22, 2016

Android studio NDK JNI環境安裝與執行原理

NDK原理的主要六個步驟:


  1. 編寫native 方法的 Java類別
  2. 將 Java 源代碼編譯成 class 字節碼文件
  3. 用 javah -jni 命令生成.h頭文件(javah 是 jdk 自帶的一個命令,-jni 參數表示將 class 中用native 聲明的函數生成 JNI 規則的函數)
  4. 用本地代碼實現.h HEAD文件中的函數
  5. 將本地代碼編譯成動態庫(Windows:\*.dll,linux/unix:\*.so,mac os x:\*.jnilib)
  6. 拷貝動態庫至 java.library.path 本地庫搜索目錄下,並運行 Java 程序 
Android Studio環境下載與安裝

解壓縮到指定目錄
Ex:  D \android-ndk-r13b


接下來設定 File->Project Structure

-------以上完成基本的環境設定-------



 接下來設定External Tools  

 File->Settings 裡面 Tools-->External Tools

因為我已經透過 Add按鈕已經增加了三個會用到的指令,所以我是用鉛筆符號Edit做修改


1.javah設定

以下提供複製直接貼上去 注意 這些路徑都是右上左下的斜線

快速複製區

$JDKPath$/bin/javah.exe

-v -jni -d $ModuleFileDir$/src/main/jni $FileClass$

$SourcepathEntry$
簡單解說: $JDKPath$是環境變數,該行會自動取得你電腦裡面JDK的javah檔案路徑 
參數為的是命令提示字元 下 javah OOOXXX的OOOXXX的內容
主旨就是路徑在Android 專案的src\main\jni下的資源來執行javah的動作

如果有疑問,可以按後面的按鈕 "Insert macro"來查看目前電腦裡環境變數相對應產生的結果


2.NDKBuild設定


快速複製區
D:\android-ndk-r10e\ndk-build.cmd

NDK_PROJECT_PATH=$ModuleFileDir$/build/intermediates/ndk
NDK_LIBS_OUT=$ModuleFileDir$/src/main/jniLibs
NDK_APPLICATION_MK=$ModuleFileDir$/src/main/jni/Application.mk
APP_BUILD_SCRIPT=$ModuleFileDir$/src/main/jni/Android.mk V=1

$ProjectFileDir$
簡單解說:
 第一行就是指定NDK目錄裡的build.cmd檔案
第二第三行是同一個欄位,因為太長自動分行 
重點是 NDK產生的結果會於src/main/jniLibs裡面XX.so的一些檔案
其他的路徑是因人而異,不過 src/main/jni/Application.mk  src/main/jni/Android.mk 這兩個必要檔案等一下因為實作關係一定會放在這個路徑裡面,所以就這樣定。

開始針對程式碼實作

1.設定JNI相關資源
產生JNI資料夾於src\main\jni



直接按下finish

2.針對app的Gradle做整體設定


快速複製區

ndk{
moduleName "myJNI"
}

sourceSets.main{
jni.srcDirs=[]
jniLibs.srcDir "src/main/jniLibs"
}

解說:
加入ndk路徑,也針對等一下NDK編譯書來的路徑指定出來 按下Sync 會產生編譯錯,系統會建議你加入另一個NDK相關的指令,馬上到下一步修正去.....

3.修正gradle.properties的錯誤

補上以下這行,錯誤就可以修正完畢


android.useDeprecatedNdk=true






4.產生JNI檔案
接著於程式碼中 新增Java Class檔案  檔名我是使用myNDK





快速複製區



static {
System.loadLibrary("myJNI");
}
public native String getMycstring();

解說:
有產生一個相對應的C語法資源,檔名是myJNI,裡面有可以呼叫的方法叫做getMycstring()

5.產生Header檔案(必須由Javah自動產生比較簡單,盡量不要自己手動建置一個新檔案來產生檔案上按下 右鍵--->external tools-->javah(我的命名)




接著Android Studio內的Terminal會被開啟,執行產生C語言HEAD檔案的步驟,

成功的話會向下圖一樣,就會於jni資料夾中產生一個根據我的Project Name產生的一個HEAD檔

建議檔名不要去動他....

如果有錯誤與失敗,HEAD檔案不會產生,那就要檢查第二個步驟中,External tools 裡的javah內的路徑是不是符合你的電腦環境





產生完成後,檢查一下HEAD內容

自動產生的內容裡面,他會將你的project name
譬如我的範例是:com.example.cheng.testndk

供android java 呼叫的java class 檔名是myNDK,會呼叫到的C方法是getMycstring

所以他會將JAVA的設定全部結合在這個HEAD中,所以我強烈建議由javah自動去產生



6.產生C/C++的原始碼檔

在jni資料夾上右鍵--> new --> c/c++ source file

產生的小視窗,記得把create associated header取消,只產生source檔案




產生之後,就是開始編寫C的內容

首先 先include剛剛自動產生的HEAD檔,因為檔名有長,可以用Copy Path的方式取得

之後方法的初始化,請打開剛剛的HEAD檔,複製其內容後貼來

快速複製區
myJNI.cpp

//
// Created by Cheng on 2016/11/22.
//

#include"com_example_cheng_testndk_myNDK.h"
JNIEXPORT jstring JNICALL Java_com_example_cheng_testndk_myNDK_getMycstring
(JNIEnv * evn, jobject obj)
{
return
(*evn).NewStringUTF("Hello NKD demo");

}

7.產生2個MK檔
在jni資料夾上按下右鍵,新增兩個純檔案,第一個Android.mk,第二個是Application.mk


接著是兩個檔案裡面的內容 解說:

LOCAL_MODULE := myJNI
LOCAL_SRC_FILES := myJNI.cpp

第一個模組名稱,必須跟build.gradle裡面的moduleName相同

也跟JAVA程式裡面,myNDK class裡面 System.loadLibrary("myJNI")相同

SCR_FILES的名稱則是真正C語言的原始碼名稱

快速複製區
Android.mk 


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := myJNI
LOCAL_SRC_FILES := myJNI.cpp

include $(BUILD_SHARED_LIBRARY)

快速複製區

Application.mk
APP_ABI := all


最後 就是真正執行一次C的編譯
 請到JNI資料夾按右鍵-->External Tools-->ndkbuild 
如果成功,就會向下圖出現一堆編譯順序,之後會寫藍色的字,表示完成。





最後檢查一下編譯產生的XXX.so檔案,是不是有依照各種手機CPU類型編譯在不同的資料夾中


MainActivity.java code

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textTX=(TextView)findViewById(R.id.textTX);
TextView textView3 = (TextView)findViewById(R.id.textView3);
myNDK testNDK=new myNDK();
int a = testNDK.getMyint();
textTX.setText(testNDK.getMycstring());
String s = Integer.toString(a);
textView3.setText(s);
}
}
Reference: 
http://blog.xuite.net/lwchafter30/blog/373974237-Android+studio+1.5.1+NDK+JNI%E7%92%B0%E5%A2%83%E5%AE%89%E8%A3%9D%E8%88%87%E5%9F%B7%E8%A1%8C%E5%8E%9F%E7%90%86
https://www.youtube.com/watch?v=RmPuwdxR1qs

Monday, November 21, 2016

Add C and C++ Code to Your Android Project

使用Android Studio 2.2或更高的版本,可以將C和C++代碼編譯成native library(即 .so文件),然後打包到APK中。Java代碼可以透過Java Native interface (JNI)調用native library中的方法。

Andorid Studio默認使用CMake編譯原生庫,由於已經有大量的代碼使用了ndk-build來編譯
native code,所以Android Studio也支持ndk build。如果你想導入一個ndk-build庫到你的Android Studio項目中,請參閱後文的連結本地庫與Gradle。然而,如果你創建了一個新的native code工程,你應該使用CMake。

本篇文章將會說明如何使用Android Studio 來創建、配置Android 項目,以支持native code,以及將其運行到你的app 中。

下載NDK和建構工具

  • Android原生開發套件(NDK):允許使用C和C++與Android的代碼,並提供平台庫。
  • CMake:外部構建工具。如果你只打算使用NDK的構建你不需要這個組件。
  • LLDB:Android Studio用於調試本機代碼。
安裝步驟:
  1. 從菜單欄中選擇 SDK Manager 
  2. 點擊SDK Tools選項卡
  3. 勾選LLDB,CMake和NDK
  1. 點擊Apply,然後點擊OK。
  2. 當安裝完成後,點擊Finish,然後點擊OK。

建立支持C/C++的Project

建立一個支持native code 的專案和創建普通的Android studio 工程很像。但是有幾點需要留意的地方:

在Configure your new project選項中,勾選Include C++ Support選項。點擊Next,後面的流程和創建普通的Android studio 工程一樣。在Customize C++ Support選項卡中。


你有下面幾種方式來自定義你的項目:
  1. C++ Standard:點擊下拉框,可以選擇標準C++,或者選擇默認CMake設置的Toolchain Default選項。
  2. Exceptions Support:如果你想使用有關C++異常處理的支持,就勾選它。勾選之後,Android Studio會在module層的build.gradle文件中的cppFlags中添加-fexcetions標誌。
  3. Runtime Type Information Support:如果你想支持RTTI,那麼就勾選它。勾選之後,Android Studio會在module層的build.gradle文件中的cppFlags中添加-frtti標誌。
  4. 點擊“Finish”。
當Android Studio完成新項目創建後,打開Project面板,選擇Android視圖。Android Studio會添加cpp和External Build Files目錄。

1.cpp目錄存放你所有native code的地方,包括源碼,頭文件,預編譯項目等。對於新項目,Android Studio創建了一個C++模板文件:native-lib.cpp,並且將該文件放到了你的app模塊的src/main/cpp/目錄下。這份模板代碼提供了一個簡答的C++函數:stringFromJNI(),該函數返回一個字符串:”Hello from C++”。

2.External Build Files目錄是存放CMake或ndk-build構建腳本的地方。有點類似於build.gradle文件告訴Gradle如何編譯你的APP一樣,CMake和ndk-build也需要一個腳本來告知如何編譯你的native library。對於一個新的項目,Android Studio創建了一個CMake腳本:CMakeLists.txt,並且將其放到了你的module的根目錄下。

運行APP

當你點擊Run按鈕,Android Studio會編譯並啟動一個APP ,然後在APP中顯示一段文字”Hello from C++”。從編譯到運行示例APP的流程簡單如下:

1.Gradle調用外部構建腳本,也就是CMakeLists.txt。

2.CMake會根據構建腳本的指令去編譯一個C++源文件,也就是native-lib.cpp,並將編譯後的產物扔進共享對像庫中,並將其命名為libnative-lib.so,然後Gradle將其打包到APK中。

3.在運行期間,APP的MainActivity會調用System.loadLibrary()方法,加載native library。而這個庫的原生函數,stringFromJNI(),就可以為APP所用了。

4.MainActivity.onCreate()方法會調用stringFromJNI(),然後返回“Hello from C++”,並更新TextView的顯示。

如果你想驗證一下Gradle 是否將native library 打包進了APK,你可以使用APK Analyzer:
  1. 選擇Build > Analyze APK
  2. 從app/build/outputs/apk/路徑中選擇APK,並點擊OK
  3. 如下圖,在APK Analyzer窗口中,選擇lib/<ABI>/,你就可以看見libnative-lib.so



Reference:
https://wl9739.github.io/2016/09/21/%E5%9C%A8-Android-Studio-2-2-%E4%B8%AD%E6%84%89%E5%BF%AB%E5%9C%B0%E4%BD%BF%E7%94%A8-C-C-md/
https://developer.android.com/studio/projects/add-native-code.html#download-ndk
https://developer.android.com/studio/projects/create-project.html