Android和iOS開發都支持C++開發,可以一套代碼多平臺使用。同時C++難以反編譯的特性也可以為Android開發帶來代碼的保密,另一native特性也可以提高代碼的運行效率。
一、為什么使用C/C++
二、開發工具介紹
盡管Android Studio可以同時編寫C++和Java代碼,寫完就可以編譯運行,但是對聯想和錯誤提示并不是非常友好,個人建議C++的整體代碼使用Visual Studio或Xcode編譯開發,聯想功能非常友好,編譯速度很快,調試也非常方便。
三、第一行代碼
1. 如何在Objective-C項目中使用C++;
在Objective-C使用C/C++非常簡單,僅僅需要把.m后綴的文件改成.mm即可使用C++,我們通常不會把.mm的文件寫到整個項目都有,而是設計一個接口,用來做兩個語言之間的橋梁,他們之間的交互僅僅在這個接口。
要點:String類型轉換
// Objective-C(NSString) -> C++(std::string)NSString * ocString = @"Hello World,OC";std::string cppString = [ocString UTF8String];std::cout<<cppString<<std::endl;// C++(std::string) -> Objective-C(NSString)std::string cppString2 = "Hello World,C++";NSString *ocString2= [NSString stringWithCString:cppString2.c_str() encoding:[NSString defaultCStringEncoding]];NSLog(@"%@",ocString2);
記得要include相關的文件
#include <stdio.h>
#include <iostream>
2.在普通的JAVA項目中使用JNI編程
由于我是在MAC下辦公,所以這里就介紹如何在MAC下進行JNI開發,在Windows平臺下的Virtual Studio也很簡單。
第一步:在Xcode下創建一個普通的C++項目


第二步:關聯JavaVM的Framework
路徑:
/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/

第三步:創建頭文件,用于和Java交互 cn_taoweiji_nativemodule_NativeDemo.h
#include <JavaVM/jni.h>#ifndef _Included_cn_taoweiji_nativemodule_NativeDemo#define _Included_cn_taoweiji_nativemodule_NativeDemo#ifdef __cplusplusextern "C" {#endif JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add (JNIEnv *, jclass, jint, jint); JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_say (JNIEnv *, jclass, jstring); JNIEXPORT jstring JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_getInfo (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava (JNIEnv *, jclass, jobject);#ifdef __cplusplus}#endif#endif第四步:創建實現 NativeDemo.cpp
#include "cn_taoweiji_nativemodule_NativeDemo.h"#include <string>JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint param1, jint param2){ jint result = param1 + param2; return result;}JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_say(JNIEnv *env, jclass, jstring param){ // std::string -> jstring const char *param_char = env->GetStringUTFChars(param, NULL); std::string str = param_char;}JNIEXPORT jstring JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_getInfo(JNIEnv *env, jclass){ // jstring -> std::string std::string str = "Hi,I am C++."; jstring result = env->NewStringUTF(str.c_str()); return result;}JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava(JNIEnv * env, jclass, jobject obj){ // 調用Java方法 jclass cls = env->FindClass("cn/taoweiji/nativemodule/NativeDemo"); //int subtract(int param1, int param2) -> (II)I jmethodID mid = env->GetMethodID(cls, "subtract", "(II)I"); int result = (int) env->CallIntMethod(obj, mid, 10, 2);// std::cout<<result<<std::endl; //常見類型轉換例子 //String getInfo(); //-> ()Ljava/lang/String; //PackageInfo getPackageInfo(String packageName, int flags); //-> (Ljava/lang/String;I)Landroid/content/pm/PackageInfo;;}第五步:編譯生成JNI文件,按 ⌘+B (Product -> Build).
編譯后文件

