1. 程式人生 > >Android開發學習之路--在Android應用中愉快地寫C/C++程式碼

Android開發學習之路--在Android應用中愉快地寫C/C++程式碼

1 前言

一直想在android層面寫c程序,然後java可以與c程序互動,以前在android原始碼中想玩就可以直接在init.rc中加上交叉編譯好的c程序就可以了,而在ide中,也就是ndk編譯後各種許可權問題就有點不得而知了。花了幾天時間研究實踐,也終於實現了。再者這個也可以為後期做程序間通訊和守護程序做準備。程序間通過一箇中轉daemon來處理分發,各個程序互動的介面也可以通過jni暴露給java層,而內部實現在c中也可以防止反編譯,安全性上也有一定的保障。說了這麼多,還是開始吧。

2 CMake JNI開發

Android studio逐漸用CMake來開發JNI層的c程式碼,也就是生成動態庫so檔案,不管是除錯還是補全都是非常不錯的。那就先來寫一個jni吧,在新建工程的時候,有個選項( Include C++ support)勾選上就會生成對應的jni的支援,

2.1 build.gradle中加了如下的配置:

cppFlags支援c++11

externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }

2.2 編譯的配置檔案CMakeLists.txt

 externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
cmake_minimum_required(VERSION 3.4
.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library
. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )

其實看下也能理解,native-lib是庫的名字,log 是列印需要依賴的android中的動態so庫,src/main/cpp/native-lib.cpp是對應的原始碼。

2.3 熟悉JNI程式碼

從CMakeLists.txt中我們可以看到程式碼路徑在src/main/cpp資料夾下,看下程式碼如下:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_jared_jnidaemon_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

    return env->NewStringUTF(hello.c_str());
}

比較簡單,返回一個Hello from C++的字串,我們再來看下java層怎麼呼叫的。

呼叫其實很簡單,首先是一個native的介面:

public native String stringFromJNI();

一個靜態的載入so庫的系統方法:

static {
        System.loadLibrary("native-lib");
    }

到這裡,只要通過stringFromJNI()這個介面就可以呼叫c++層的程式碼獲取對應的字串了。是不是很簡單,那就繼續嘗試下自己加一個方法吧。這裡我們使用c++來開發吧。

2.4 動手寫JNI程式碼

首先在java層定義下我們需要的介面:

public native int sumFromJNI(int a, int b);

然後通過”Show Intention Actions”快捷鍵來create對應的jni的方法

extern "C"
JNIEXPORT jint JNICALL
Java_com_jared_jnidaemon_MainActivity_sumFromJNI(JNIEnv *env, jobject instance, jint a, jint b) {

    // TODO

}

然後我們就可以在這裡返回 a + b的值就可以了。這裡我們會新建一個c++檔案,那樣涉及的東西多點。cpp目錄下新建Hello.cpp檔案和Hello.h檔案:

#ifndef JNIDAEMON_HELLO_H
#define JNIDAEMON_HELLO_H

#include <string>
using namespace std;

class Hello {
public:
   Hello();

   string getHello();
   int sum(int a, int b);
};

#endif //JNIDAEMON_HELLO_H

新建一個Hello類,包含getHello方法和sum方法

#include <string>
#include "hello.h"

using namespace std;

Hello::Hello() {}

string Hello::getHello() {
    return "Hello eastmoon1117";
}

int Hello::sum(int a, int b) {
    return a + b;
}

實現Hello類的方法,比較簡單。再看下native-lib.cpp:

extern "C"
JNIEXPORT jint JNICALL
Java_com_jared_jnidaemon_MainActivity_sumFromJNI(JNIEnv *env, jobject instance, jint a, jint b) {

    Hello *hello= new Hello();
    return hello->sum(a, b);
}

這裡返回了hello的sum方法。
都準備好了,那就把.cpp加入到CMake編譯中吧

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp
             src/main/cpp/hello.cpp
             )

然後我們再activity中呼叫
Log.d(“MainActivity”, sumFromJNI(2, 6));就可以打印出8了。

3 NDK生成c可執行檔案並執行

因為是c程序,可以說和主工程沒有啥依賴,那就新建一個module來完成,這裡新建Daemon模組。本來想著用cmake的,但是不知道怎麼玩,就放棄了,直接用ndk編譯吧還是。在新的Daemon模組中新建jni目錄,在jni中新建daemon目錄。

3.1 Makefile

首先在jni目錄下建立兩個檔案,Android.mk和Application.mk
先看下Android.mk檔案:

