移动端跨平台开发一个跨平台的helloworld

Posted zhanghui_cuc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了移动端跨平台开发一个跨平台的helloworld相关的知识,希望对你有一定的参考价值。

在上一篇文章移动端音视频跨平台开发技术概论中,我们分析了跨平台开发的总体架构。今天我们实际动手,写一个helloworld项目,这个项目很简单,就是做一个简单的日志库,最终我们希望能在androidios手机上打印出一行hello world日志。

项目名字就叫simplest_crossplatfrom_helloworld,简称sch,对应下文代码中的变量、方法名称。

一、编写打印日志的核心代码流程

在Android平台上,我们利用jni的log.h进行日志的输出,在ios平台上,则直接利用printf函数,利用一个预编译宏SCH_PLATFORM_ANDROID来区分当前是否在Android平台上,相应地选择是否要包含jni的头文件,以及是否要调用__android_log_print来输出日志,所以最终的cpp代码如下

#include <string>

#define LOG_TAG "SCH"

#ifdef SCH_PLATFORM_ANDROID
#include "android/log.h"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);
#endif

void sch_log(const char* filename, int line, const char* format, ...) 
    char buf[1024];
    memset(buf, 0, 1024);

    va_list ap;
    va_start(ap, format);
    vsnprintf(buf, 1024, format, ap);
    va_end(ap);

    std::string log_string;
    log_string += "[" + std::string(filename) + "(" + std::to_string(line) + ")] ";
    log_string += buf;

#ifdef SCH_PLATFORM_ANDROID
    LOGI("%s", log_string.c_str());
#else
    printf("[%s] %s", LOG_TAG, log_string.c_str());
#endif

    fflush(stdout);
    fflush(stderr);

基于上面的实现,我们定义对应的头文件如下

#ifndef SIMPLEST_CROSSPLATFORM_HELLOWORLD_LOGGER_HPP
#define SIMPLEST_CROSSPLATFORM_HELLOWORLD_LOGGER_HPP

#ifdef __cplusplus
extern "C" 
#endif

#include <string.h>

#define filename(x) strrchr(x,'/') ? strrchr(x,'/') + 1 : x

void sch_log(const char* filename, int line, const char* format, ...);

#define SCH_LOGI(format, ...) sch_log(filename(__FILE__), __LINE__, format, ##__VA_ARGS__)

#ifdef __cplusplus

#endif

#endif //SIMPLEST_CROSSPLATFORM_HELLOWORLD_LOGGER_HPP

这样的话,调用者就只需要使用SCH_LOGI这个宏,即可输出日志。

未来我们还可以丰富日志级别,定义SCH_LOGE、SCH_LOGD等等。

二、编写编译工具链

根据上一篇文章的架构,我们还需要一套能够为双平台生成静态库、动态库的编译工具链,这里我们选择cmake来构建编译链。

在下面的CMakeList中,我们做了以下几件事

  1. 根据编译目标的不同,分别添加了SCH_PLATFORM_ANDROIDSCH_PLATFORM_IOS的宏定义
  2. 对于两个平台,我们都编译名为libschdemo.a的静态库
  3. 导出了Logger.hpp头文件
cmake_minimum_required(VERSION 3.7.1 FATAL_ERROR)

set(CMAKE_CXX_STANDARD 11)
project(schdemo C CXX)

message($PROJECT_SOURCE_DIR)

# used for local debug only
#set(CMAKE_SYSTEM_NAME "Android")
#set(TARGET_ABI "arm64-v8a")
#add_definitions(-D __ANDROID__)
##set(CMAKE_SYSTEM_NAME "iOS")
##set(TARGET_ABI "arm64")
##add_definitions(-D __APPLE__)
#set(jni_location "$ANDROID_NDK_HOME/sysroot/usr/include")
#message($jni_location)
#include_directories($jni_location)

if (CMAKE_SYSTEM_NAME MATCHES "Android")
    message("building for Android")
    add_definitions(-D SCH_PLATFORM_ANDROID)
    find_library(android_log_lib log)
elseif (CMAKE_SYSTEM_NAME MATCHES "iOS")
    message("building for iOS, arch $TARGET_ABI")
    add_definitions(-D SCH_PLATFORM_IOS)
endif()

if(CMAKE_ANDROID_NDK)
    list(APPEND PLATFORM_LIBS c++abi)
else()
    list(APPEND PLATFORM_LIBS $CMAKE_CXX_IMPLICIT_LINK_LIBRARIES)
endif()

add_library(schdemo
        STATIC
        Logger.cpp
        Logger.hpp)
set(header_files
        Logger.hpp)

if (CMAKE_SYSTEM_NAME MATCHES "Android")
    target_link_libraries(schdemo
            android
            $android_log_lib
            $PLATFORM_LIBS)
elseif (CMAKE_SYSTEM_NAME MATCHES "iOS")
    target_link_libraries(schdemo
            $PLATFORM_LIBS)
endif()

install(FILES $header_files DESTINATION include)
install(TARGETS schdemo
        LIBRARY DESTINATION lib  # 动态库安装路径
        ARCHIVE DESTINATION lib  # 静态库安装路径
        RUNTIME DESTINATION bin  # 可执行文件安装路径
        )

