日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

加载SD卡中的SO库

發(fā)布時間:2025/4/16 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 加载SD卡中的SO库 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

基本信息

  • 作者:kaedea

  • 項目:android-dynamical-loading

JNI與NDK

Android中JNI的使用其實就包含了動態(tài)加載,APP運行時動態(tài)加載.so庫并通過JNI調(diào)用其封裝好的方法。后者一般是使用NDK工具從C/C++代碼編譯而成,運行在Native層,效率會比執(zhí)行在虛擬機的Java代碼高很多,所以Android中經(jīng)常通過動態(tài)加載.so庫來完成一些對性能比較有需求的工作(比如T9搜索、或者Bitmap的解碼、圖片高斯模糊處理等)。此外,由于.so庫是由C++編譯而來的,只能被反編譯成匯編代碼,相比Smali更難被破解,因此.so庫也可以被用于安全領(lǐng)域。

與我們常說的基于ClassLoader的動態(tài)加載不同,SO庫的加載是使用System類的(由此可見對SO庫的支持也是Android的基礎(chǔ)功能),所以這里這是作為補充說明。不過,如果使用ClassLoader加載SD卡里插件APK,而插件APK里面包含有SO庫,這就涉及到了對插件APK里的SO庫的加載,所以我們也要知道如何加載SD卡里面的SO庫。

一般的SO文件的使用姿勢

以一個“圖片高斯模糊”的功能為例,如果使用Java代碼對圖像Bitmap的每一個像素點進行計算,那整體耗時將會非常大,所以可以考慮使用JNI。(詳細的JNI使用教程網(wǎng)絡(luò)上有許多,這里不贅述)

這里推薦一個開源的高斯模糊項目?Android StackBlur

在命令行定位到Android.mk文件所在目錄,運行NDK工具的ndk-build命令就能編譯出我們需要SO庫

再把SO庫復制到Android Studio項目的jniLibs目錄中

(Android Studio現(xiàn)在也支持直接編譯SO庫,但是有許多坑,這里我選擇手動編譯)

接著在Java中把SO庫對應(yīng)的模塊加載進來

// load so file from internal directorytry {System.loadLibrary("stackblur");NativeBlurProcess.isLoadLibraryOk.set(true);Log.i("MainActivity", "loadLibrary success!");} catch (Throwable throwable) {Log.i("MainActivity", "loadLibrary error!" + throwable);}

加載成功后就可以直接使用Native方法了

public class NativeBlurProcess {public static AtomicBoolean isLoadLibraryOk = new AtomicBoolean(false);//native methodprivate static native void functionToBlur(Bitmap bitmapOut, int radius, int threadCount, int threadIndex, int round);}

由此可見,在Android項目中,SO庫的使用也是一種動態(tài)加載,在運行時把可執(zhí)行文件加載進來。一般情況下,SO庫都是打包在APK內(nèi)部的,不允許修改。這種“動態(tài)加載”看起來不是我們熟悉的那種啊,貌似沒什么卵用。不過,其實SO庫也是可以存放在外部存儲路徑的。

如何把SO文件存放在外部存儲

注意到上面加載SO庫的時候我們用到了System類的“l(fā)oadLibrary”方法,同時我們也發(fā)現(xiàn)System類還有一個“l(fā)oad”方法,看起來差不多啊,看看他們有什么區(qū)別吧!

/*** See {@link Runtime#load}.*/public static void load(String pathName) {Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());}/*** See {@link Runtime#loadLibrary}.*/public static void loadLibrary(String libName) {Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());}

先看看loadLibrary,這里調(diào)用了Runtime的loadLibrary,進去一看,又是動態(tài)加載熟悉的ClassLoader了(這里也佐證了SO庫的使用就是一種動態(tài)加載的說法)

/** Searches for and loads the given shared library using the given ClassLoader.*/void loadLibrary(String libraryName, ClassLoader loader) {if (loader != null) {String filename = loader.findLibrary(libraryName);String error = doLoad(filename, loader);return;}……}

看樣子就像是通過庫名獲取一個文件路徑,再調(diào)用“doLoad”方法加載這個文件,先看看“l(fā)oader.findLibrary(libraryName)”

protected String findLibrary(String libName) {return null;}

ClassLoader只是一個抽象類,它的大部分工作都在BaseDexClassLoader類中實現(xiàn),進去看看

public class BaseDexClassLoader extends ClassLoader {public String findLibrary(String name) {throw new RuntimeException("Stub!");} }

不對啊,這里只是拋了一個RuntimeException異常,什么都沒做啊!

其實這里有一個誤區(qū),也是剛開始開Android SDK源碼的同學容易搞混的。Android SDK自帶的源碼其實只是給我們開發(fā)者參考的,基本只是一些常用的類,Google不會把整個Android系統(tǒng)的源碼都放到這里來,因為整個項目非常大,ClassLoader類平時我們接觸得少,所以它的具體實現(xiàn)的源碼并沒有打包進SDK里,如果需要,我們要到官方AOSP項目里面去看(順便一提,整個AOSP5.1項目大小超過150GB,真的有需要的話推薦用一個移動硬盤存儲)。

這里為了方便,我們可以直接看在線的代碼?BaseDexClassLoader.java

@Overridepublic String findLibrary(String name) {return pathList.findLibrary(name);}

再看進去DexPathList類