include $(call all-subdir-makefiles)

意思就是包含素有子目錄的makefile也就是所有子目錄的Android.mk

再看下Application.mk檔案:

APP_ABI := all
APP_PLATFORM := android-18

APP_ABI是cpu相關的,比如arm的,mips的,x86的,這裡就編譯所有就好了。
APP_PLATFORM 是平臺相關的

現在回到daemon檔案,我們需要的東東,新建Android.mk,daemon.c和log.h檔案。
先看下Android.mk檔案:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := daemon
LOCAL_SRC_FILES := daemon.c

LOCAL_C_INCLUDES += \
    $(LOCAL_PATH) \

LOCAL_LDLIBS := -lm -llog

include $(BUILD_EXECUTABLE)

編譯生成的檔名為daemon,原始碼是daemon.c,依賴m和log庫,BUILD_EXECUTABLE表示生成可執行檔案。

3.2 原始碼

再看下log.h檔案:


#include <jni.h>
#include <android/log.h>

#define TAG     "Native"

#define LOG_I(...)  __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOG_D(...)  __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOG_W(...)  __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOG_E(...)  __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

#define LOGI(tag, ...)  __android_log_print(ANDROID_LOG_INFO, tag, __VA_ARGS__)
#define LOGD(tag, ...)  __android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__)
#define LOGW(tag, ...)  __android_log_print(ANDROID_LOG_WARN, tag, __VA_ARGS__)
#define LOGE(tag, ...)  __android_log_print(ANDROID_LOG_ERROR, tag, __VA_ARGS__)

這個就是對log的一個封裝。

最後看下daemon.c檔案:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#include "log.h"

#define LOG_TAG         "Daemon"

/* signal term handler */
static void sigterm_handler(int signo) {
    LOGD(LOG_TAG, "handle signal: %d ", signo);
}

int main(int argc, char *argv[]) {

    LOGI(LOG_TAG, "Copyright (c) 2018, eastmoon<[email protected]>");

    LOGI(LOG_TAG, "=========== daemon start =======");

    /* add signal */
    signal(SIGTERM, sigterm_handler);

    while (1) {
        LOGI(LOG_TAG, "=========== daemon running ======");
        sleep(3);
    }
}

主程序就是睡眠3s列印一條log。

3.3 build.gradle配置

建立一個ndk編譯的task

task ndkBuild(type: Exec) {
    //def ndkDir = project.plugins.findPlugin('com.android.library').sdkHandler.getNdkFolder()
    def ndkDir = project.android.ndkDirectory
    commandLine "$ndkDir/ndk-build.cmd", '-C', 'src/main/jni',
            "NDK_OUT=$buildDir/ndk/obj",
            "NDK_APP_DST_DIR=$buildDir/ndk/libs/\$(TARGET_ARCH_ABI)"
}

因為最後需要把對應的c程序拷貝到asset中,所以需要加上

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

然後新建一個copyExeFile任務:

task copyExeFile(type: Copy) {
    from fileTree(dir: file(buildDir.absolutePath + '/ndk/libs'), include: '**/*')
    into file('src/main/assets/')
}

再右上角找到Daemon–>Tasks–>other–>ndkBuild執行就可以編譯出對應的c可執行檔案
然後右上角找到Daemon–>Tasks–>other–>copyExeFile執行把需要的c可執行檔案拷貝到對應的assets資料夾下。

3.4 執行java層執行c程序

準備好了c可執行檔案後,下一步就是執行這個程序。由於android的許可權問題,對應的app會安裝到/data資料夾下,而/data資料夾又需要root後才能進去,一般手機是沒有root掉的,所以我們就通過讀取assets中的c檔案,寫到/data資料夾的對應的包中,然後再執行該檔案,那麼不需要root的情況下就可以執行我們的c程序了。

3.4.1 CommandForNative

在java層新建CommandForNative類:程式碼有點多,這裡不貼了,可以參考github的程式碼。簡要介紹下:
installBinary方法,就是根據cpu型號找到對應的c檔案,從assets中拷貝到指定的目錄下,然後設定許可權成為可執行許可權。
restartBinary方法:先殺死已有的程序,然後再起這個程序。

3.4.2 Daemon

