1. 程式人生 > >Android Studio NDK開發——三步實現HelloWorld一篇就夠了

Android Studio NDK開發——三步實現HelloWorld一篇就夠了

引言

        之前工作做MTK平臺那會,一直用的C語言,現在改做高通平臺Android了,C語言也用的少了,這裡就藉助NDK再鍛鍊下C語言的程式設計能力。
相信和大多數人一樣,在用一樣新東西時,肯定會在網上一陣亂搜,搜尋自己需要的相關的資訊。
        小編在網上搜索了很多關於NDK開發的(這裡關於C語言的基礎知識不在單獨介紹),開篇HelloWorld,但是照著網上說的敲程式碼,還是遇到了很多問題,或者使用的軟體環境配置不一樣,其他人寫的時候可能不會有問題,你自己寫的時候會出問題,這就需要自己根據錯誤提示來一步一步解決了。而且其他的介紹真的非常非常複雜,真的有那麼難嗎,就一個NDK helloworld的demo,不試不知道
NDK HelloWorld真的非常非常簡單:三步

一、AS中下載NDK即可
二、新增程式碼塊
static{
System.loadLibrary("hello");
}
public native String getStringFromC();
三、自動生成C檔案
四、build專案,執行
這裡不需要javah 生成標頭檔案,不需要copy so庫檔案,真的就三步,試試才知道。

一、環境配置

Android Studio 開發NDK,環境配置真的非常非常簡單
就像大多數人一樣,在做NDK開發時(自己寫demo也好,專案需要也好),可能第一步就是NDK環境配置,小編使用的是Android Studio,故這裡講述的是AS關於NDK開發的環境配置。
網上有的說還要自己單獨下載NDK android-ndk-r11c類似這個,完全沒必要,在AS中下載即可

1、AS->File->Settings,如圖

這裡寫圖片描述
前面帶 - 的是可以升級的項,可以不用管,如果前面沒有勾選的,勾選,然後下載。NDK這項是必須要下載的,看其他博文,有讓下載CMake,LLDB的,小編還沒用到這兩樣,這裡一併下載,以防以後出現問題。
下載過的可以略過此步驟。

3、AS->File->Project Structure

如果AS中NDK下載好後,新建專案,你會發現NDK的路徑已經配置好了,如果路徑沒有配置好,自行點選路徑欄後面的更多按鈕,找到NDK下載到的路徑,確定即可
這裡寫圖片描述

2、新建Project

這裡新建Project時,不需要勾選C++ support,記住,不用勾選此項(你可以勾選,小編就感覺勾選後再自己配置NDK挺彆扭的,因為勾選後,相當於AS很多東西都給你寫好了,不需要你手動來新增檔案之類的了,再學習配置也就失去了本身的意義,你可以在對NDK的配置及需要寫的東西熟練後,再預設勾選。記得當上大學那會,剛學Html時,我們用的是記事本編寫的heml,沒有用MyEclipse,或者其他的自動化編寫軟體,一個道理,對裡面的結構,內容熟練後可以用自動生成,前期為了鍛鍊,熟練後是為了省時,高效)。

這裡寫圖片描述

二、程式碼部分

環境配置就上面那麼多,有沒有發現真的特別簡單,其實環境配置說白了就只需要下載下NDK就可以了(小編不確認需要不需要配置環境變數,還是說小編之前已經配置過環境變量了,這裡沒有特殊的要求,沒有再配置環境變數)
到了擼程式碼的時候了,有沒有很興奮。

1、so庫檔案的生成

(1)、
在project的gradle.properties中新增如下程式碼,表示支援舊版本NDK

android.useDeprecatedNdk=true

這裡寫圖片描述

(2)、

1>在defaultConfig{}中新增如下程式碼,

ndk {
            moduleName "hello" //模組名,需要和System.loadLibrary("xxx");中的xxx保持一致,必現一致
            abiFilters "armeabi", "armeabi-v7a", "x86"//平臺支援,java是跨平臺語言,C語言不是,這裡新增的是支援的平臺,具體含義百度
}

2>在module的build.gradle中新增gradle-experimental外掛支援,此外掛只是為了便於C檔案程式碼的自動生成,在C程式碼生成後,需要註釋掉此外掛,否則專案執行時會報錯。記住,在新增此外掛後,clean或者rebuild時有可能會報錯,不用管,待C檔案自動生成(見步驟<3>)後註釋掉。

compile 'com.android.tools.build:gradle-experimental:0.7.0'
//僅僅在我們生成jni方法框架時新增,
// 當我們全部新增完JNI方法框架之後,必須註釋或者刪除掉,否則run的時候就絕對報錯

(3)、
在自己的包名下新建java檔案,HelloWorld.java

package com.ndk.demo;

