1. 程式人生 > >Mac下使用Eclipse實現Android中呼叫C/C++(NDK)基礎詳細教程

Mac下使用Eclipse實現Android中呼叫C/C++(NDK)基礎詳細教程

寫於2014年那個辦公室停電導致熱爆了汗流浹背的夏天。

需求

NDK是由谷歌娘提供的,某種意義上就是可以讓android使用c開發的第“三”方sdk,所以,正常來說eclipse是沒有配置這個東西的,當然如我所云,我只考慮用最小的工程成本(較少的時間保證一定質量)來實現我的目標,所有我使用的是由谷歌提供的標準的ADT,下載完安裝後可以自動完成android開發的基本配置,也就是可以直接拿來寫HelloAndroid的開發工具,當然,普通的android開發也不是我這裡討論的問題,所以以下涉及到java下完成android開發的問題我預設所有人都能理解我說的東西,不會做解釋,我的解釋會放在如何使用NDK上(囉嗦的我啊願萌萌的油乎乎永安你的靈魂,普天齊明!我受到攻擊hp-250,我觸發被動天賦厚臉皮hp+2500)。

另外由於我配置完也過了有大半個月了,所以有些細節可能不會記得很清楚,也可能會遺漏一些細節,如果我日後還活著而且想起來了,我發四一定會回來修改補充這篇部落格。或者在配置過程中遇到有什麼問題可以留言補充,有緣的話我一定會看到的。

Android嚴格來說是linux的一個分支,換個角度講,android就是一個被修改過的linux系統,linux系統執行直接用c寫的東西當然是沒問題的,這也是為什麼android可以允許ndk的存在的一個核心原因。而偶們平時使用的c開發版本里邊,大方向上可能可以算作有兩種,一種是GUNC,一種是個windows用的C(原諒無知的我又忘了叫什麼了,要不簡稱wc吧),這名字不是太重點,重點是這兩種c雖然大同小異,但是卻會有嚴重的跨平臺問題,gunc是個unix系統使用為主,也就是在unix環境下編譯gunc是個事半功倍的事情,wc的問題同。linux是unix的分支,所以linux下也是使用gunc的,換個角度說,我這段想表達的問題很簡單,windows下做NDK會多很多麻煩,根據我的原則以及我的開發環境考慮,我勇敢的放棄了在windows下的努力(顯然我是被折騰的不行了才放棄的,做專案跟做研究畢竟有差別,專案工程有工期的鴨梨,使用一個更穩定更可控的平臺是更好的選擇),我使用的開發環境是unix的另外一個分支,mac,也就是蘋果的作業系統(顯然全名太長了我有限的腦容量是記不住的,打個mac意思一下咯)。換個角度說,android和mac都是屬於unix系統的大類中,就像香港人和深圳人要交流總比深圳人跟加州人要交流容易些一樣,mac下做android的工作也比在windows下做要容易一些(說白了就是給android做windows開發工具的人比較少——)。換個角度,linux下做ndk也會更容易些,可能某些細節不完全一樣,但大體應該是接近的。

綜述

嗯嗯,上一章其實沒什麼好看的,可以跳過,這章開始正式討論使用ndk過程中遇到(或者說需要解決的問題)。

巨集觀上偶們先梳理一下邏輯,整個過程中偶們需要解決哪些問題,後續章節會逐個講述這些問題的具體解決方案(有些方案並不唯一,有興趣可以自己再研究)。

讓開發工具Eclipse知道NDK已經存在。

呼叫ndk首先要解決的就是ndk在哪的問題?ndk不是憑空出現的,可以到google提供ndk的地方去下載,由於網路的原因可能有些人沒法下載到,可以百度到一些第三方的地方下載,注意使用的作業系統是mac,64位,版本肯定要最新的啦,舊的版本可能會出現有些功能不支援的問題。

下載好ndk以後當然是要讓開發工具,也就是eclipse君知道有這個東西已經存在在電腦深深的腦海裡,TA的硬盤裡,TA的記憶體裡,TA的廢紙簍裡。

配置ndk的開發設定(引數)。

eclipse知道怎麼從垃圾箱裡找出ndk以後,就需要配置ndk開發環境了,android呼叫ndk的方式一般是通過呼叫動態連結庫的方式完成的,所以所謂配置ndk開發設定,實質上是為了配置編譯器如何編譯寫好的c程式碼。說真的這塊很多麻煩事,我當時是被弄得很崩潰。

實現java和c通訊的介面(以下稱ndk介面)。

顯然java無法那麼簡單就可以呼叫c的函式,所以需要使用ndk提供的中間介面,這個介面一式兩份,一份是用java編寫的,另一份使用C編寫。這兩份協議內容實質是一樣的,雖然兩者用的語言不一樣,就像一些國際商業合同可能會一份是中文,一份是英文一樣。

ndk提供的與標準c類似jint、jstring等“c的資料型別”(就是說,這些資料型別雖然姓“j”,但它還是一個c的資料型別。就像金剛石沒有金屬,也跟某隻猩猩沒有關係;鉛筆也不是鉛做的一樣。“j”只是描述了這個資料型別具有某些特殊性質),通過這些資料型別可以通過ndk與java中的對應int型別、string型別實現自由轉換。

編譯出動態連結庫(顯然靜態連結庫也是可以的,但還是那句話,一切根據需求來)。

由於動態連結庫的字尾是*.so,所以下文統稱so檔案(體諒下我滑鼠手鍵盤爪多年打字不易)。當你的程式碼寫完以後,就需要開始編譯了,編譯有兩種結果,All In or Nothing(成功或者失敗=。=)。成功的話會生成一個so檔案,這個so檔案可以認為是由以下三個部分編譯而成的(你寫的程式碼,你呼叫的庫,ndk的介面),雖然編譯so檔案並不需要ndk介面的java部分,但如果沒有這個部分,這個so檔案是無法正常使用的,就像一輛車,沒有鑰匙是沒法開的(神馬,你會撬鎖,那確實可以用,就是成本略高)使用這個so檔案配合ndk介面的java部分就可以形成一個完成的NDK動態連結庫了。

將動態連結庫新增入已有的android專案中。

到這一步就已經很接近成功了,把動態連結庫加入已有的專案,留意之前準備好的ndk介面中的java部分,在正常的java函式中呼叫它吧,相信自己,你可以的。

除錯和……

除錯神馬的,就不多說了,大夥都懂的,使用NDK可以考慮配合TDD的開發模式,原理不多說,顯然會跑題。

基本的配置

