美文网首页
C++工程:一文看懂如何使用 C++ 开发 Android、iO

C++工程:一文看懂如何使用 C++ 开发 Android、iO

作者: ImWiki | 来源:发表于2021-02-21 12:02 被阅读0次

    C/C++是相对底层的语言,相比OC、Swift、Kotlin、Java等都要难,但是C/C++是Android和iOS都支持的语言,我们使用C++主要有一下几种原因:

    • 跨平台,一套代码多端使用;
    • 安全性,Java是极其容易被反编译的语言,如果把核心的代码改成C++可以有效提高安全性,但是在iOS意义就不大了;
    • 高性能,在多数的场景下这个优势并不明确,只有在一些特定的场景下才能发挥他的价值,比如音频、视频;
    • 调用C++库API,有部分的库只提供了C++版本,如果需要访问那么就要使用C++实现。

    项目基本要求

    • 一个工程实现Android和iOS工程师一起编码;
    • 可以添加依赖第三方C++库;
    • 可以脱离手机实现代码调试;
    • 可以使用手机实现代码调试;

    项目设计

    设计思想参考了 Flutter Plugin 和 protobuf 的工程结构,基于CMake 和 CocoaPods 实现,为大家提供一种多平台协同开发的思路,本篇文章不会把全部的代码贴出来,只会列出设计思想和关键的代码,详细代码请查看 源码

    目录结构

    ├── CMakeLists.txt
    ├── android
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           ├── AndroidManifest.xml
    │           ├── java/com/cross/Cross.java
    │           └── cpp
    │               ├── include
    │               ├── cross.cpp
    │               └── CMakeLists.txt
    ├── cross.podspec
    ├── ios
    │   └── Classes
    │       ├── Cross.h
    │       └── Cross.mm
    ├── src
    │   └── url_signature
    │       ├── include/url_signature.h
    │       ├── url_signature.cpp
    │       └── CMakeLists.txt
    ├── third_party
    │   ├── cxxurl
    │   │   ├── include
    │   │   ├── src
    │   │   └── CMakeLists.txt
    │   └── hash
    │       ├── include
    │       ├── src
    │       └── CMakeLists.txt
    ├── test
    │   ├── gtest
    │   │   ├── include
    │   │   ├── src
    │   │   └── CMakeLists.txt
    │   ├── main.cpp
    │   └── CMakeLists.txt
    ├── example 
    │   ├── android #省略
    │   └── ios #省略
    ├── build.gradle
    ├── gradle.properties
    ├── settings.gradle
    

    上面的目录结构主要分为:

    • Android代码(android):虽然C++是通用的语言,但是也有部分代码并不通用,这里主要是编写和NDK特定有关的代码,比如log,除了C++代码也会在这里编写JNI接口代码,提供给Java调用;
    • iOS代码(ios):和上面同理,也是编写和iOS有特定关联的代码,虽然.m改成.mm就可以调用C++代码,但是为了避免C++代码混入主项目,这里也要编写接口层代码,通过接口调用;
    • iOS库配置文件(cross.podspec):这个文件放到/ios目录才是比较合理,由于podspec不支持指定上一级的源文件,所以只能放到根目录;
    • 核心C++(src):这里是包含了我们编写的C++代码,这里也可以把不同功能的代码分为多个项目,实现代码隔离;
    • 测试代码(test):这里主要是包含了我们日常开发调试编写的测试代码,也可以包含单元测试代码;
    • 第三方库(third_party):这里是放第三方库,每一个库都有独立的文件夹和CMakeLists.txt,管理自身的头文件和代码;
    • 示例(example/android、example/ios)
    • Gradle脚本文件:build.gradle/gradle.properties/settings.gradle

    C++库设计

    无论是第三方库还是我们编写的代码,都使用同一的目录结构,都是一个库,这里以 jsoncpp 为例,每一个库都包含了CMakeLists.txt、include、src三部分,CMakeLists.txt是库定义,include是需要暴露的头文件,src是放实现源码。

    ├── CMakeLists.txt
    ├── include
    │   └── json
    │       ├── reader.h
    │       ├── value.h
    │       └── writer.h
    └── src
        ├── json_reader.cpp
        ├── json_value.cpp
        └── json_writer.cpp
    

    在这个项目当中,如果是第三方库,我会把实现代码放到src文件夹,如果是我们自己写的代码,我会把实现代码放到和CMakeLists.txt同一级别的目录。

    CMakeLists.txt 的作用主要是暴露当前库的头文件,定义需要参与编译的源码文件。

    # 设置cmake版本要求
    cmake_minimum_required(VERSION 3.10.2)
    # 定义库的名字
    project(jsoncpp)
    # 定义需要参与编译的源文件
    add_library(${PROJECT_NAME} src/json_reader.cpp src/json_value.cpp src/json_writer.cpp)
    # 定义需要暴露的头文件
    target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
    

    有了这个C++库的定义,接下来我们看看根目录的 CMakeLists.txt

    CMakeLists.txt(根目录)

    根目录的 CMakeLists.txt 是用来关联各个子项目,需要注意是,如果关联进来那么编译成Android的aar文件的都会把代码包含进来,所以我们要根据不同的场景编写脚本,比如在Android环境下需要包含android的C++代码,不要包含测试代码。

    cmake_minimum_required(VERSION 3.10.2)
    project(cpp-android-ios-example)
    set(CMAKE_CXX_STANDARD 17)
    add_subdirectory(third_party/hash)
    add_subdirectory(third_party/cxxurl)
    add_subdirectory(src/url_signature)
    if (ANDROID)
        add_subdirectory(android/src/main/cpp)
    else ()
        add_subdirectory(test/gtest)
        add_subdirectory(test)
    endif ()
    

    重点:android/build.gradle定义了 CMakeLists.txt 的路径,而这个路径必须指向根目录的 CMakeLists.txt。

    externalNativeBuild {
         cmake {
             path "../CMakeLists.txt" // 这里需要指向项目根目录的 CMakeLists.txt
             version "3.10.2"
       }
    }
    
    库依赖

    通过上面的方式把各个子项目关联起来后,那么我们就可以非常简单在一个子项目中引用另外一个子项目,这里以 src/url_signature/CMakeLists.txt 为例,我们需要在 url_signature 子项目添加 cxxurl 和 hash 的依赖。

    cmake_minimum_required(VERSION 3.10.2)
    set(CMAKE_CXX_STANDARD 14)
    project(url_signature)
    add_library(${PROJECT_NAME} url_signature.cpp)
    target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
    target_link_libraries(${PROJECT_NAME} cxxurl hash)
    

    Android

    以下是android的目录结构,这部分的代码主要是实现Java和C++的转换。

    ├── build.gradle
    ├── gradle.properties
    ├── settings.gradle
    └── android
        ├── build.gradle
        └── src/main
            ├── AndroidManifest.xml
            ├── cpp
            │   ├── CMakeLists.txt
            │   └── native-lib.cpp
            └── java/com/cross/Cross.java
    

    从上面的目录可以看到,项目的根目录有 build.gradle、gradle.properties和settings.gradle,之所以放在根目录是

    android/build.gradle
    apply plugin: 'com.android.library'
    android {
        compileSdkVersion 30
        buildToolsVersion "30.0.2"
        defaultConfig {
            minSdkVersion 16
            targetSdkVersion 30
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles "consumer-rules.pro"
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        externalNativeBuild {
            cmake {
                // 这里需要指向项目根目录的 CMakeLists.txt 文件
                path "../CMakeLists.txt"
                version "3.10.2"
            }
        }
    }
    dependencies {
    }
    
    android/src/main/cpp/CMakeLists.txt
    cmake_minimum_required(VERSION 3.10.2)
    project("cross")
    include_directories(export_include)
    add_library(${PROJECT_NAME} SHARED cross.cpp)
    find_library(log-lib log)
    target_link_libraries(${PROJECT_NAME} ${log-lib} url_signature)
    
    cross.cpp
    #include <jni.h>
    #include <string>
    #include "url_signature.h"
    
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_cross_Cross_signatureUrl(JNIEnv *env, jclass clazz, jstring url) {
        const char *str = env->GetStringUTFChars(url, JNI_FALSE);
        std::string result = SignatureUrl(str);
        return env->NewStringUTF(result.c_str());
    }
    
    Cross.java
    package com.cross;
    public class Cross {
        static {
            System.loadLibrary("cross");
        }
        public static native String signatureUrl(String url);
    }
    

    iOS

    由于 podspec 无法指定父级的文件,如果放在ios目录下,那么就无法关联src和third_party的源文件,所以就把 cross.podspec 放到工程的根目录。Cross 类主要就是负责对接OC和C++,作为统一的接口类。

    ├── cross.podspec
    ├── ios
    │   └── Classes
    │       ├── Cross.h
    │       └── Cross.mm
    
    cross.podspec
    Pod::Spec.new do |s|
      s.name             = 'cross'
      s.version          = '0.0.1'
      s.summary          = 'cross library'
      s.description      = 'Cross library'
      s.homepage         = 'http://example.com'
      s.license          = { :file => '../LICENSE' }
      s.author           = { 'Your Company' => 'email@example.com' }
      s.source           = { :path => '.' }
      # 设置源文件,切记不要把测试代码包含进来
      s.source_files = 'ios/Classes/**/*','third_party/**/*.{cc,cpp,h}','src/**/*.{cc,cpp,h}'
      # 暴露头文件,否则引用该spec的项目无法找到头文件
      s.public_header_files = 'ios/Classes/**/*.h','src/url_signature/include/*.h'
      s.platform = :ios, '8.0'
      # 必须配置HEADER_SEARCH_PATHS属性,是否会导致项目中C++找不到头文件
      s.xcconfig = {
            'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/third_party/cxxurl/include/" "${PODS_TARGET_SRCROOT}/third_party/hash/include/" "${PODS_TARGET_SRCROOT}/src/url_signature/include/"'
      }
    end
    
    Cross.h
    @interface Cross : NSObject
    - (NSString*)signatureUrl:(NSString *)url;
    @end
    
    Cross.mm
    #import "Cross.h"
    #include <string>
    #include <url_signature.h>
    
    @implementation Cross
    - (NSString*)signatureUrl:(NSString *)url{
        std::string str = [url UTF8String];
        std::string result = SignatureUrl(str);
        NSString *newUrl = [NSString stringWithUTF8String:result.c_str()];
        return newUrl;
    }
    @end
    

    示例设计

    Android
    ├── build.gradle
    ├── settings.gradle
    └── src
        └── main
            ├── AndroidManifest.xml
            ├── java/com/cross/example/MainActivity.java
            └── res
    

    示例就是一个普通的工程,唯一需要注意的是 settings.gradle

    rootProject.name = 'example'
    include ":cross"
    project(":cross").projectDir = new File("../../android")
    

    build.gradle

    buildscript {
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath "com.android.tools.build:gradle:4.1.2"
        }
    }
    apply plugin: 'com.android.application'
    android {
        ... 
    }
    dependencies {
        implementation project(path: ':cross')
    }
    
    iOS
    ├── Podfile
    ├── example
    │   ├── AppDelegate.h
    │   ├── AppDelegate.m
    │   ├── ViewController.h
    │   ├── ViewController.m
    │   └── main.m
    ├── example.xcodeproj
    

    Podfile

    target 'example' do
      use_frameworks!
        pod 'cross', :path => '../../'
    end
    
    如何编码?
    • 如果是Android开发者,建议使用 Android Studio 打开根目录,文件目录选择 Android 风格的,就可以在一个环境下同时编写 C++、Java、还有example的代码。
    • 如果是iOS也是同样打开example/ios项目,记得要先执行 pod install 哦。
    • 但多数情况下还是建议使用 vscode 或者 clion 打开 工程的根目录,在 test 目录下编写测试代码,脱离与平台关联进行开发调试,这样的效率更高。

    总结

    通常C++部分的代码不会和APP主工程的代码放到同一个仓库,而是独立开发,比如Android会打包成aar发布到maven仓库中,而iOS就会发布到内部的CocoaPods仓库,通过外部库的引入到主项目当中。

    源码:https://github.com/taoweiji/cpp-android-ios-example

    附加资料

    集成开发环境(IDE)
    • CLion:JetBrains出品,最好用的C++ IDE,对CMake支持非常友好,需要付费的,对开源社区有贡献的可以申请免费使用;
    • AppCode:JetBrains出品,支持Objective-C, Swift, C/C++语言,也是需要付费的;
    • Visual Studio Code:微软出品,支持多种平台,可以通过插件实现C++编码,也是一个非常不错的选择;
    • Xcode:苹果公司出品,对于iOS开发者非常友好,可以搭配CocoaPods一起开发C++;
    • Visual Studio:微软出品,仅支持Windows,功能非常强大;
    • Android Studio:用于Android开发的IDE,同样也可以编译C++代码,适用于Android开发者;
    C++常用库
    • 综合
    • json: jsoncppnlohmann/json 都是不错的选择。
    • fmt 是一个先进的文本格式库,具有现代语言的特征,缺点是占用过大。
    • 压缩:libzip2
    • 网络:Boost.Asio:用于网络和底层I/O编程的跨平台的C++库。
    • MD5/SHA1:hash-library
    • 调试、单元测试:CMake自带的 CTestgoogletest
    • 脚本、虚拟机:
      • V8:Google开发并维护的高性能 JavaScript 引擎;
      • J2V8:针对 V8 的 Java 绑定的JSI,可以在Android上使用;
      • JavaScriptCore:在 WebKit 中提供 JavaScript 引擎的开源框架,iOS7 后集成到了 iPhone 平台;
      • quickjs:支持多平台的小型JavaScript引擎,只有210KB大小;
      • Lua:一个小型的lua脚本引擎,可以用于做简单的逻辑运行和控制;
    • 序列化:protobuf 可以代替json/xml在不同的语言/设备之间传递对象序列化数据;
    • 音频视频:FFmpeg 用于音视频的处理;
    构建系统
    • cmake:是目前主流的构建系统,简单易用,也适合构建大型项目,常用的第三方库基本都支持cmake,可以非常简单添加第三方依赖,强烈推荐;
    • makefile:是最基本的构建系统
    • CocoaPods:是用于开发OC和Swift项目的构建系统,同样也支持C++,通常用于iOS应用开发;
    • ndk-build:Android 上一代的构建方式,配置文件为Android.mk,目前Android开发默认的构建方式已经变成了cmake;
    • bazel:Google开源的构建系统,TensorFlow和Flutter都是基于bazel构建,使用复杂,适合大型的项目;

    相关文章

      网友评论

          本文标题:C++工程:一文看懂如何使用 C++ 开发 Android、iO

          本文链接:https://www.haomeiwen.com/subject/vvczxltx.html