zoukankan      html  css  js  c++  java
  • 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"
            }
        }

    看下CMakeLists.txt文件,之前有一篇关于Android Studio cmake编译ffmpeg的文章也做了讲解。

    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<chenjianneng1117@gmail.com>");
    
        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
    执行看下进程
    shell@jflte:/ $ 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信息:

    shell@jflte:/ $ logcat -s Daemon
    logcat -s Daemon
    --------- beginning of system
    --------- beginning of main
    I/Daemon  (14695): Copyright (c) 2018, eastmoon<chenjianneng1117@gmail.com>
    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++代码。

    github代码:https://github.com/eastmoon1117/StudyTestCase/tree/master/JNIDaemon

  • 相关阅读:
    Could't creat any visual c# project in visual studio 2008 beta 2
    最近有点时间赶紧学习,好久没有写东西了
    三亚 渡假 不多, 以后要多去去 芒果很好吃
    windows8. 微软要颠覆PC传统模式
    IM
    windows8 预览版本 安装 VC6 + sp6
    windows 8 截图部分
    分享一个常识 如果你也和VC 打交道
    有时间看看, 热爱技术,不沉迷于技术
    LIVE555 Media Server
  • 原文地址:https://www.cnblogs.com/DreamRecorder/p/9227443.html
Copyright © 2011-2022 走看看