Android 是 Google 公司基於 Linux 平臺開發的開源手機操做系統, 天然要對 C C++ 提供原生支持. 經過 NDK, Android應用程序能夠很是方便地實現 Java 與 C/C++代碼的相互溝通.java
隨着語言的發展, 近些年來出現了一些諸如 Rust, Haskell, Go等新的系統編程語言對 C/C++ 的系統編程語言地位發起了強烈的攻擊. (Kotlin-Native 這門技術也能實現 native 開發, 不過要依賴專門的垃圾回收器進行內存管理)linux
另外, 經過相應的交叉編譯鏈, 使它們在 Android 平臺上進行 NDK 開發成爲了新的可能.android
本文的主角是 Haskell, 一門純函數式編程語言.git
本文會實現一個 JNI
的例子, 其中相關的 so 庫是使用 少許 C++ 代碼 + Haskell
來實現的.github
這是 Activity 代碼 (裏面包含一個 JNI 接口) :編程
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var rxPermissions: RxPermissions = RxPermissions(this)
rxPermissions
.requestEachCombined(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ permission ->
// will emit 2 Permission objects
if (permission.granted) {
// `permission.name` is granted !
//Toast.makeText(this@MainActivity, "已成功授予全部權限!", Toast.LENGTH_SHORT).show()
doSthAfterAllPermissionGranted()
} else if (permission.shouldShowRequestPermissionRationale) {
// Denied permission without ask never again
} else {
// Denied permission with ask never again
// Need to go to the settings
}
})
}
private fun doSthAfterAllPermissionGranted() {
// 搜索 "/sdcard/*.txt" 下的 text 文檔.
Log.w("demo", "${namesMatchingJNI("/sdcard/*.txt").joinToString()}}")
}
// 使用通配符進行模糊匹配搜索 sdcard 的相關文件
external fun namesMatchingJNI(path: String): Array<String>
}
複製代碼
#include <jni.h>
#include <unistd.h>
#include <sstream>
#include <string>
#include "ghcversion.h"
#include "HsFFI.h"
#include "Rts.h"
#include "my_log.h"
#include "Lib_stub.h"
#include "FileSystem_stub.h"
#include "ForeignUtils_stub.h"
#include "android_hs_common.h"
extern "C" {
JNIEXPORT jobjectArray JNICALL Java_com_xxx_yyy_MainActivity_namesMatchingJNI( JNIEnv *env, jobject thiz, jstring path) {
LOG_ASSERT(NULL != env, "JNIEnv cannot be NULL.");
LOG_ASSERT(NULL != thiz, "jobject cannot be NULL.");
LOG_ASSERT(NULL != path, "jstring cannot be NULL.");
const char *c_value = env->GetStringUTFChars(path, NULL);
CStringArrayLen *cstrArrLen = static_cast<CStringArrayLen *>(namesMatching(
const_cast<char *>(c_value)));
char **result = cstrArrLen->cstringArray;
jsize len = cstrArrLen->length;
env->ReleaseStringUTFChars(path, c_value);
jobjectArray strs = env->NewObjectArray(len, env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for (int i = 0; i < len; i++) {
jstring str = env->NewStringUTF(result[i]);
env->SetObjectArrayElement(strs, i, str);
}
// freeCStringArray frees the newArray pointer created in haskell module
freeNamesMatching(cstrArrLen);
return strs;
}
}
複製代碼
上面這代碼只是少許的 C++ 代碼, 主要的功能是稍微地封裝調用 Haskell
實現的 namesMatching
函數.ruby
由於 JNI 不能直接調用 Haskell
代碼實現的函數, 藉助 FFI
實現間接調用 (跟 Rust
同樣):app
JVM --> JNI --> C++ --> FFI --> Haskell
複製代碼
Haskell
實現的 namesMatching
函數:module Android.FileSystem
( matchesGlob
, namesMatching
) where
import Android.ForeignUtils
import Android.Log
import Android.Regex.Glob (globToRegex, isPattern)
import Control.Exception (SomeException, handle)
import Control.Monad (forM)
import Foreign
import Foreign.C
import System.Directory (doesDirectoryExist, doesFileExist, getCurrentDirectory, getDirectoryContents)
import System.FilePath ((</>), dropTrailingPathSeparator, splitFileName)
import Text.Regex.Posix ((=~))
matchesGlob :: FilePath -> String -> Bool
matchesGlob name pat = name =~ globToRegex pat
_matchesGlobC name glob = do
name <- peekCString name
glob <- peekCString glob
return $ matchesGlob name glob
doesNameExist :: FilePath -> IO Bool
doesNameExist name = do
fileExists <- doesFileExist name
if fileExists
then return True
else doesDirectoryExist name
listMatches :: FilePath -> String -> IO [String]
listMatches dirName pat = do
dirName' <-
if null dirName
then getCurrentDirectory
else return dirName
handle (const (return []) :: (SomeException -> IO [String])) $ do
names <- getDirectoryContents dirName'
let names' =
if isHidden pat
then filter isHidden names
else filter (not . isHidden) names
return (filter (`matchesGlob` pat) names')
isHidden ('.':_) = True
isHidden _ = False
listPlain :: FilePath -> String -> IO [String]
listPlain dirName baseName = do
exists <-
if null baseName
then doesDirectoryExist dirName
else doesNameExist (dirName </> baseName)
return
(if exists
then [baseName]
else [])
namesMatching :: FilePath -> IO [FilePath]
namesMatching pat
| not $ isPattern pat = do
exists <- doesNameExist pat
return
(if exists
then [pat]
else [])
| otherwise = do
case splitFileName pat
-- 在只有文件名的狀況下, 只在當前目錄查找.
of
("", baseName) -> do
curDir <- getCurrentDirectory
listMatches curDir baseName
-- 在包含目錄的狀況下
(dirName, baseName)
-- 因爲目錄自己可能也是一個符合 glob 模式的字符牀, 如(/foo*bar/far?oo/abc.txt)
-> do
dirs <-
if isPattern dirName
then namesMatching (dropTrailingPathSeparator dirName)
else return [dirName]
-- 通過上面操做, 拿到全部符合規則的目錄
let listDir =
if isPattern baseName
then listMatches
else listPlain
pathNames <-
forM dirs $ \dir -> do
baseNames <- listDir dir baseName
return (map (dir </>) baseNames)
return (concat pathNames)
_namesMatchingC :: CString -> IO (Ptr CStringArrayLen)
_namesMatchingC filePath = do
filePath' <- peekCString filePath
pathNames <- namesMatching filePath'
pathNames' <- forM pathNames newCString :: IO [CString]
newCStringArrayLen pathNames'
_freeNamesMatching :: Ptr CStringArrayLen -> IO ()
_freeNamesMatching ptr = do
cstrArrLen <- peekCStringArrayLen ptr
let cstrArrPtr = getCStringArray cstrArrLen
freeCStringArray cstrArrPtr
free ptr
return ()
foreign export ccall "matchesGlob" _matchesGlobC :: CString -> CString -> IO Bool
foreign export ccall "namesMatching" _namesMatchingC :: CString -> IO (Ptr CStringArrayLen)
foreign export ccall "freeNamesMatching" _freeNamesMatching :: Ptr CStringArrayLen -> IO ()
複製代碼
咱們藉助交叉編譯鏈, 把這段 Haskell 代碼編譯成靜態庫, 名爲 libHSandroid-hs-mobile-common-0.1.0.0-inplace-ghc8.6.5.a
編程語言
其中 foreign export ccall "namesMatching" _namesMatchingC :: CString -> IO (Ptr CStringArrayLen)
是 FFI
接口, 暴露給 C++
代碼調用.ide
libHSandroid-hs-mobile-common-0.1.0.0-inplace-ghc8.6.5.a
add_library(lib_hs STATIC IMPORTED)
set_target_properties(lib_hs
PROPERTIES
IMPORTED_LOCATION $ENV{HOME}/dev_kit/src_code/android-hs-mobile-common/dist-newstyle/build/${ALIAS_1_ANDROID_ABI}/ghc-8.6.5/android-hs-mobile-common-0.1.0.0/build/libHSandroid-hs-mobile-common-0.1.0.0-inplace-ghc8.6.5.a)
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
...
lib_hs
...
)
複製代碼
運行的結果(經過打印 log 來呈現):
// logcat
2019-08-26 18:14:43.662 12344-12344/com.xxx.yyy.helloworld W/demo: /sdcard/jl.txt, /sdcard/ceshitest.txt, /sdcard/treeCallBack.txt}
複製代碼
Android 的 NDK 開發並非只有 C/C++, 還有別的一方天地. 特別是使用 C/C++ 寫業務邏輯的場景, 開發效率特別低.