就像所說的第一個問題,先下載好ndk的開發包,解壓縮,記下儲存的路徑,例如
“/Applications/adt-bundle-mac-x86_64-20140321/android-ndk-r9d/”。

  1. 開啟你的eclipse(也就是我這裡的ADT),在ADT—Preferences-Android-NDK裡邊把這個路徑設定一下,這樣ADT就知道你的NDK在哪裡了。
    這裡寫圖片描述
    這裡寫圖片描述
  2. 假設現在新建了一個Android的專案,或者已經有了一個Android的專案,“HelloNDK”。
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
  3. 在專案上點選右鍵,選擇Android Tools—Add Native Support…
  4. 設定將建立的第一個so檔案的名字,所有的so檔案都是而且必須以lib***.so的格式命名,這樣APK在執行的時候才能正確載入,點選Finish。
    這裡寫圖片描述
  5. 這個時候你會發現你的Project裡邊多出了一個jni的資料夾,木有錯,介個資料夾就是給你放*.c*.cpp*.h這些檔案的地方啦。裡邊預設生成了兩個檔案,一個”IHateNDK.cpp”,一個“Android.mk”。注意,第一個檔案不是必須的,裡邊什麼都沒有,可以自己另外建立,第二個檔案才是必須存在的,關於cpp、mk檔案的說明,後邊會有進一步的介紹,本章只討論配置的過程。
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
  6. 點選Project—Build All,這樣就能完成對檔案的編譯了
    這裡寫圖片描述
  7. Build的結果會輸出在Console窗口裡邊
    這裡寫圖片描述
  8. 編譯成功的SO檔案會儲存在專案工程的libs\armeabi\目錄下。
  9. 到這一步為止,編譯一個新的NDK動態連結庫的流程就走完了,換個角度講,剩下的就是如何呼叫以及如何完成程式碼的部分了。

java與c++之間的通訊

顯然,java的“編譯器”是不認識C的東西滴,同理,C滴東西也不認識java是神馬,所以偶們需要通過一些介面協議來完成這樣一部分,也就是實現讓java能呼叫C的函式,並正確地把java的物件傳給C,同時C能正確的獲得並解析Java傳過來的物件,並正確地把結果轉換為java的物件傳回去。

java的物件可以看做兩種,一種是如int、double、string這樣的值物件,另一種是自己建立的繼承自object的自定義物件(以下成為object物件)。事實上兩種物件都可以傳遞給C進行處理,但顯然後一種由於是自定義的會更復雜,作為進階內容,我就不囉嗦了,這裡就囉嗦一下普通的數值型別,以下以String型別在java與C的通訊為例,其他的數值傳遞問題可以舉一反三,或者自己百度=。=

協議的Java部分。

假設偶們現在要做的事情是輸入一個字串,然後返回這個字串的長度,偶們首先實現一個java版的。新建一個叫做NdkForJava的類,package com.hellondk;類定義如下:

public class NdkForJava
{
    public int SizeOfString(String str)
    {
        return str.length();
    }
}

這個函式顯然不是很難,但裡邊包含了這些關鍵點:首先,實現了String引數的傳入;再者,對傳入的String引數進行了計算;最後,返回了一個int引數。這幾點已經可以完整的應付最基本的NDK呼叫問題了。

那這個函式的NDK協議應該怎麼寫呢?其實很簡單,將上一個函式的主體部分刪掉(也就是別寫主體,光起名就好),再增加一個關鍵字native即可:

public native int SizeOfString(String str);

這裡有幾個點跟java的普通函式寫法有些不同,首先,增加了關鍵字native,也就是NDK中的N,該關鍵字聲明瞭這個函式的實現交由NDK完成,然後,省略了函式主體,因為函式主體是用C完成的嘛,要在這寫了就能用,還要java幹嘛=。=

好,就醬,java部分的通訊協議就醬就可以了。

協議的C部分。

先回到之前建立的“IHateNDK.cpp”中,新增第一行語句,新增一個NDK專用的標頭檔案jni.h,裡邊包含了java與c實現通訊的核心函式及物件,要完成通訊,這個部分是必須有的,也是ndk專用的一個,標準C(顯然我介裡說的是GUN-C)沒有的標頭檔案。

完成了include以後,根據java協議的情況,宣告一個函式如下:

extern "C"
{
    JNIEXPORT jint Java_com_hellondk_NdkForJava_SizeOfString(JNIEnv* env, jobject thiz, jstring str);
}

這裡有很多關鍵點:

函式名

顯然這個函式名隱含天地大道,好像又符合某種天地之理,怎麼看和都好像資訊量很大的樣子,似乎跟java的協議有某種關聯(呸,瞎子都看得出來啦,再囉嗦板磚伺候;啊,這要靠領悟的喂)。

C協議中的函式名為了跟java協議中的函式名對應,所以命名的格式(區分大小寫)為Java_com_包名1_包名2_……包名n類名_函式名。

NDK使用了“反射”的技術完成了通過java呼叫C函式的功能,所以這個名字非常重要,千萬不能寫錯,寫錯的話編譯器是無法正常載入NDK中的函式的。

關鍵字extern

extern "C"
{
    //...
}

C++語言在編譯的時候為了解決函式的多型問題,會將函式名和引數聯合起來生成一箇中間的函式名稱,而C語言則不會,因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間函式名。

前邊偶們知道了NDK函式的名字灰常重要,就可以理解為什麼這裡要使用extern這個關鍵字了,正如百科所說,為了防止反射被破壞,為了守護函式名的平衡,貫徹愛與正義的邪惡……咳咳跑題了……雖然我一直用C來稱呼,但就像本po開篇所說,其實這個C是包含了CPP的含義,因為某個懶貨才沒寫那麼清楚。

關鍵字JNIEXPORT。

顧名思義,這是個port,拆解開來應該是Java Native Interface Extra Port(顧名思義尼槑啊誰特麼能思的出那麼複雜的解釋)。好好好,這個關鍵字的功能就是告訴編譯器,別看這個函式名字長的搓,引數也莫名其妙,這可是是在java那邊掛了號的,別把TA與普通的C函式等同視之。

常用引數。

可以看到以前常見的int、string型別前邊都多了一個噁心難看的j,像這樣的jint、jstring就是java的值型別在c函式中的定義,NDK提供了一系列的jxxx用來定義各種資料型別,參考對應如下:
這裡寫圖片描述
這裡寫圖片描述
對於特殊的陣列型別,則可以通過jxxxArray來傳遞。

注意,這個圖樣圖森破的資料型別基本就拿來完成從java資料到C資料或者C資料到java資料的轉換的中間型別就好了,最好別哪來直接做運算,有時候會產生很多很奇怪的結果(例如記憶體洩漏)。