/*** Finds the named native code library on any of the library* directories pointed at by this instance. This will find the* one in the earliest listed directory, ignoring any that are not* readable regular files.** @return the complete path to the library or {@code null} if no* library was found*/public String findLibrary(String libraryName) {String fileName = System.mapLibraryName(libraryName);for (File directory : nativeLibraryDirectories) {File file = new File(directory, fileName);if (file.exists() && file.isFile() && file.canRead()) {return file.getPath();}}return null;}

到這里已經(jīng)明朗了,根據(jù)傳進來的libName,掃描APK內(nèi)部的nativeLibrary目錄,獲取并返回內(nèi)部SO庫文件的完整路徑filename。再回到Runtime類,獲取filename后調(diào)用了“doLoad”方法,看看

private String doLoad(String name, ClassLoader loader) {String ldLibraryPath = null;String dexPath = null;if (loader == null) {ldLibraryPath = System.getProperty("java.library.path");} else if (loader instanceof BaseDexClassLoader) {BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;ldLibraryPath = dexClassLoader.getLdLibraryPath();}synchronized (this) {return nativeLoad(name, loader, ldLibraryPath);}}

到這里就徹底清楚了,調(diào)用Native方法“nativeLoad”,通過完整的SO庫路徑filename,把目標SO庫加載進來。

說了半天還沒有進入正題呢,不過我們可以想到,如果使用loadLibrary方法,到最后還是要找到目標SO庫的完整路徑,再把SO庫加載進來,那我們能不能一開始就給出SO庫的完整路徑,然后直接加載進來?我們猜想load方法就是干這個的,看看。

void load(String absolutePath, ClassLoader loader) {if (absolutePath == null) {throw new NullPointerException("absolutePath == null");}String error = doLoad(absolutePath, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}}

我勒個去,一上來就直接來到doLoad方法了,這證明我們的猜想可能是正確的,那么在實際項目中測試看看吧!

我們先把SO放在Asset里,然后再復制到內(nèi)部存儲,再使用load方法把其加載進來。

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);File dir = this.getDir("jniLibs", Activity.MODE_PRIVATE);File distFile = new File(dir.getAbsolutePath() + File.separator + "libstackblur.so");if (copyFileFromAssets(this, "libstackblur.so", distFile.getAbsolutePath())){//使用load方法加載內(nèi)部儲存的SO庫System.load(distFile.getAbsolutePath());NativeBlurProcess.isLoadLibraryOk.set(true);}}public void onDoBlur(View view){ImageView imageView = (ImageView) findViewById(R.id.iv_app);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), android.R.drawable.sym_def_app_icon);Bitmap blur = NativeBlurProcess.blur(bitmap,20,false);imageView.setImageBitmap(blur);}public static boolean copyFileFromAssets(Context context, String fileName, String path) {boolean copyIsFinish = false;try {InputStream is = context.getAssets().open(fileName);File file = new File(path);file.createNewFile();FileOutputStream fos = new FileOutputStream(file);byte[] temp = new byte[1024];int i = 0;while ((i = is.read(temp)) > 0) {fos.write(temp, 0, i);}fos.close();is.close();copyIsFinish = true;} catch (IOException e) {e.printStackTrace();Log.e("MainActivity", "[copyFileFromAssets] IOException "+e.toString());}return copyIsFinish;} }

點擊onDoBlur按鈕,果然加載成功了!

那能不能直接加載外部存儲上面的SO庫呢,把SO庫拷貝到SD卡上面試試。

看起來是不可以的樣子,Permission denied!

java.lang.UnsatisfiedLinkError: dlopen failed: couldn't map "/storage/emulated/0/libstackblur.so" segment 1: Permission denied

看起來像是沒有權(quán)限的樣子,看看源碼哪里拋出的異常吧

/** Loads the given shared library using the given ClassLoader.*/void load(String absolutePath, ClassLoader loader) {if (absolutePath == null) {throw new NullPointerException("absolutePath == null");}String error = doLoad(absolutePath, loader);if (error != null) {// 這里拋出的異常throw new UnsatisfiedLinkError(error);}}

應(yīng)該是執(zhí)行doLoad方法時出現(xiàn)了錯誤,但是上面也看過了,doLoad方法里調(diào)用了Native方法“nativeLoad”,那應(yīng)該就是Native代碼里出現(xiàn)的錯誤。平時我很少看到Native里面,上一次看的時候,是因為需要看看點九圖NinePathDrawable的縮放控制信息chunk數(shù)組的具體作用是怎么樣,費了好久才找到我想要的一小段代碼。所以這里就暫時不跟進去了,有興趣的同學可以告訴我關(guān)鍵代碼的位置。

我在一個Google的開發(fā)者論壇上找到了一些答案

The SD Card is mounted noexec, so I'm not sure this will work.

Moreover, using the SD Card as a storage location is a really bad idea, since any other application can modify/delete/corrupt it easily.
Try downloading the library to your application's data directory instead, and load it from here.

這也容易理解,SD卡等外部存儲路徑是一種可拆卸的(mounted)不可執(zhí)行(noexec)的儲存媒介,不能直接用來作為可執(zhí)行文件的運行目錄,使用前應(yīng)該把可執(zhí)行文件復制到APP內(nèi)部存儲再運行。

最后,我們也可以看看官方的API文檔

看來load方法的用途和我們理解的一致,文檔里說的shared library就是指SO庫(shared object),至此,我們就可以把SO文件移動到外部存儲了,或者從網(wǎng)絡(luò)下載都行。

總結(jié)

以上是生活随笔為你收集整理的加载SD卡中的SO库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。