/**
 * Created by wangqixu on 2017/10/9.
 */

public class HelloWorld {
    //這裡先把static程式碼塊去掉,static程式碼塊是為了呼叫so庫中的方法,和生成so庫沒有關係。
    //另外,小編在生成.h標頭檔案時,帶上static程式碼塊,報錯了。
    /*static{
        System.loadLibrary("hello");
    }*/
    public static native String getStringFromC();//如果第(2) 2>步你做了的話,
    你會發現當你寫完這行程式碼時,AS會提示你建立C函式(解釋下函式概念,C語言中的函式即相當於java中的方法,只是的稱呼不一樣),
    點選alter+enter(自動完成快捷鍵,視你設定的快捷鍵而定),將會在jni目錄下自動建立C檔案,如果沒有新增gradle-experimental外掛支援,是不會提示自動穿件C檔案的
}

有沒有感覺到,特別的簡單。如果(2) 2>步驟完成後,(3)中寫完native方法聲明後,c函式的實現將可以自動建立完成。
自動生成的hello.c檔案(c檔名和java檔名保持一致,這個小編不確定,檔名任意也沒有影響)如下

這裡寫圖片描述

return (*env)->NewStringUTF(env, "來自C語言的返回值 哈哈哈哈");//修改其中的return語句
JNIEXPORT jstring JNICALL//注意到沒,自動生成的程式碼jstring和JNICALL之間少了空格,新增上空格(在程式碼編寫過程中不同的人可能會遇到不同的問題,需要你自己看log,錯誤提示,來解決,有可能其他人使用的AS版本自動生成的是正常的,而你用的AS版本或者外掛版本卻是有錯誤的,類似這樣的問題自己除錯)

c檔案編寫完成無誤後,註釋掉(2)中的gradle-experimental外掛,clean,rebuild 專案,會在如下圖,生成相應的so檔案。
這裡寫圖片描述

三、載入so庫檔案

1、MainActivity.java檔案
程式碼真的特別特別的少

這裡寫圖片描述

只需要這兩句即可

package com.ndk.demo;

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

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView)findViewById(R.id.tv);
        textView.setText(HelloWorld.getStringFromC());//關鍵的就這一句程式碼,少不少
    }
}

HelloWorld.java

package com.ndk.demo;

/**
 * Created by wangqixu on 2017/10/9.
 */

public class HelloWorld {
    static{
        System.loadLibrary("hello");//載入庫檔案
    }
    public static native String getStringFromC();
}



執行專案,你會發現效果已經出來了
這裡寫圖片描述

四、疑惑

不知道你會不會有疑問,怎麼這麼奇怪,網上說的還有生成.h檔案 javah ,還要copy so檔案呢,怎麼這裡都沒做,執行專案效果就出來了,難道就這麼簡單嗎,我的天,完全顛覆了之前看的NDK的部落格,什麼情況啊這是??有沒有一萬個草泥馬飛過。對,就是這麼簡單,因為這只是個demo,本來就特別簡單。
1、為何沒有生成.h檔案:因為不需要生成.h檔案,c語言中只有在互相呼叫函式時(不再解釋了,.h檔案的作用,自行百度)
有人說了,我就要生成.h檔案,怎麼辦,好辦,也簡單

(1)、生成.h檔案

在AS Terminal中執行

javah -d ../jni -jni com.ndk.demo.HelloWorld

即可在main目錄下的jni目錄下生成com_ndk_demo_HelloWorld.h檔案

E:\Users\NDK\app\src\main\java>javah -d ../jni -jni com.ndk.demo.HelloWorld

解釋:此命令相當於在java目錄下執行的,-d指定生成生成檔案所在的目錄 ../jni 表示父目錄下的jni資料夾,即main下的jni目錄,如果沒有此目錄,則生成jni資料夾
-jni,意思是生成jni標識的標頭檔案,其實這個引數完全可以不加,暫時沒有發現什麼意義。
com.ndk.demo.HelloWorld 需要帶上包名
注:小編在使用此命令的時候,僅僅是把HelloWorld中的 static程式碼塊註釋掉,但是依然報錯,索性直接去掉了,沒有再報錯。
這裡寫圖片描述


如果你的快捷欄沒有Terminal,可以
這裡寫圖片描述


或者滑鼠右鍵電腦形狀的圖示或點選此圖示
這裡寫圖片描述
調出Terminal

(2)、手動編寫c檔案
        copy .h檔案中的函式宣告部分到c檔案,然後編輯此函式(之所以copy,是為了防止書寫錯誤)

        hello.c檔案,(檔名可以隨意定),如果c檔案是自動生成的則檔名應該是和java檔名保持一致的(小編的是這樣的)