然後看下Daemon類:

    private static final String BIN_DIR_NAME = "bin";
    private static final String BINARY_NAME = "daemon";

    /**
     * 執行可執行檔案
     * @param context
     * @param args 可執行檔案的引數
     */
    public static void run(final Context context, final String args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                CommandForNative.installBinary(context, BIN_DIR_NAME, BINARY_NAME);
                //CommandForNative.execBinary(context, BIN_DIR_NAME, BINARY_NAME, args);
                CommandForNative.restartBinary(context, BIN_DIR_NAME, BINARY_NAME, args);
            }
        }).start();
    }

這裡daemon就是對應的可執行檔名字,bin是需要新建的/data/data資料夾下的對應app資料夾的目錄,也即是daemon檔案的安裝目錄。

3.5 執行效果

看下效果,執行

adb shell

執行看下程序

[email protected]:/ $ ps | grep jared
ps | grep jared
u0_a620   14648 317   1597528 41920 ffffffff 00000000 S com.jared.jnidaemon
u0_a620   14695 14648 3108   412   ffffffff 00000000 S /data/data/com.jared.jnidaemon/app_bin/daemon

看下列印的log資訊:

[email protected]:/ $ logcat -s Daemon
logcat -s Daemon
--------- beginning of system
--------- beginning of main
I/Daemon  (14695): Copyright (c) 2018, eastmoon<[email protected]>
I/Daemon  (14695): =========== daemon start =======
I/Daemon  (14695): =========== daemon running ======
I/Daemon  (14695): =========== daemon running ======
I/Daemon  (14695): =========== daemon running ======
I/Daemon  (14695): =========== daemon running ======
I/Daemon  (14695): =========== daemon running ======

就是我們所需要的。當然這個只是簡單地例子,後續我們就可以基於這個愉快地寫c/c++程式碼。

相關推薦

Android開發學習--Android Studio外掛開發

前言 因為使用了mvp和dagger,所以每次新的一個功能頁面都需要重新寫一堆東西,比如Activity, Fragment, Presenter,dagger等,而這些程式碼基本上都是大同小異,完全可以寫一個模板,然後生成,略微修改便可以完成我們需要的功能。

Android開發學習--Android Studio cmake編譯ffmpeg

  最新的android studio2.2引入了cmake可以很好地實現ndk的編寫。這裡使用最新的方式,對於以前的android下的ndk編譯什麼的可以參考之前的文章:Android開發學習之路–NDK、JNI之初體驗。 1.ffmpeg編譯  

Android開發學習--在Android應用愉快C/C++程式碼

1 前言 一直想在android層面寫c程序,然後java可以與c程序互動,以前在android原始碼中想玩就可以直接在init.rc中加上交叉編譯好的c程序就可以了,而在ide中,也就是ndk編譯後各種許可權問題就有點不得而知了。花了幾天時間研究實踐,也終於

Android開發學習--異步消息Handler,Message,Looper和AsyncTask初體驗

被調用 project 輸入 gettext npos article app sso 音樂播放 在簡易音樂播放器中。用了Handler。也沒有過多地去研究學習,這裏再學習下android下的異步消息處理機制。這裏用了Handler主要是在線程中不能更新UI

Android開發學習--圖表實現(achartengine/MPAndroidChart)初體驗

bundle 喜歡 嵌入式linux Y軸 tid ren sca ref java代碼 ??已經有一段時間沒有更新博客了,在上周離開工作了4年的公司,從此不再安安穩穩地工作了。很多其它的是接受挑戰和實現自身價值的提高。離開了嵌入式linux,從此擁抱移

Android開發學習-EventBus使用

轉載自: https://www.cnblogs.com/Fndroid/p/5910992.html EventBus是一個通過釋出、訂閱事件實現元件間訊息傳遞的工具。 它存在的目的,就是為了優化元件之間傳遞訊息的過程。傳統元件之間傳遞訊息的方法有使用廣播,回撥等,而這些方法使用都比較

Android開發學習--Drawable mutations

  時間過得很快,明天終於可以拿到房子了,交完這次房租,也可以成為房東了,看看部落格也好久沒有更新了,最近一直在整機器人,也沒有太多時間整理部落格。   今天下午和同事一起遇到了一個問題,就是明明沒有

Android開發學習--RxAndroid簡單原理

  學習了RxAndroid,其實也就是RxJava了,但是還是不是非常清楚到底RxAndroid有什麼用呢?為什麼要使用RxAndroid呢?這篇文章講得不錯,RxJava的原理。但是這裡還是把整個過

Android開發學習--Activity初體驗

    環境也搭建好了,android系統也基本瞭解了,那麼接下來就可以開始學習android開發了,相信這麼學下去肯定可以把android開發學習好的,再加上時而再溫故下linux下的知識,看看an