有了CMakeList之后,我们在分别编写两个平台对应的编译脚本,Android平台我们就用ndk提供的交叉编译工具链,ios平台我们利用https://github.com/leetal/ios-cmake现成的交叉编译工具链。

Android平台编译脚本

basepath=$(cd `dirname $0`; pwd)
echo "$basepath"

target="arm64-v8a"
if [ $# -gt 0 ];then
    target=$1
    echo "build target $target"
fi

if [ -d ./install/android/"$target" ];then
    rm -rf install/android/"$target"
fi

if [ -d ./android_build ];then
    rm -rf android_build
fi

mkdir android_build

cd android_build

$ANDROID_SDK_HOME/cmake/3.10.2.4988404/bin/cmake ../sch/ \\
    -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \\
    -DANDROID_ABI="$target" \\
    -DANDROID_PLATFORM=21 \\
    -DTARGET_ABI=$target \\
    -DCMAKE_INSTALL_PREFIX=$basepath/install/android/"$target"

make -j4
make install

ios平台编译脚本

这里的third_pary/ios-cmake路径就对应前面说的https://github.com/leetal/ios-cmake项目

basepath=$(cd `dirname $0`; pwd)
echo "$basepath"

target="arm64"
if [ $# -gt 0 ];then
    target=$1
    echo "build target $target"
fi

platform="OS64"
if [ "$target" = "i386" ];then
    platform="SIMULATOR"
elif [ "$target" = "x86_64" ];then
    platform="SIMULATOR64"
elif [ "$target" = "armv7" ];then
    platform="OS"
fi

if [ -d ./install/ios/"$target" ];then
    rm -rf install/ios/"$target"
fi

if [ -d ./ios_build ];then
    rm -rf ios_build
fi

mkdir ios_build

cd ios_build

cmake ../sch/ \\
-DCMAKE_TOOLCHAIN_FILE="$basepath"/third_party/ios-cmake/ios.toolchain.cmake \\
-DPLATFORM=$platform \\
-DARCHS=$target \\
-DTARGET_ABI=$target \\
-DENABLE_BITCODE=TRUE \\
-DCMAKE_INSTALL_PREFIX="$basepath"/install/ios/"$target"

make -j4
make install

双端编译脚本

有了各个平台自己的脚本后,为了方便编译,我们再写一个统一编译脚本,其中编译了Android arm64和armv7架构的静态库,编译了ios四个架构的库并利用lipo工具进行了大包,如下

./build_sch_android.sh
./build_sch_android.sh armeabi-v7a

./build_sch_ios.sh
./build_sch_ios.sh armv7
./build_sch_ios.sh i386
./build_sch_ios.sh x86_64
xcrun lipo -create install/ios/arm64/lib/libschdemo.a \\
        install/ios/armv7/lib/libschdemo.a \\
        install/ios/i386/lib/libschdemo.a \\
        install/ios/x86_64/lib/libschdemo.a -output install/ios/libschdemo_uni.a

到这里,我们的编译工具链就打造完毕了。

三、编写应用层API

对于Android平台,我们可以封装一个Java日志工具类,通过jni调用前面编译出来的libschdemo.a,来进行日志的打印,设计Java工具类接口如下

public class LogUtil 

    static 
        System.loadLibrary("c++_shared");
        System.loadLibrary("logJNI");
    

    public static void i(String msg) 
        nativelog(msg);
    

    private static native void nativelog(String msg);

其中nativelog对应的jni代码如下,非常简单

#include <jni.h>

#include "Logger.hpp"

extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhanghui_schdemo_LogUtil_nativelog(JNIEnv *env, jclass clazz, jstring msg) 
    const char* msg_str = env->GetStringUTFChars(msg, 0);
    SCH_LOGI("%s", msg_str);
    env->ReleaseStringUTFChars(msg, msg_str);

对于ios平台,因为当前这个hello world的case比较简单,我们选择直接引用libschdemo库然后进行调用,示例如下

#import "AppDelegate.h"
#include "Logger.hpp"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    // Override point for customization after application launch.
    SCH_LOGI("%s\\n", "Simplest Crossplatfrom Hello World!");
    return YES;

当然,更详细的demo使用代码就不在这里展示了,比较简单。
至此,跨平台Hello World项目就全部编写完毕,最终我们运行Android和ios平台的demo项目,都能看到如下的日志打印

03-20 20:28:26.661 22755 22755 I SCH     : [log_jni.cpp(13)]Simplest CrossPlatform Hello World!

项目完整代码扫描下方二维码,回复“跨平台”获取。


欢迎关注我的公众号灰度五十,分享各类音视频、移动开发知识,以及名企内推信息~

以上是关于移动端跨平台开发一个跨平台的helloworld的主要内容,如果未能解决你的问题,请参考以下文章

移动端跨平台开发一个跨平台的helloworld

为什么移动端跨平台开发不靠谱?

推荐一个专注于移动端技术研究领域的优质平台

推荐一个专注于移动端技术研究领域的优质平台

移动端跨平台开发的深度解析

译使用 Flutter 实现跨平台移动端开发