所以原來的java函式返回值是int,這裡的返回值就改成了jint,原來的java函式引數是String,所以這裡就改成了jstring。嗯,麼麼噠(麼麼噠你槑啊,沒發現多出了兩個引數啊,那個thiz是什麼來的啊拼的辣麼奇葩的是日語還是韓語啊)。

關於這個嘛,今天天氣好像很好的樣子。

好,這個問題提的很好,能提出這個問題說明童鞋你的數學已經有一定境界了,都快超過幼兒園的小盆友了,灰常好灰常好。

好好言歸正傳,所有的ndk下的jniexport協議,也就是說已經在java那邊掛上號的函式,天生自帶天賦,第一個引數必須是JNIEnv型別,第二個引數必須是jobject型別。也就是說,即便java那邊的native函式沒有一個引數,這邊對應的jniexport函式也要有這兩個引數。

JNIEnv指標是JVM建立的,用於Native的c/c++方法操縱Java執行棧中的資料,比如Java Class, Java Method等。JNIEnv中定義了一組函式指標,c/c++ Native程式是通過這些函式指標操縱Java資料。這樣設計的好處是:你的c/c++ 程式不需要依賴任何函式庫,或者DLL。由於JVM可能由不同的廠商實現,不同廠商有自己不同的JNI實現,如果要求這些廠商暴露約定好的一些標頭檔案和庫,這不是靈活的設計。
轉載自別人的部落格

thiz指代的是呼叫這個JNI函式的Java物件,有點類似於C++中的this指標。但因為在這裡是特殊指定java的物件,所以與一般的this做了一個區分,使用了thiz。

【至於為什麼會出現這個東西,其實可以追溯到java是一個完全面向物件的開發語言,而C卻是一個函式式面向過程的語言,但由於這東東以講又是進階內容,其實就是如果面向物件開發學的比較紮實或者做的比較多的話很好理解,而且不理解也不怎麼妨礙基本使用,所以就不詳敘了】

java資料與c資料之間的轉換

由於java部分才是Android的主體,所以整個流程都是由java部分的程式碼驅動的,一般來說ndk只用於完成計算的部分,所以偶們可以把java看作傳送方,ndk看作接收方,java傳送的資料需要通過ndk進行一些轉換,才能交給C的部分進行進一步的計算,所以資料從接受後的操作方式來說可以歸納為三類(個人意見),以下對它們的基本操作進行分類說明:

綜述

有一些操作是對所有的這些型別都有效的,首先要考慮的一個問題就是記憶體的管理分配問題,當java部分宣告並給一個物件分配了記憶體空間以後,把這個物件的引用引數傳遞給ndk之後,ndk就需要面臨一種選擇,即,之後使用C對這些引數進行操作的時候,是直接對這個物件的記憶體本身進行操作呢,還是將該物件的記憶體拷貝一份,再對拷貝進行操作。

這兩種方案各有利弊,直接操作的話,有可能會導致記憶體洩漏、記憶體覆寫等不容易控制的後果,需要在開發的時候花費更多的注意力去關注這部分物件(記憶體)的生命週期。拷貝法簡單直接,而且由於是在拷貝上執行操作,所以可以不用擔心對java部分產生影響,只要維護好自己的生命週期即可。

再者,在實際開發的過程中,特別是涉及到陣列等線性表或者非線性表的操作時,如果使用自動變數,我遇到過很多次同樣的程式碼,由於自動釋放的時機不同,導致有些手機可以正常執行,有些手機無法正常執行的情況,這也是NDK技術不是非常成熟的表現,為了避免這個問題,建議在使用的時候,儘可能手動分配和管理C部分的記憶體。

標準的int、boolean、byte等值型別;

由於剛剛宣告的函式SizeOfString傳遞的引數String有特殊的使用方式,我下邊會專門獨立拿出來說說,這邊為了展示標準值型別的傳遞,偶們先做這麼一個函式,完成對輸入變數的加法運算,這個函式的java部分如下:

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

然後根據偶們之前提到的NDK部分的命名原則,偶們可以寫出它的ndk形式,注意大小寫噢:

JNIEXPORT jint Java_com_hellondk_NdkForJava_Plus(JNIEnv* env, jobject thiz, jint a, jint b);

然後偶們現在在函式主體中就得到了一個jint的資料a,和另一個jint的資料b。現在第一步偶們要做的事情是把這個jint的資料轉換為int型別,方便偶們做進一步的處理。從jin.h的標頭檔案中偶們可以找到如下的定義:
這裡寫圖片描述
以jint為例,如果再追溯的話可以看到:

typedef __int32_t int32_t;

然後是:

typedef int __int32_t;

如果傳入的是這類資料型別,毫無疑問傳入的將會是一個值參,所以這個時候你可以放心的對傳入的引數進行各種操作,因為你的操作都是直接發生在拷貝的副本上的,如果沒有意外的話,這個副本會隨著函式的結束而被釋放(考慮到使用NDK的童鞋肯定要有C的背景,我這裡可能會借用一些C的術語來描述某些JAVA沒有的狀態,相信C基礎紮實的您一定可以明白我想表達的意思=。=好吧就是我詞窮的不知道該怎麼說)。其實跟java中對int引數的處理一下,無論你在函式對對傳入的int陣列怎麼加減乘除,主呼叫函式中的傳入引數並不會發生改變。

簡單的說,jint其實是int資料別名。所以Plus函式的某種寫法可以是:

JNIEXPORT jint Java_com_hellondk_NdkForJava_Plus(JNIEnv* env, jobject thiz, jint a, jint b)
{
    int pA = a;
    int pB = b;
    return pA + pB;
}

或者是:

JNIEXPORT jint Java_com_hellondk_NdkForJava_Plus(JNIEnv* env, jobject thiz, jint a, jint b)
{
    return a+ b
}

其他的上圖列出的資料型別都可以這樣用,那麼為什麼還要弄個jint出來呢?因為下邊有陣列的問題需要解決。

標準的int、boolean、byte等值型別的陣列型別,也就是int[]、boolean[]、byte[]等;

本節參考
這一塊會比較複雜,偶們都知道,java預設的陣列提供了很多額外的功能,例如說你可以直接訪問陣列的長度。但是,對應的C的陣列是沒有這些資訊的,在NDK這邊獲得的最終將是一個對應資料型別的指標(甚至可以是jobject型別),就像C對陣列的處理一個,你得到的只是陣列第一個資料所在的記憶體地址,後邊的訪問要靠索引器(實際上是記憶體地址的平移)來進行訪問了。

偶們先來看看怎麼獲得陣列的第一個索引的指標,首先先創造一個進階的Plus函式,該函式
完成了把陣列a和陣列b中的所有數值相加並返回的功能,偶們先定義它的Java部分:

public native int Plus(int[]a, int[]b);

然後是NDK部分

JNIEXPORT jint Java_com_hellondk_NdkForJava_Plus(JNIEnv* env, jobject thiz, jintArray a, jintArray b);

好,第一步,偶們要決定是否建立一份該陣列的拷貝,記得前邊說的麼,建立或者不建立各有各的優點,所以偶們可以定義一個引數來標記它:

jboolean ifCopy = JNI_FALSE;//JNI_TRUE;

可選的結果有兩個,JNI_FALSE和JNI_TRUE,其實這是jni提供的巨集定義,實際上就是常數0和常數1,按照避免“魔數”的原則,我更願意這樣來寫。顧名思義,False的時候,不會對傳入的陣列進行拷貝,而是直接在原陣列的記憶體上進行操作;True的時候會進行拷貝,在拷貝的陣列上進行操作。

然後,一般來說偶們還需要獲得陣列的大小,要不然很可能會發生索引越界的情況:

int lengthOfArrayA = env->GetArrayLength(a);
int lengthOfArrayB = env->GetArrayLength(b);

之後,偶們開始考慮怎麼獲得陣列,就像偶們所熟知的那樣,C下邊的陣列是一個指標:

int* arrayA = env->GetIntArrayElements(a,&ifCopy);
int* arrayB = env->GetIntArrayElements(b,&ifCopy);

這裡要留心的是,GetIntArrayElements函式的第二個引數是一個執行jboolean型別的指標,所以偶們這裡需要對前邊宣告的ifCopy增加一個&符號,以傳入正確的引數。

OK,現在陣列有了,陣列的長度也有了,偶們可以完成下一步的相加的工作了:

int sum = 0;
for(int i = 0; i < lengthOfArrayA; i ++)
    sum += arrayA[i];

for(int i = 0; i < lengthOfArrayB; i ++)
    sum += arrayB[i];

這一塊是標準的C++寫法,不多囉嗦了。按照一般的邏輯,下一步應該直接用return語句把結果返回,這個函式就結束了。

不過很遺憾,這樣做的話,在有些手機上會出現記憶體洩露的問題,因為在執行env->GetIntArrayElements函式的時候,無論傳入的ifCopy是True還是False,返回的陣列指標(在本例中就是arrayA和arrayB啦)所指向的那一片記憶體(沒錯,是那一片,整一個數組所有的記憶體,而不僅僅是索引為0的位置的記憶體)都會被系統鎖定,防止被java自動回收,這個鎖定是需要手動解除的,所以在執行完上述的計算過程以後,偶們還需要這樣解除被鎖定的記憶體,否則會產生記憶體洩漏(根據我用過的這麼多開發機,確實有些機器不會在這裡發生錯誤,但為了儘可能的相容更多機型,手動解鎖還是必須的步驟)。

在這裡偶們先來看函式原型:

void ReleaseIntArrayElements(jintArray array, jint* elemts, jint mode);

引數array就是這個陣列來源的jxxxArray物件,elems就是剛才使用GetXXXElements函式獲得的陣列指標,mode是由jni.h定義的三種模式,其中有:

#define JNI_COMMIT 1 /* copy content, do not free buffer */
#define JNI_ABORT 2 /* free buffer w/o copying back */

另外,還可以取值為0,此時表示在更新陣列元素後釋放elems緩衝器;取JNI_COMMIT的時候,表示在更新陣列元素後不釋放elems緩衝器 ;取JNI_ABORT的時候,表示不更新陣列元素釋放elems緩衝器。一般來說,偶們取0。
所以在本例中,偶們需要執行:

env->ReleaseIntArrayElements(a,arrayA,0);
env->ReleaseIntArrayElements(b,arrayB,0);

最後完成return的工作,合起來偶們得到的函式可以是這樣的:

JNIEXPORT jint Java_com_hellondk_NdkForJava_Plus(JNIEnv* env, jobject thiz, jintArray a, jintArray b)
{
    jboolean ifCopy = JNI_FALSE;//JNI_TRUE;

    int lengthOfArrayA = env->GetArrayLength(a);
    int lengthOfArrayB = env->GetArrayLength(b);

    int* arrayA = env->GetIntArrayElements(a,&ifCopy);
    int* arrayB = env->GetIntArrayElements(b,&ifCopy);

    int sum = 0;
    for(int i = 0; i < lengthOfArrayA; i ++)
        sum += arrayA[i];

    for(int i = 0; i < lengthOfArrayB; i ++)
        sum += arrayB[i];

    env->ReleaseIntArrayElements(a,arrayA,0);
    env->ReleaseIntArrayElements(b,arrayB,0);

    return sum;
}

啊哈,好像又解決了一個問題耶,休息,休息一會~