#include <jni.h>
JNIEXPORT jstring JNICALL Java_com_ndk_demo_HelloWorld_getStringFromC(JNIEnv *env, jclass type){
    return (*env)->NewStringUTF(env, "來自C語言的返回值 哈哈哈哈");
}

        有人說了,那這樣的話生成的.h檔案不是還是沒有用到嗎,是的,沒有用到,因為helloworld這個demo確實不需要用.h檔案,當然你也可以用

#include <com_ndk_demo_HelloWorld.h>
//它的作用其實就是在裡面包含了jni.h,c檔案中如果加入#include <com_ndk_demo_HelloWorld.h>
//則可以不用寫#include <jni.h>,效果是一樣的
小編在使用#include <com_ndk_demo_HelloWorld.h>時,
程式碼編譯過程中報錯了,故沒有使用標頭檔案,另外c檔案如果報錯,試著將AS新建檔案時自動新增的Created by xxx  2017 xxx,類似的這些沒用的都去掉,再編譯試試。

2、為何沒有copy 庫檔案到libs目錄
        之所以沒copy:因為不需要copy,專案中有原始碼C檔案,so是c檔案生成的,故即使不copy so庫檔案到jni目錄,也可以正常執行專案。之所以說需要copy,是因為你在使用別人的so檔案時,一般是沒有c程式碼的(為了程式碼的安全,比如別人寫的一個很牛逼的影象處理演算法,做相機美顏時使用的,別人肯定只會給你so檔案,不會給你c程式碼。所以你需要將so庫檔案copy到 libs目錄下),c程式碼生成的so檔案很難反編譯,另外c可以操作硬體,執行效率要比java快(不一定是絕對的)

        copy so檔案到libs目錄下後,你可以刪除jni目錄,即刪除c程式碼,此時仍然可以執行。如果你不copy so庫檔案到libs,刪除c程式碼後項目肯定是不能執行的。

五、自動生成so庫檔案並copy到指定目錄的方法

前面說到在build.gradle中新增

ndk {
            moduleName "hello"
            abiFilters "armeabi", "armeabi-v7a", "x86"
    }

        則最後生成的so庫目錄分別是 armeabi,armeabi-v7a,x86,那如果有很多平臺,我是不是要一個一個都寫上呢,如果我不知道其他是什麼平臺怎麼辦呢?有沒有辦法解決呢,有,直接在jni目錄上滑鼠右鍵,執行ndk-build即可生成so檔案,並自動copy到jnilibs目錄下。你可能會躍躍欲試,但是最後卻發現自己的右鍵中沒有NDK這個命令,奇了怪,又一萬個草泥馬飛過吧,這都不是事,見下面配置。
滑鼠右鍵,直接執行ndK-build命令
這裡寫圖片描述



執行命令後生成的so庫檔案,是不是很全,而且build.gradle中也不用新增 ndk{xxx},不用新增
這裡寫圖片描述

1、ndk命令配置,AS-File-Settings,見下圖,
這裡寫圖片描述

javah:

Projram              $JDKPath$/bin/javah
Parameters:          -encoding UTF-8 -d ../jni -jni $FileClass$
Working directory:   $SourcepathEntry$\..\java

ndk-build:

Projram              D:\adt\sdk\ndk-bundle\build\ndk-build.cmd //換成你自己的ndk-bundle目錄即可
Parameters:          NDK_LIBS_OUT=$ModuleFileDir$/src/main/jniLibs
Working directory:   $ModuleFileDir$\src\main\

        除了ndk-build中 projram換成你自己的ndk-bundle路徑外(一般都在sdk目錄下xxx\xxx),其他基本一致,關於勾選框的地方,勾選不勾選沒有特殊要求,預設就可以

效果圖
javah

這裡寫圖片描述

ndk-build
這裡寫圖片描述

別忘確定儲存。

2、編寫mk檔案,檔案必須是Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello   //模組名,和System.loadLibrary("xxx");xxx必須保持一致
LOCAL_SRC_FILES := helloworld.c  //和你的c檔名對應
include $(BUILD_SHARED_LIBRARY)

helloworld.c 檔案

#include <jni.h>
JNIEXPORT jstring JNICALL Java_com_ndk_demo_HelloWorld_getStringFromC(JNIEnv *env, jclass type){
// TODO
return (*env)->NewStringUTF(env, "來自自C語言的返回值  哈哈哈哈");
}

        再說一遍,helloWorld這個demo,真的不需要生成com_ndk_demo_HelloWorld.h標頭檔案,不需要引用com_ndk_demo_HelloWorld.h檔案

        當然關於HelloWorld.java這個檔案,你也可以不用單獨寫,裡面的程式碼完全可以放到Activity中,這裡只是為了和Activity獨立出來,其他地方便於呼叫,要靈活使用。
        ok,本篇講完。有疑問可以留言。