Android開發學習--資料持久化初體驗

    上班第一天,雖然工作上處於醬油模式,但是學習上依舊不能拉下,接著學習android開發吧,這裡學習資料持久化的 知識。     其實資料持久化就是資料可以儲存起來,一般我們儲存資料都是以檔案,或者資料庫的形式儲存的,android程式也有 檔案和資料庫的儲存,此外還

Android開發學習--非同步訊息Handler,Message,Looper和AsyncTask初體驗

    在簡易音樂播放器中,用了Handler,也沒有過多地去研究學習,這裡再學習下android下的非同步訊息處理機制。這裡用了Handler主要是線上程中不能更新UI,而需要通過Handler才可以。關於非同步訊息處理有幾個概念。     1、Message:訊息,執行

Android開發學習--NDK、JNI初體驗

    好久沒有更新部落格了,最近一直在看一個仿微信專案,然後看原始碼並自己實現下,相信經過這個專案可以讓自己瞭解一個專案中的程式碼以及種種需要注意的事項。不知不覺中部落格已經快要40w訪問量,而且排名也即將突破3000了,在此感謝朋友們的支援和認可。今天趁著有點時間就來完

Android開發學習--UI基本佈局

    上一篇文章中主要介紹了ui的控制元件,這裡就學習下佈局吧。android的基本佈局在layout下主要如圖:     從上圖可以看出有FrameLayout(單幀佈局),LinearLayo

Android 音視訊開發學習

  一直欠大家一篇音視訊入門之路的文章,這篇文章是我見過寫的最詳細的一篇了,今天算還了哈。作者從入門、進階、探究分別編寫了一系列文章。   Android 音視訊開發這塊目前的確沒有比較系統的教程或者書籍,網上的部落格文章也都是比較零散的。只能通過一點點的學習和積累把這塊的知識串聯積累起

Android學習往系統應用新增framework層的jar包

Framework中的app為什麼在編譯的時候需要到原始碼中編譯: 因為缺少必要的包(原始碼)----在連線的時候是以class檔案來連線編譯的 以Systemeui為例:   缺少這個包 尋找android原始碼的時候可以找到這個包: 知道framework在編譯

Android破解學習(十二)—— GP錄影漢化過程及添加布局

## 前言 最近閒著發慌,想起了很久之前就想漢化的一款錄影APP,APP大小不到1MB,但是好用,本期就給大家帶來漢化的基本步驟以及如何在APP中新增我們漢化的資訊 ## 漢化思路 1. **查詢關鍵字** 關鍵字挺好找的,由於APP本身就是英文,我們找到某個英文單詞進行搜尋即可 2. **找到string.

Android破解學習(十三)—— 另類的破解VIP思路

前言 一般按照以往,我們想要獲得某個軟體的VIP,一般是通過修改支付寶的支付流程,原本購買失敗的,我們修改程式碼,從而使得失敗變成了成功,不花費金錢 另類思路 有些軟體將判斷使用者是否為VIP的程式碼寫在了本地,這樣我們就可以修改這個程式碼獲得VIP,注意是有些軟體,並不是所有 判斷VIP也是一個簡單

android 通訊學習 socket udp tcp

原文 https://www.jianshu.com/p/61de9478c9aa   整體步驟流程 先來說一下整體的步驟思路吧: 傳送 UDP 廣播,大家都知道 UDP 廣播的特性是整個網段的裝置都可以收到這個訊息。 接收方收到了 UDP 的廣播,將

Android破解學習(十)—— 我們戀愛吧 三色繪戀 二次破解

前言 好久沒有寫破解教程了(我不會告訴你我太懶了),找到一款戀愛遊戲,像我這樣的宅男只能玩玩戀愛遊戲感覺一下戀愛的心動了。。 這款遊戲免費試玩,但是後續章節得花6元錢購買,我怎麼會有錢呢,而且身在吾愛的大家庭裡,不破解一波怎麼對得起我破解渣渣的身份呢! 喲,還是支付寶購買的,直接9000大法,但是破解的時候沒

Android破解學習(十四)——【Unity3D】王牌大作戰破解

一、前言 今天帶來的是王牌大作戰的破解教程,遊戲下載的話,我是直接去TapTap官網下載的 支付寶內購破解用老套了,今天學點破解的新花樣吧!! 二、支付寶內購破解 支付寶的內購破解已經很熟悉了, 直接搜尋“9000”,之後找到程式碼,修改判斷條件即可,若不明白,請看我之前寫的部落格,Android破解