(´Д`)好吧其實在陣列這塊還有一個問題,木有錯,上邊只討論瞭如何處理傳入的陣列,但是如果有童鞋想傳出一個數組該腫麼辦?

介是一個複雜的問題,偶們,呃,偶們還是來聊聊天氣吧。

好,為了說明這個問題,我先建立一個新的函式,它的功能是返回一個排序從1到100的陣列,系不繫很激動,那TA的java和ndk部分應該是什麼樣的呢?

快速解決一下Java的部分:

public native int[] ArrayFrom1To100();

快速的解決一下C的部分,弄個數組出來。

表示我懶筋煩了,於是TA決定就假設存在一個int* resultArray的陣列已經弄好了,這個陣列的長度是100,就等傳出去。

JNIEXPORT jint Java_com_hellondk_NdkForJava_ArrayFrom1To100(JNIEnv* env, jobject thiz, jintArray a, jintArray b)
{
    int length = 100;
    int* resultArray = new int[length];
//自己給陣列賦值完成1到100的壯舉
}

為了把資料傳出去,偶們必須先建立一個jintArray:

jintArray result = env->NewIntArray(length);

這時偶們需要使用SetIntArrayRegion函式,偶們首先來看看函式原型:

void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf);

這個函式的功能就是快速把一個int陣列的值拷貝到一個jintArray裡邊,其中要求建立jintArray的時候,這個jintArray的長度不能小於被拷貝的陣列的長度(也就是len-start),否則就會出現錯誤。array表示準備被賦值的陣列,也是偶們打算return出去的陣列;start指從被拷貝陣列中開始拷貝的起始位置(根據題目需求,這裡我會從0開始拷貝,但不意味著不能從18,36,甚至40開始拷,但這樣會不符合這個函式的需求);len指從start位置開始,連續拷貝的資料的數量(同樣根據題目要求,這裡偶們需要拷貝100個,但同樣會違背偶們函式的需求);buf就是偶們在C環境下完成的陣列的索引0的指標。因此偶們應該執行的語句是:

env->SetIntArrayRegion(result,0,length,resultArray);

最後,偶們處理一下陣列的記憶體管理問題,就可以return了,所以偶們應該看到的完整函式是:

JNIEXPORT jint Java_com_hellondk_NdkForJava_ArrayFrom1To100(JNIEnv* env, jobject thiz, jintArray a, jintArray b)
{
    int length = 100;
    int* resultArray = new int[length];
    //自己給陣列賦值完成1到100的壯舉

    jintArray result = env->NewIntArray(length);

    env->SetIntArrayRegion  (result,0,length,resultArray);

    if(resultArray)
    {
        delete resultArray;
        resultArray = 0;        
    }

    return result;
}

最後還是要強調一下,由於我之前提過的自動變數的回收問題,儘可能不要使用自動變數完成陣列的操作,改用手動管理,要不有些手機上邊會爆出莫名其妙的錯。我一貫認為這些不一定會出但是有可能會出問題,特別是跨裝置的問題,最好能避免儘量避免。

String型別的問題

在ndk的部分,回到偶們剛剛寫好的函式SizeOfString,偶們接收到了一個jstring的物件str,這也就是java調動SizeOfString函式時傳送過來的物件,但jstring並不能直接進行操作,偶們需要對其進行一些處理以將其轉換為C下邊可用的字串物件。其實嚴格來說,String物件是指是一個char型別的陣列,但由於這是最常用的物件,所以ndk把它獨立出來作為一個傳統的只對象,操作上跟其他的jxxxArray類似,所以獲取String的方法跟其他陣列大同小異,但要留心的是,函式略有不同:

jboolean isCopy = JNI_FALSE;
const char* cStr = env->GetStringUTFChars(str,&isCopy);

//計算長度的問題由於某個我的懶筋犯了所以掠過

env->ReleaseStringUTFChars(str,cStr);

用到的Get函式需要使用GetStringUTFChars函式,UTF的含義相信能看到這塊的童鞋應該不用我多囉嗦啦(但你還是囉嗦了=。=),同樣是用完以後使用Release函式處理掉。

這塊其實不復雜,參考上前邊其他的陣列操作就可以完成只是對應函式略不一樣,我為什麼還要獨立把String拿出來說一下呢?

因為首先string是一種非常常用的變數;然後,如果想返回一個String是一個很麻煩的事情。當然有童鞋會說啦,我返回一個charArray,再在java裡把它處理成String不就行了麼,呃,其實可以算作一種解決方案,但因為String的特殊性,我再提供一種方案(所以這塊內容可以算作可有可無的內容啦;騷年圖樣吖,你看看為什麼要加UTF就知道這事情在java那邊也沒那麼簡單~好吧這是我瞎猜的,無節操掠過)。

偶們還是先來構造一個基本的函式,它的功能是返回一個字串,字串的內容是經典的”Hello World”。

java部分:

public native String HelloWorld();

那,偶們來看看NDK的部分:

JNIEXPORT jstring Java_com_hellondk+NdkForJava_HelloWOrld(JNIEnv* env, jobject thiz);

嗯,看起來好像跟前邊差不多嘛,為什麼要單獨拎出來討論呢?那麼,偶們再看看jstring的具體定義:

typedef jobject jstring;

咦?為什麼jstring會變成一個jobject的型別?因為,String雖然是一個很常用的型別,但事實上,它在java裡邊並沒有被視為值型別,簡單說,它一般情況下都是被用作引用引數的。所以,偶們在實現jstring的時候,跟之前的部分有些區別:

JNIEXPORT jstring Java_com_hellondk+NdkForJava_HelloWOrld(JNIEnv* env, jobject thiz)
{
    char* qrText = "Hello World";
    jstring jStr = env->NewStringUTF(qrText);
    return jStr;
}

抱頭跑……現在其實是要思考這樣一個問題,如果我想返回的是一個String[]陣列腫麼破? 讓偶們輕微的調整一個HelloWorld的這個函式,讓它返回一個String的陣列,陣列中分別是“Hello”和“World”,應該怎麼辦?

首先是Java的部分:

public native String[] HelloWorld();

然後是NDK的部分:

JNIEXPORT jstring Java_com_hellondk+NdkForJava_HelloWOrld(JNIEnv* env, jobject thiz);

在這裡偶們要注意一點,是不存在jstringArray這個物件的,雖然jni要typedef一下也不難,但這樣很可能會造成不必要的誤會,所以jni沒有做(雖然我更傾向於認為是偷懶=。=以己度人),所以偶們要返回的其實是一個jobjectArray。

第一步來說,偶們應該先new一個objectArray出來,但這裡有一些與之前不同的地方,由於jobject可以是任何java的物件,也就是包括但不止限於String型別,所以偶們要用“反射”的方法指定新生成的objectArray具體是什麼型別的陣列:

jclass typeString = env->FindClass("java/lang/String");

後邊的字串是特指String型別的,其實還有很多其它用法,這裡只把String作為一種最常用的特殊情況拿出來討論。
然後請先看這麼一個new函式:

jobjectArray resultArray = env->NewObjectArray((jsize)2, typestring, NULL);

現在,偶們聲明瞭一個長度為2,每個資料都是NULL,資料型別為java的String型別的陣列。可以看到,這個陣列是空的,偶們要先準備一些字串塞進去,例如:

char* text1 = "Hello";
char* text2 = "World";

jstring jstr1 = env->NewStringUTF(text1);
jstring jstr2 = env->NewStringUTF(text2);

字串好辦,但怎麼塞卻是個問題,還記得之前其他Array的寫法嗎?過程差不多:

env->SetObjectArrayElement(resultArray,0,jstr1);
env->SetObjectArrayElement(resultArray,0,jstr2);

分別把jstr1和jstr2放到索引為0和索引為1的位置。那麼,這個時候偶們可以放心的return陣列了麼?很遺憾,不是的,偶們還需要管理一下jstr的生存週期問題,由於SetObjectArrayElement做的是把目標物件拷貝了一份,所以,set完成之後,jstr1就沒用了,但由於ndk的機制,new出來的jstring並不會自動回收,所以偶們還有手動刪除一下:

env->DeleteLocalRef(jstr1);
env->DeleteLocalRef(jstr2);

好,現在看看這個函式的完整寫法:

JNIEXPORT jstring Java_com_hellondk+NdkForJava_HelloWOrld(JNIEnv* env, jobject thiz)
{
    jclass typeString = env->FindClass("java/lang/String");

    jobjectArray resultArray = env->NewObjectArray((jsize)2, typestring, NULL);

    char* text1 = "Hello";
    char* text2 = "World";

    jstring jstr1 = env->NewStringUTF(text1);
    jstring jstr2 = env->NewStringUTF(text2);

    env->SetObjectArrayElement(resultArray,0,jstr1);
    env->SetObjectArrayElement(resultArray,0,jstr2);

    env->DeleteLocalRef(jstr1);
    env->DeleteLocalRef(jstr2);

    return resultArray;
}

所有不是這些標準值型別的物件型別(既可以是自定義的物件,也可以是java自己提供的其他物件)。

對於這種物件,我只能說,我不會用。對於我來說使用成本太高了,有興趣的童鞋可以自己研究下=。=上文的String[]型別的處理是jobject的一種情況,更復雜的情況下還可以在ndk中呼叫java物件的特定函式完成一些工作,但這些都有點遙遠,而且偶們用ndk的目的不就是為了使用C的高效運算能力麼?所以這塊我沒有太大的動力去研究,起碼放在我現在這個專案來看(懶就懶啦,死性不改)。

MK檔案的說明

其實咧,上邊神馬介面吖,神馬編譯吖,別以為就可以用了,因為,NDK屬於比較手動的工具,編譯成連結庫還要設定很多東西滴,而這些設定是沒有介面給你方便的使用的(沒銀性啊),甚至還需要用一種特定的語言書寫(相當於又編一個程式啦-^-),語言的複雜來說,我也不太懂,我這裡只介紹一些基本的設定,複雜的使用方式請自己再研究啦~

Android.mk

要設定mk檔案之前,顯然偶們需要先建立一個,如果是第一次建立mk檔案,可能eclipse會提示安裝一個外掛,按提示安裝即可,這個外掛只是用來快速格式化文字的,我覺得應該不是必須品,但對優雅的IDE(顯然eclipse不算)有重度依賴症的我毫不猶豫的就裝了,就算爛,也總比沒有好(不是說好了不吐槽了喂~)。

一般來說,add native support以後會預設建立一個Android.mk檔案,但如果沒有的話也不要緊,在jni資料夾中右鍵-New-File,然後檔案命名為Android.mk即可,注意字尾和大小寫。

一個最簡單的mk檔案是這樣的:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := IHateNDK
LOCAL_SRC_FILES := IHateNDK.cpp

include $(BUILD_SHARED_LIBRARY)

第一行相當指定了當前資料夾為預設根目錄,之後所有關於檔案(路徑)的操作都會基於這個目錄來進行。

第三行使用了一個include命令,這裡的include命令並不是C語言中的引用某個檔案的意思,而是執行某個命令,這裡的程式碼中,第三行意思是清空當前所有的命令(也可以理解另起一組編譯選項),第八行表示建立一個動態連結庫。

第五行命令確定了即將編譯出來的so檔案的名稱,如前文所言,所有的動態連結庫都是而且必須以lib+這裡寫的名字+.so作為結尾才能正常使用。

第六行命令列出了所有要編譯的c/cpp檔案的目錄,沒錯,是所有,也就是如果你有100個cpp的檔案,麻煩用空格作為分隔符把所有的名字帶字尾全部寫出來!

好吧這個是逗你玩的,還是有一些方法可以省略一部分檔名,但確實還是很不方便,更多的引數及設定之後再介紹。

現在通過這個檔案偶們可以簡單的完成動態連結庫libIHateNDK.so的編譯(command+b)。

現在偶們來簡單介紹一些進階設定的情況:

基本操作符

這裡就介紹四個比較重要的操作符,更多內容請自己研究。

首先是“:=”操作符,該操作符的意思是初始化並把右邊的值賦給左邊。

然後是“+=”操作符,該操作符的意思是在左邊已有的設定基礎上增加一個引數值。這個有點難理解,一會統一用一個例子來解釋一下。

之後是“\”操作符,這個操作符的意思是下一行跟當前行是連在一起的,由於mk檔案的命令使用換行的方式表示結束,所以必要時需要通過“\”來對一些較長的命令進行排版。

最後是“#”操作符,該操作符的功能就是註釋本行中該操作符之後的所有文字。

下邊偶們來看一個例子:

#這是甲寫法
LOCAL_SRC_FILES := IHateNDK.cpp IHateJNI.cpp

#這是乙寫法a
LOCAL_SRC_FILES := IHateNDK.cpp \
                   IHateJNI.cpp


#這是乙寫法b(錯誤寫法)
LOCAL_SRC_FILES := IHateNDK.cpp 
                   IHateJNI.cpp

#這是丙寫法
LOCAL_SRC_FILES := IHateNDK.cpp
LOCAL_SRC_FILES := IHateJNI.cpp

簡單的說,甲寫法、乙寫法a和丁寫法可以認為是等價的,而乙寫法b是一個錯誤示範,考慮到用得著NDK的童鞋肯定都是學貫CJ滴銀,我就不多囉嗦了。

快速新增多個程式碼檔案

如果有100個檔案,我難道要把100個檔案都寫一遍麼?不要哇(我已抓狂),明明只要把jni資料夾下的所有檔案都預設索引了就可以了吧?(但這樣就缺少訂製能力了,某大神推推眼鏡道。qu shi=。=)

NDK提供了一種折衷的方案,起碼我只找到這種啦,再好的暫時沒見著。可以讓編譯器自動引用某個資料夾下的所有指定字尾的檔案(至於能不能編譯成功就看騷年你真正的技術了),而且注意,這個引用是非迴圈的,也就是說,被引用的資料夾下的子目錄內的檔案並不會被搜尋,所以,折衷方案就是,你把所有資料夾的路徑寫一遍(啊多麼痛的領悟啊這是那麼無聊啊)。

這個命令的原理是首先建立一張列表,列表上標註了所有資料夾的路徑,以及該路徑需要被引用的檔案的字尾名:

MY_CPP_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)

這裡引用的是預設目錄,也就是jni檔案下所有的字尾為cpp的檔案,是不是有理有據讓人信服?如果偶們要再增加一個資料夾,可以使用之前提到的“+=”操作符:

MY_CPP_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)
MY_CPP_LIST += $(wildcard $(LOCAL_PATH)/sbndk/*.cc)

這一句在原來的基礎上增加了jni目錄下bigint目錄下所有後綴為cc的檔案。好,按照這個方式可以建立一個很長的列表,但是怎麼讓這個列表生效呢?如下:

LOCAL_SRC_FILES += $(MY_CPP_LIST$(LOACL_PATH)/%...%/

搞定,這樣編譯的時候編譯器就會自動索引列表中所有你指名字尾的檔案啦。

Application.mk

這是個奇怪的世界,我一直沒弄明白有什麼用,不過確實有一些引數需要在這個檔案裡邊設定,如果預設沒有的話,跟建立Android.mk一樣的方式建立吧。

Log的問題

我沒能解決除錯的問題,所以只能使用Log的方式來輸出一些資訊輔助除錯,這個過程略煩躁(我躁狂症晚期沒救了)。

首先在需要Log的檔案中加入標頭檔案:

然後在Android.mk檔案中設定引數:

呼叫函式如下:

((void)__android_log_print(ANDROID_LOG_WARN, “logcat中tag列的值","要列印的具體字串" ))

由於Log的等級問題,可以把引數ANDROID_LOG_WARN替換為如下幾種:

__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,“***”) // LOG型別:debug
__android_log_print(ANDROID_LOG_INFO,LOG_TAG,“***”) // LOG型別:info
__android_log_print(ANDROID_LOG_WARN,LOG_TAG,“***”) // LOG型別:warning
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG,“***”) // LOG型別:error
__android_log_print(ANDROID_LOG_FATAL,LOG_TAG,“***”) // LOG型別:verbose

Log的結果會輸出在Logcat中,相信用Java的銀肯定比我熟,我繼續犯懶去咯。

STL的問題

相信stl的重要性我不必多言,如果不知道stl是神馬的童鞋相信要麼是已經超越了stl,要麼是還沒到用得著stl的時候,這章可以先不看了。

在NDK目錄中,存在著一個神祕的資料夾,裡邊全是各式各樣的stl標頭檔案,但是騷年以為只要把這些標頭檔案include進去就可以了麼?可沒有那麼簡單,但偶們首先可以先找到TA。

由於NDK提供了四個版本的stl庫供選擇,但最推薦的是stlport_static,所以我呼叫的也是這個(其實是另外幾個根本編譯不起來)。

首先開啟Application.mk檔案,新增引數:

APP_STL := stlport_static
APP_CPPFLAGS := -frtti
LOCAL_CPPFLAGS += -fexceptions

然後,暫時想不起來還要新增什麼了,在專案上點選右鍵-New-Folder,選擇Advanced,再勾上Link to alternate location (Linked Folder)。Browse,找到NDK開發包\sources\cxx-stl\stlport\stlport,注意是兩個stlport噢,是裡邊的那個,選中它,但別開啟,因為需要引用的就是這個資料夾。

以vector為例,在需要使用vector的地方引用vector。

#include<vector>
std::vector<int>* test = new std::vector<int>();

然後的事就不多說啦。

Zxing的問題

商業機密(懶病病入膏肓放棄治療)。

在Java專案下呼叫編譯好的so動態連結庫

在其他的Java專案下的libs資料夾,如果沒有則手動建立一個,再在libs資料夾下再建立一個armeabi資料夾,把編譯好的so檔案放在這個資料夾裡,注意so檔案的名字一定是’lib’開頭的,這個問題我已經提過好多次啦。
這裡寫圖片描述
在需要呼叫偶們ndk的java介面的地方增加一段靜態程式碼:

static
{
    System.loadLibrary("IHateNDK");
}

注意這裡的名字是沒有lib的,但資料夾中的名字一定要有lib開頭(囉嗦成癮無藥可救)。

然後,在你想用的地方進行灰翔吧~~~

報錯和解決方案

  1. 編譯的時候Console報錯WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 8 in ./AndroidManifest.xml
    這裡寫圖片描述

解決方案:

顯然從文字上就能看出是因為使用的AndroidManifest檔案中的支援的最低版本的引數和NDK編譯配置檔案中的最低版本配置不相符,將兩者統一即可(以下以14為例,不一定要14,只要統一即可)。

在Application.mk檔案中修改(或增加)關鍵字:

APP_PLATFORM := android-14

修改AndroidManifest檔案中的android:minSdkVersion=”14”

吐槽

果然跟我用之前猜的差不多,eclipse不愧是我非常非常沒有猿糞的開發工具,我簡直已經無力吐槽了,為什麼穩定性這麼糟糕的東西居然還能被如此多大牛開發者甘之如飴?一群叫囂著要開發出世界上最人性化UI的人連自己用的工具都不穩定(人性化?呵呵),就像一個拿著漏勺爛鍋殘口菜刀的乞丐跟你說他能做世界上最好的叫花雞一樣——你信嗎?當然,廚師可以不會(一般也不)生產菜刀,生產菜刀的也不一定是廚師,但起碼要能分辨出好的菜刀;開發者可以不會開發開發者工具,但開發者工具一定是開發者開發的。

專案的需求是使用NDK將偶們以前使用的一些在xcode下編譯的用於objective-c的c++靜態庫(顯然,偶們有原始碼)移植到ndk的環境中,NDK是由谷歌娘提供的可以允許java的安卓程式在安卓平臺下以c/cpp(由於所有人都知道c跟cpp的關係,不知道的人請不要浪費時間看這篇東西了,然後那個反斜槓打起來很麻煩,以下所有寫c或cpp的地方請自己腦補另外一半)的方式(實際上跟語言特性有關,不多說,反正運算速度比java下直接執行要快很多)執行一部分函式的功能,加快運算速度。因為偶們原有的專案涉及到大量的圖形運算,顯然java本身並不擅長做這些東西(原諒我一生java黑),他已經被java各種不穩定(顯然,這裡更多是IDE,也就是eclipse的問題)弄的無數次想砸電腦了,我不否認java在軟體開發史上的地位,以及作出的貢獻,就像我不否認無聲黑白電視是一個偉大的發明一樣)(不擅長並不代表不能做,請明白擅長的意思是很容易就能做好)(關於java的問題請各位讀者老爺不要跟我討論了,我這裡用到也是迫不得已,請相信我如果有的選根本不想碰這個東西,也對此不感興趣,所以我的使用完全是為了達到目的而完成的,如有得罪請見諒,蘿蔔白菜各有所好,謝絕人蔘公雞,如果有忍不住的請相信我也不是那種忍得住的銀)。

相關推薦

Mac使用Eclipse實現Android呼叫C/C++NDK基礎詳細教程

寫於2014年那個辦公室停電導致熱爆了汗流浹背的夏天。 需求 NDK是由谷歌娘提供的,某種意義上就是可以讓android使用c開發的第“三”方sdk,所以,正常來說eclipse是沒有配置這個東西的,當然如我所云,我只考慮用最小的工程成本(較少的

Android-3】Android的任務棧Task

集合 情況下 清除 bsp 生命周期方法 任務棧 保存 sin 也會 一、Android任務棧 概述:Android中的任務棧其實就是Activity的集合,在Android中退出程序的時候必須把任務棧中的所有Activity清除出棧,此時才能安全的完全的退出程序, 任務棧

Android的對話方塊AlertDialog

建立android中分體式對話方塊需要四個步驟: 第一:獲得AlertDialog的靜態內部類Builder物件,有該類建立對話方塊。 第二:通過Builder物件設定對話方塊的標題,按鈕UI及將要響應的事件。、 第三:呼叫Builder的Create()方法建立對對話方塊 第四

MacCMakeLists.txt檔案的使用快速入門

在用Cmake編譯檔案,發現需要自己動手寫CMakeLists.txt檔案,簡單研究了下,記錄如下: 一、介紹 CMake是一種跨平臺編譯工具,比make更為高階,使用起來要方便得多。CMake主要是編寫CMakeLists.txt檔案,然後用cmake命令將CMakeL

如何在html呼叫JS檔案

一、JavaScript指令碼語言的特性 JavaScript指令碼語言是一種面向瀏覽器的網頁尾本程式語言。指令碼語言有以下幾個特性: 1、在客戶端執行。完全在使用者的計算機上執行,無須經過伺服器。 2、面向物件。具有內建物件,也可以直接操作瀏覽器物件。 3、動態變化。可以對使用者的輸入作出

Android圖片壓縮分析

一、前言 在 Android 中進行圖片壓縮是非常常見的開發場景,主要的壓縮方法有兩種:其一是質量壓縮,其二是下采樣壓縮。 前者是在不改變圖片尺寸的情況下,改變圖片的儲存體積,而後者則是降低影象尺寸,達到相同目的。 由於本文的篇幅問題,分為上下兩篇釋出

Android的Http通訊之Http協議基本知識

寫了這麼久的專案,幾乎每個專案都用到了網路請求,不對,是所有的專案。一直沒有對這一塊做過詳細的總結,今天結合一些網上的資料以及自己的理解,來談談Http。廢話不多說,直奔正題吧...... 我們如果想要真正的瞭解Http,我們必須要知道什麼是Http? 一、什麼是Http?

Android 圖片壓縮分析

歡迎大家前往騰訊雲社群,獲取更多騰訊海量技術實踐乾貨哦~ 作者: shawnzhao 一、前言 在 Android 中進行圖片壓縮是非常常見的開發場景,主要的壓縮方法有兩種:其一是質量壓縮,其二是下采樣壓縮。 前者是在不改變圖片尺寸的情況下,

Android同步類MutexAutoMutex與Condition。

 在Android中,封裝的同步類主要有Mutex(AutoMutex)與Condition。 這兩個類在android中被大量的使用,這也說明這兩個類是非常重要的。 下面我們就從3個方面來分析他們。 它們是什麼,他們的實現原理,即what 為什麼要這

Mac node安裝和環境配置詳解最新

1、進入node官網下載頁http://nodejs.cn/download/       如下圖: 選擇macOS安裝程式下載,此為  10.12.0版本、 2、雙擊安裝程式安裝 如下: 顯示程式將會安裝的位置 一直點選繼續到最後為 3

Android 的 IPC 方式 Messenger

Messenger 可以翻譯為信使,顧名思義,通過它可以在不同程序中傳遞 Message 物件,在 Message 中加入我們需要傳遞的資料,就可以輕鬆地實現資料的程序間傳遞了。Messenger 是一種輕量的 IPC 方案,它的底層實現是 AIDL,下面是 Messenge

Android顯示Html內容總結

效果圖如下: 首先,Android中顯示Html內容,有3中方式:(目前我用到的有這3種) 1、可以利用Android原生的Html.fromHtml(str, imageGetter, tagHandler)來進行顯示。(不過,我這邊用了,即使加了頁面載入動畫,還是

Android的WiFi P2P

Android中的WiFi P2P能夠允許一定範圍內的裝置通過Wifi直接互連而不必通過熱點或網際網路。 使用WiFi P2P需要Android API Level >= 14才可以,而且不要忘記在Manifest檔案中加入下面5個許可權: ● android.per

探索Android的Parcel機制

一.先從Serialize說起 我們都知道JAVA中的Serialize機制,譯成序列化、序列化……,其作用是能將資料物件存入位元組流當中,在需要時重新生成物件。主要應用是利用外部儲存裝置儲存物件狀態,

Android觀察者模式Observable的理解

對於觀察者模式還是第一次接觸,今天在上網看了些資料瞭解了一下,大意瞭解了…… 定義:“定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變的時候,所有依賴於它的物件都將得到通知,並自動更新”,這就是所謂的觀察者模式,照意思理解那麼就一定會有觀察者和被觀察者了,在Jav

Android 的 IPC 方式 Binder 連線池和選用合適的 IPC 方法

1. Binder 連線池 通過前面幾篇文章的介紹,我們知道,不同的 IPC 方式有不同的特點和適用場景。在這篇文章中,我們在介紹下 AIDL,原因是 AIDL 是一種最常用的程序間通訊方式,是日常開發中程序間通訊的首選,所以我們需要額外強調一下。 如何使用 AIDL 我

Android網路流量控制防火牆——Iptables

Iptables簡介iptables是與最新的 2.6.x 版本 Linux 核心整合的 IP 資訊包過濾系統。如果 Linux 系統連線到因特網或 LAN、伺服器或連線 LAN 和因特網的代理伺服器, 則該系統有利於在 Linux 系統上更好地控制 IP 資訊包過濾

AndroidExpandableListView的使用

相關文章: ExpandableListView是可擴充套件的下拉列表,它的可擴充套件性在於點選父item可以拉下或收起列表,適用於一些場景的使用,下面介紹的是在Activity中如何使用,關於它的各種樣式的詳細解釋請見另一篇文章:Android中ExpandableLi

Android的搜尋框SearchView的功能和用法

1、SearchView是搜尋框元件,它可以讓使用者在文字框裡輸入文字,通過監聽器取得使用者的輸入,當用戶點選搜尋時,監聽器執行實際的搜尋。 2、SearchView元件的常用方法如下: ①setIconifiedByDefault(boolean iconified) =

Java/Android的網路程式設計--

網路是20世紀最偉大的發明之一,眾多的裝置可以以有線或者無線的方式連入整個網際網路,進而互相通訊。為了更好的開發、管理、接入網路,科學家設計了通訊協議,將整個網路架構分為7層(4層),並規範了每一層的功能。 網路分層 早期的OSI參考模型將網路分為7層:應用層、表示層、會