根據自己的電腦環境,查找編譯后的文件,我的路徑是
/Users/Wiki/Library/Developer/Xcode/DerivedData/DEMO_MAC_JNI-clxymnzifegyfaajsaattzgxqfbr/Build/Products/Debug/DEMO_MAC_JNI
第六步:編寫JNI接口
package cn.taoweiji.nativemodule;/** * 包名和類名稱一定要和前面的C++頭文件對應 * cn_taoweiji_nativemodule_NativeDemo.h */public class NativeDemo { public static native int add(int param1, int param2); public static native void say(String name); public static native String getInfo(); public static native void nativeToJava(NativeDemo nativeDemo); public int subtract(int param1, int param2) { System.out.println("NativeDemo:" + String.format("%s - %s = %s", param1, param2, param1 - param2)); return param1 - param2; }}第七步:調用C++
public class Main { static { System.load("/Users/Wiki/Library/Developer/Xcode/DerivedData/DEMO_MAC_JNI-clxymnzifegyfaajsaattzgxqfbr/Build/Products/Debug/DEMO_MAC_JNI"); } public static void main(String[] args) { System.out.println("Hello World!"); int result = NativeDemo.add(1, 2); System.out.println("1+2=" + String.valueOf(result)); NativeDemo.say("Hello,I am Java."); System.out.println("getInfo:" + NativeDemo.getInfo()); NativeDemo.nativeToJava(new NativeDemo()); }}3. 在ANDROID項目中使用NDK
Android的JNI開發,C++文件必須編寫在獨立的module里面,Java接口代碼可以編寫在app(module),也可以和C++放在同一個module,通過gradle關聯。詳細代碼請自行下載demo瀏覽
gradle配置(NativeModule)apply plugin: 'com.android.library'android { compileSdkVersion 23 buildToolsVersion "24.0.0 rc2" defaultConfig { minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { moduleName "joyrun" stl "stlport_static" ldLibs "log"http://用于解決__android_log_print abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //add -fexceptions to allow throw error //add -w to "format not a string literal and no format arguments [-Werror=format-security" cFlags "-w -fexceptions" } } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])}編寫JNI接口
// NativeDemo.javapackage cn.taoweiji.nativemodule;public class NativeDemo { public static native int add(int param1, int param2);}編寫C++接口代碼,JNI文件目錄默認是module/src/main/jni,可以通過gradle配置改變
// cn_taoweiji_nativemodule_NativeDemo.h#include <jni.h>#ifndef _Included_cn_taoweiji_nativemodule_NativeDemo#define _Included_cn_taoweiji_nativemodule_NativeDemo#ifdef __cplusplusextern "C" {#endif/* * Class: cn_taoweiji_nativemodule_NativeDemo * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint, jint);#ifdef __cplusplus}#endif#endif// NativeDemo.cpp#include "cn_taoweiji_nativemodule_NativeDemo.h"JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint param1, jint param2){ jint result = param1 + param2; return result;}調用
// 靜態加載static { System.loadLibrary("joyrun");}//調用int result = NativeDemo.add(1,2);Log.i("1+2=",String.valueOf(result));四、NDK開發要點
編譯文件分析
將NativeModule編譯后生成的aar文件后綴改成.zip解壓,可以發現里面有個jni文件,打開可以看到”armeabi”, “armeabi-v7a”, “x86”, “x86_64”, “arm64-v8a”等文件夾,再打開可以看到是以lib前綴的so格式文件,這就是編譯后的native層文件,我們平常引用的第三方庫(百度地圖)也是要添加這些文件到我們的libs文件夾,不同的名稱代表著不同平臺相關的編譯文件,市面上大多數的手機都是arm架構CPU,x86架構的手機幾乎沒人用(genymotion模擬器屬于x86平臺),所以我們通常發布APP不會考慮x86平臺,僅僅添加armeabi文件即可,但是在開發過程中建議也添加x86的so文件,以方便我們在模擬器上運行。
運行庫
Android平臺帶有一個微型的C運行庫支持庫,成為系統運行庫。該運行庫不支持一下特性:C標準庫、異常支持、RTTI支持。NDK提供了用于補充系統運行庫功能的一些額外的C++運行庫。
| C++運行庫 | C++異常支持 | C++RTTI | C++標準庫 |
|---|---|---|---|
| 系統庫 | No | No | No |
| GAbi++ | No | Yes | No |
| STLport | No | Yes | Yes |
| GNU STL | Yes | Yes | Yes |
1.STLportSTLport是一個開源的、多平臺的C標準庫實現。它提供一個C標準庫頭文件的完整集合以及對RTTI的支持。
2.GNU STLGNU標準C庫,也叫libstdc-v3,是Android NDK最全面的標準C運行庫。它是一個正在開發的、以實現ISO標準C庫為目標的開源項目。
gradle配置
// 生成so文件的名稱moduleName "joyrun"http:// 引入STL標準庫stl "stlport_static"http://gnustl_static//用于解決__android_log_printldLibs "log"abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //添加編譯的平臺//add -fexceptions to allow throw error//add -w to "format not a string literal and no format arguments [-Werror=format-security"cFlags "-w -fexceptions"
LOGCAT輸出
#include <android/log.h>#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "tag_joyrun", __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "tag_joyrun", __VA_ARGS__)LOGE("Hello Logcat");類型轉換
// std::string -> jstringstd::string str = "Hello World";jstring result = env->NewStringUTF(str.c_str());// jstring -> std::stringjstring param;const char *param_char = env->GetStringUTFChars(param, NULL);std::string str = param_char;// jboolean 兩個值 JNI_TRUE、JNI_FALSE
C++調用JAVA代碼
//Java
public static native void nativeToJava(NativeDemo nativeDemo);public int subtract(int param1, int param2) {Log.e("NativeDemo", String.format("%s - %s = %s", param1, param2, param1 - param2));return param1 - param2;}//C++
JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava(JNIEnv * env, jclass, jobject obj){ // 調用Java方法 jclass cls = env->FindClass("cn/taoweiji/nativemodule/NativeDemo"); jmethodID mid = env->GetMethodID(cls, "subtract", "(II)I"); int result = (int) env->CallIntMethod(obj, mid, 10, 2); //常見類型轉換例子 //String getInfo(); //-> ()Ljava/lang/String; //PackageInfo getPackageInfo(String packageName, int flags); //-> (Ljava/lang/String;I)Landroid/content/pm/PackageInfo;;}一鍵生成從JAVA到C++接口代碼腳本
文件:autojavah.sh
#!/bin/shexport ProjectPath=$(cd "../$(dirname "$1")"; pwd)export TargetClassName="co.runner.app.jni.NativeDemo"export SourceFile="${ProjectPath}/app/src/main/java"export TargetPath="${ProjectPath}/jni-joyrun/src/main/jni"cd "${SourceFile}"javah -d ${TargetPath} -classpath "${SourceFile}" "${TargetClassName}"echo -d ${TargetPath} -classpath "${SourceFile}" "${TargetClassName}"五、C++面向對象及標準庫入門
C++類定義
// Demo.hpp#ifndef Demo_hpp#define Demo_hpp#include <stdio.h>#include <string>class Demo{public: std::string name; int age = 0; void say(); static int add(int param1,int param2) { return param1 + param2; }};#endif /* Demo_hpp */類方法的實現
// Demo.cpp#include "Demo.hpp"#include <iostream>void Demo::say(){ std::cout<<"name = "<<name<<",age = "<<age<<std::endl;}對象創建及訪問對象的成員
//對象創建Demo d1;Demo * d2 = new Demo;// 運算符訪問d1.say();// 指針訪問d2->say();//靜態函數訪問int result = Demo::add(1,2);std::cout<<"1 + 2 = "<<result<<std::endl;
LIST鏈表
//include相關文件#include <stdio.h>#include <iostream>#include <list>#include "Demo.hpp"http:// 鏈表定義std::list<Demo> * demos = new std::list<Demo>;Demo * demo = new Demo;demo->name = "Wiki";demo->age = 24;// 在后面插入數據demos->push_back(*demo);demo = new Demo;demo->name = "Wiki2";demo->age = 25;// 在前面插入數據demos->push_front(*demo);// 順序鏈表遍歷for (std::list<Demo>::iterator iter = demos->begin(); iter != demos->end(); ++iter) { iter->say();}// 反順序鏈表遍歷for (std::list<Demo>::reverse_iterator iter = demos->rbegin(); iter != demos->rend(); ++iter) { iter->say();}// 獲取指定位置元素std::list<Demo>::iterator iter = demos->begin();advance(iter, 1);iter->say();指針、引用和值
在C++中,函數可以用許多不同的方法傳遞參數,比如通過指針、引用或是直接傳值。
// 通過指針void handle1(Demo *p);// 通過引用void handle1(Demo& p);// 通過值void handle1(Demo *demo);
堆和棧的理解
棧(stack):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧;
堆(heap): 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似于鏈表。
Demo d1;//棧Demo * d2 = new Demo;//堆char c; //棧上分配char *p = new char[3]; //堆上分配,將地址賦給了p;
垃圾回收
new和C++ delete運算符用于動態分配和撤銷內存的運算符,自己new出來的對象就要通過delete釋放內存。
delete demos;
六、開發中常用的方法及注意事項(坑)
相關源代碼:
Android: https://github.com/taoweiji/DEMO_NDK
Objective-C: https://github.com/taoweiji/DEMO_CPP_OC
MAC-JNI: https://github.com/taoweiji/DEMO_MAC_JNI
以上就是C/C++ 在Android和java,Objective-C 三大平臺下開發應用的資料整理,后續繼續整理相關文章,謝謝大家對本站的支持!
新聞熱點
疑難解答