Android 动态加载 .SO

需求:

有时候应用修复了native层一个小BUG,应用需要更新了,但是用户必须下载整个APK包进行安装,而我们需要的只是替换SO

于是想,能不能加载自定义路径下的 SO 文件呢

答案是完全没问题:

使用系统方法:

void java.lang.System.load(String pathName)

但是有一点,pathName 路径必须有执行权限,意思就是说我们不能加载SD卡上的SO,因为没有执行权限

那也没关系,我们复制到应用私有目录下就OK嘛。

看码

private void load() {
        File dir = getDir("libs", Context.MODE_PRIVATE);
        File soFile = new File(dir, "libTestJNI.so");
        FileUtils.assetToFile(this, "libTestJNI.so", soFile);
        
        try {
            System.load(soFile.getAbsolutePath());
        } catch (Exception e) {
        }
    }

这样就完全OK,

我们只需要架个服务器,每次启动时动态监测 SO 文件有没有更新,有则下载SO,然后加载,这样就可以避免用户安装新的应用,

要知道重新安装应用的用户体验是很差的,要让用户无感知的更新他。

  1 package com.edroid.common.utils;
  2 
  3 import java.io.File;
  4 import java.io.FileInputStream;
  5 import java.io.FileOutputStream;
  6 import java.io.IOException;
  7 import java.io.InputStream;
  8 import java.lang.ref.WeakReference;
  9 import java.lang.reflect.Method;
 10 import java.nio.channels.FileLock;
 11 import java.util.ArrayList;
 12 import java.util.List;
 13 import java.util.Locale;
 14 
 15 import org.apache.http.util.ByteArrayBuffer;
 16 
 17 import android.content.Context;
 18 import android.content.res.AssetManager;
 19 import android.os.Environment;
 20 import android.os.StatFs;
 21 import android.util.Log;
 22 
 23 /**
 24  * 文件操作工具类
 25  * 
 26  * @author Yichou 
 27  * 
 28  * @date 2013-08-31
 29  */
 30 public final class FileUtils {
 31     static final String LOG_TAG = "FileUtils";
 32 
 33     /**
 34      * 操作成功返回值
 35      */
 36     public static final int SUCCESS = 0;
 37 
 38     /**
 39      * 操作失败返回值
 40      */
 41     public static final int FAILED = -1;
 42 
 43     private static final int BUF_SIZE = 32 * 1024; // 32KB
 44 
 45     public static final int S_IRWXU = 00700;
 46     public static final int S_IRUSR = 00400;
 47     public static final int S_IWUSR = 00200;
 48     public static final int S_IXUSR = 00100;
 49 
 50     public static final int S_IRWXG = 00070;
 51     public static final int S_IRGRP = 00040;
 52     public static final int S_IWGRP = 00020;
 53     public static final int S_IXGRP = 00010;
 54 
 55     public static final int S_IRWXO = 00007;
 56     public static final int S_IROTH = 00004;
 57     public static final int S_IWOTH = 00002;
 58     public static final int S_IXOTH = 00001;
 59     
 60     private static WeakReference<Exception> exReference;
 61         
 62     /**
 63      * 文件类型枚举
 64      */
 65     public static enum FileState {
 66         FState_Dir("I am director!"), // 目录
 67         FState_File("I am file!"), // 文件
 68         FState_None("I am a ghost!"), // 不存在
 69         FState_Other("I am not human!"); // 其他类型
 70 
 71         private String tag;
 72 
 73         private FileState(String tag) {
 74             this.tag = tag;
 75         }
 76 
 77         public String getTag() {
 78             return tag;
 79         }
 80 
 81         @Override
 82         public String toString() {
 83             return tag;
 84         }
 85     }
 86 
 87     private FileUtils() {
 88     }
 89 
 90     /**
 91      * 获取文件状态
 92      * 
 93      * @param path
 94      * @return
 95      */
 96     public static FileState fileState(String path) {
 97         return fileState(new File(path));
 98     }
 99 
100     public static FileState fileState(File file) {
101         if (!file.exists())
102             return FileState.FState_None;
103 
104         if (file.isFile())
105             return FileState.FState_File;
106 
107         if (file.isDirectory())
108             return FileState.FState_Dir;
109 
110         return FileState.FState_Other;
111     }
112 
113     /**
114      * 创建文件夹
115      * 
116      * @param path
117      * @return
118      */
119     public static int createDir(String path) {
120         // int l = path.length();
121         // if(path.charAt(l-1) == File.separatorChar){ //如果末尾是 /
122         // 会导致创建目录失败,测试发现不会
123         // path = path.substring(0, l-1);
124         // }
125 
126         return createDir(new File(path));
127     }
128 
129     public static int createDir(File file) {
130         if (file.exists()) {
131             if (file.isDirectory())
132                 return SUCCESS;
133             file.delete(); // 避免他是一个文件存在
134         }
135 
136         if (file.mkdirs())
137             return SUCCESS;
138 
139         return FAILED;
140     }
141 
142     public static int removeDir(String path) {
143         return removeDir(new File(path));
144     }
145 
146     /**
147      * 删除一个文件夹
148      * 
149      * <p>
150      * by:yichou 2013-5-7 15:24:41
151      * <p>
152      * 
153      * @param dir
154      * @return
155      */
156     public static int removeDir(File dir) {
157         if (!dir.exists())
158             return SUCCESS;
159 
160         if (dir.isDirectory()) {
161             File[] files = dir.listFiles();
162             if (files != null) {
163                 for (File f : files) {
164                     if (f.isDirectory())
165                         removeDir(f);
166                     else
167                         f.delete();
168                 }
169             }
170         }
171 
172         return dir.delete() ? SUCCESS : FAILED;
173     }
174 
175     /**
176      * @see {@link #checkParentPath(File)}
177      */
178     public static void checkParentPath(String path) {
179         checkParentPath(new File(path));
180     }
181 
182     /**
183      * 在打开一个文件写数据之前,先检测该文件路径的父目录是否已创建,保证能创建文件
184      * 
185      * @param file
186      */
187     public static void checkParentPath(File file) {
188         File parent = file.getParentFile();
189         if (parent != null && !parent.isDirectory())
190             createDir(parent);
191     }
192 
193     /**
194      * 将一缓冲流写入文件
195      * 
196      * @param path
197      *            目标文件路径
198      * @param is
199      *            输入流
200      * @param isAppend
201      *            是否追加
202      * 
203      * @return 成功 {@link #SUCCESS}; 失败 {@link #FAILED}
204      */
205     public static int streamToFile(String path, InputStream is, boolean isAppend) {
206         return streamToFile(new File(path), is, isAppend);
207     }
208 
209     public static int streamToFile(File file, InputStream is, boolean isAppend) {
210         checkParentPath(file);
211 
212         FileOutputStream fos = null;
213         try {
214             fos = new FileOutputStream(file, isAppend);
215             byte[] buf = new byte[BUF_SIZE];
216             int readSize = 0;
217 
218             while ((readSize = is.read(buf)) != -1)
219                 fos.write(buf, 0, readSize);
220             fos.flush();
221 
222             return SUCCESS;
223         } catch (Exception e) {
224         } finally {
225             try {
226                 fos.close();
227             } catch (Exception e) {
228             }
229         }
230 
231         return FAILED;
232     }
233 
234     /**
235      * 写字节数组到文件
236      * 
237      * @param file
238      *            目标文件
239      * @param data
240      *            字节数组
241      * @param offset
242      *            偏移 {@code >=0&&<=data.length}
243      * @param length
244      *            长度 ==0 表示 data.length
245      * @param isAppend
246      *            是否追加
247      * 
248      * @return 成功 {@link #SUCCESS}; 失败 {@link #FAILED}
249      */
250     public static int bytesToFile(File file, byte[] data, int offset,
251             int length, boolean isAppend) {
252         checkParentPath(file);
253 
254         if (data == null)
255             return FAILED;
256 
257         if (length <= 0)
258             length = data.length;
259 
260         FileOutputStream fos = null;
261         try {
262             fos = new FileOutputStream(file, isAppend);
263             fos.write(data, offset, length);
264             fos.flush();
265 
266             return SUCCESS;
267         } catch (Exception e) {
268         } finally {
269             try {
270                 fos.close();
271             } catch (Exception e) {
272             }
273         }
274 
275         return FAILED;
276     }
277 
278     public static int bytesToFile(File file, byte[] data, boolean isAppend) {
279         return bytesToFile(file, data, 0, data.length, isAppend);
280     }
281     
282     public static int bytesToFile(File file, byte[] data) {
283         return bytesToFile(file, data, 0, data.length, false);
284     }
285     
286     public static int stringToFile(File file, String string) {
287         return bytesToFile(file, string.getBytes());
288     }
289 
290     /**
291      * @see {@link #bytesToFile(File file, byte[] data, int offset, int length, boolean isAppend)}
292      */
293     public static int bytesToFile(String path, byte[] data, int offset,
294             int length, boolean isAppend) {
295         return bytesToFile(new File(path), data, offset, length, isAppend);
296     }
297 
298     /**
299      * 读取文件内容到二进制缓冲区
300      * 
301      * @param path
302      *            文件路径
303      * @param offset
304      *            起始位置
305      * @param length
306      *            读取长度 ,0为全部
307      * 
308      * @return 失败 或 length <=0 返回null,成功返回 字节数组
309      */
310     public static byte[] fileToBytes(String path, int offset, int length) {
311         return fileToBytes(new File(path), offset, length);
312     }
313 
314     public static byte[] fileToBytes(File file) {
315         return fileToBytes(file, 0, 0);
316     }
317 
318     public static String fileToString(File file) {
319         byte[] data = fileToBytes(file);
320         return data!=null? new String(data) : null;
321     }
322 
323     /**
324      * 读取文件内容到二进制缓冲区
325      * 
326      * @param path
327      *            文件路径
328      * @param offset
329      *            起始位置
330      * @param length
331      *            读取长度,==0 为全部
332      * 
333      * @return 失败 或 length < 0 返回null,成功返回 字节数组
334      */
335     public static byte[] fileToBytes(File file, int offset, int length) {
336         if (length < 0 || !file.exists())
337             return null;
338 
339         InputStream is = null;
340         try {
341             is = new FileInputStream(file);
342             if (length == 0)
343                 length = is.available();
344             byte[] outBuf = new byte[length];
345             is.read(outBuf, offset, length);
346 
347             return outBuf;
348         } catch (Exception e) {
349         } finally {
350             try {
351                 is.close();
352             } catch (Exception e) {
353             }
354         }
355 
356         return null;
357     }
358 
359     /**
360      * 复制文件, 对于大的文件, 推荐开启一个线程来复制. 防止长时间阻塞
361      * 
362      * @param newPath
363      * @param oldPath
364      * 
365      * @return 成功 {@link #SUCCESS}; 失败 {@link #FAILED}
366      */
367     public static int copyTo(String dstPath, String srcPath) {
368         return copyTo(new File(dstPath), new File(srcPath));
369     }
370 
371     public static int copyTo(File dstFile, File srcFile) {
372         if (fileState(srcFile) != FileState.FState_File) // 源非文件
373             return FAILED;
374 
375         FileInputStream fis = null;
376         try {
377             fis = new FileInputStream(srcFile);
378 
379             return streamToFile(dstFile, fis, false);
380         } catch (Exception e) {
381         } finally {
382             try {
383                 fis.close();
384             } catch (Exception e) {
385             }
386         }
387 
388         return FAILED;
389     }
390 
391     /**
392      * @see {@link #assetToFile(Context context, String assetName, File file)}
393      */
394     public static int assetToFile(Context context, String assetName, String path) {
395         return assetToFile(context, assetName, new File(path));
396     }
397 
398     /**
399      * assets 目录下的文件保存到本地文件
400      * 
401      * @param context
402      * @param assetName
403      *            assets下名字,非根目录需包含路径 a/b.xxx
404      * @param file
405      *            目标文件
406      * 
407      * @return 成功 {@link #SUCCESS}; 失败 {@link #FAILED}
408      */
409     public static int assetToFile(Context context, String assetName, File file) {
410         InputStream is = null;
411 
412         try {
413             is = context.getAssets().open(assetName);
414             return streamToFile(file, is, false);
415         } catch (Exception e) {
416         } finally {
417             try {
418                 is.close();
419             } catch (Exception e) {
420             }
421         }
422 
423         return FAILED;
424     }
425     
426     public static int assetToFileIfNotExist(Context context, String assetName, File file) {
427         InputStream is = null;
428         try {
429             is = context.getAssets().open(assetName);
430             if(!checkExistBySize(file, is.available())) {
431                 return streamToFile(file, is, false);
432             } else {
433                 return SUCCESS;
434             }
435         } catch (Exception e) {
436         } finally {
437             try {
438                 is.close();
439             } catch (Exception e) {
440             }
441         }
442 
443         return FAILED;
444     }
445 
446     /**
447      * 读取 assets 下 name 文件返回字节数组
448      * 
449      * @param context
450      * @param name
451      * @return 失败返回 Null
452      */
453     public static byte[] assetToBytes(Context context, String name) {
454         InputStream is = null;
455 
456         try {
457             is = context.getAssets().open(name);
458             byte[] buf = new byte[is.available()];
459             is.read(buf);
460 
461             return buf;
462         } catch (Exception e) {
463             setLastException(e);
464         } finally {
465             try {
466                 is.close();
467             } catch (Exception e) {
468             }
469         }
470 
471         return null;
472     }
473     
474     /**
475      * 从 Assets 文件读取文件全部,并转为字符串
476      * 
477      * @param manager
478      * @param name
479      *            文件名
480      * @return 读取到的字符串
481      * 
482      * @author Yichou
483      *         <p>
484      *         date 2013-4-2 11:30:05
485      */
486     public static String assetToString(Context context, String name) {
487         byte[] data = assetToBytes(context, name);
488 
489         return data!=null? new String(data) : null;
490     }
491 
492     /**
493      * 检查 assets 下是否存在某文件
494      * 
495      * @param am
496      * @param name
497      * @return
498      */
499     public static boolean assetExist(AssetManager am, String name) {
500         InputStream is = null;
501         try {
502             is = am.open(name);
503             return true;
504         } catch (IOException e) {
505         } finally {
506             try {
507                 is.close();
508             } catch (Exception e) {
509             }
510         }
511 
512         return false;
513     }
514 
515     /**
516      * @return SD卡是否已挂载
517      */
518     public static boolean isSDMounted() {
519         String sdState = Environment.getExternalStorageState(); // 判断sd卡是否存在
520         return sdState.equals(android.os.Environment.MEDIA_MOUNTED);
521     }
522     
523     /**
524      * 2013-9-27 check if the sdcard mounted and can read and wirte, and remain size > {@value minRemainMB}
525      * 
526      * @param minRemainMB unit mb
527      */
528     public static boolean isSDAvailable(int minRemainMB) {
529         if(!isSDAvailable())
530             return false;
531         
532         return (getSDLeftSpace() >= minRemainMB*1024L*1024L);
533     }
534     
535     /**
536      * 2013-9-27 check if the sdcard mounted and can read and wirte
537      */
538     public static boolean isSDAvailable() {
539         if(!isSDMounted())
540             return false;
541             
542         File file = Environment.getExternalStorageDirectory();
543         if(!file.canRead() || !file.canWrite())
544             return false;
545         
546         return true;
547     }
548 
549     /**
550      * @return SD卡剩余容量 
551      */
552     public static long getSDLeftSpace() {
553         if (isSDMounted() == false) {
554             return 0;
555         } else {
556             StatFs statfs = new StatFs(
557                     Environment.getExternalStorageDirectory() + File.separator);
558             return (long) statfs.getAvailableBlocks()
559                     * (long) statfs.getBlockSize();
560         }
561     }
562 
563     public static String coverSize(long size) {
564         String s = "";
565         if (size < 1024)
566             s += size + "b";
567         else if (size < 1024 * 1024) {
568             s = String.format(Locale.getDefault(), "%.1fK", size / 1024f);
569         } else if (size < 1024 * 1024 * 1024) {
570             s = String.format(Locale.getDefault(), "%.1fM", size / 1024 / 1024f);
571         } else {
572             s = String.format(Locale.getDefault(), "%.1fG", size / 1024 / 1024 / 1024f);
573         }
574         
575         return s;
576     }
577 
578     public static long getROMLeft() {
579         File data = Environment.getDataDirectory();
580 
581         StatFs sf = new StatFs(data.getAbsolutePath());
582         long blockSize = sf.getBlockSize();
583         long blockCount = sf.getBlockCount();
584         long availCount = sf.getAvailableBlocks();
585 
586         Log.i("", "ROM Total:" + coverSize(blockSize * blockCount) + ", Left:"
587                 + coverSize(availCount * blockSize));
588 
589         return availCount * blockSize;
590     }
591 
592     /**
593      * 获取私有目录下的文件夹绝对路径,末尾带 "/",不创建
594      * 
595      * @param context
596      * @param name
597      *            文件夹名
598      * @return
599      */
600     public static String getDirPathInPrivate(Context context, String name) {
601         return context.getDir(name, Context.MODE_PRIVATE).getAbsolutePath()
602                 + File.separator;
603     }
604 
605     /**
606      * 或者本应用 so 存放路径
607      * 
608      * @param context
609      * @return
610      */
611     public static String getSoPath(Context context) {
612         return context.getApplicationInfo().dataDir + "/lib/";
613     }
614 
615     public static FileLock tryFileLock(String path) {
616         return tryFileLock(new File(path));
617     }
618 
619     /**
620      * 占用某个文件锁
621      * 
622      * @param file
623      * @return
624      */
625     public static FileLock tryFileLock(File file) {
626         try {
627             checkParentPath(file); // 父目录不存在会导致创建文件锁失败
628 
629             FileOutputStream fos = new FileOutputStream(file);
630             FileLock fl = fos.getChannel().tryLock();
631             if (fl.isValid()) {
632                 Log.i(LOG_TAG, "tryFileLock " + file + " SUC!");
633                 return fl;
634             }
635         } catch (Exception e) {
636             Log.e(LOG_TAG, "tryFileLock " + file + " FAIL! " + e.getMessage());
637         }
638 
639         return null;
640     }
641 
642     public static void freeFileLock(FileLock fl, File file) {
643         if (file != null)
644             file.delete();
645 
646         if (fl == null || !fl.isValid())
647             return;
648 
649         try {
650             fl.release();
651             Log.i(LOG_TAG, "freeFileLock " + file + " SUC!");
652         } catch (IOException e) {
653         }
654     }
655 
656     /**
657      * 截取路径名
658      * 
659      * @return
660      */
661     public static String getPathName(String absolutePath) {
662         int start = absolutePath.lastIndexOf(File.separator) + 1;
663         int end = absolutePath.length();
664         return absolutePath.substring(start, end);
665     }
666 
667     /**
668      * 重命名
669      * 
670      * @param oldName
671      * @param newName
672      * @return
673      */
674     public static boolean reNamePath(String oldName, String newName) {
675         File f = new File(oldName);
676         return f.renameTo(new File(newName));
677     }
678 
679     /**
680      * 列出root目录下所有子目录
681      * 
682      * @param path
683      * @return 绝对路径
684      */
685     public static List<String> listPath(String root) {
686         List<String> allDir = new ArrayList<String>();
687         SecurityManager checker = new SecurityManager();
688         File path = new File(root);
689         checker.checkRead(root);
690         if (path.isDirectory()) {
691             for (File f : path.listFiles()) {
692                 if (f.isDirectory()) {
693                     allDir.add(f.getAbsolutePath());
694                 }
695             }
696         }
697         return allDir;
698     }
699 
700     /**
701      * 删除空目录
702      * 
703      * 返回 0代表成功 ,1 代表没有删除权限, 2代表不是空目录,3 代表未知错误
704      * 
705      * @return
706      */
707     public static int deleteBlankPath(String path) {
708         File f = new File(path);
709         if (!f.canWrite()) {
710             return 1;
711         }
712         if (f.list() != null && f.list().length > 0) {
713             return 2;
714         }
715         if (f.delete()) {
716             return 0;
717         }
718         return 3;
719     }
720 
721     /**
722      * 获取SD卡的根目录,末尾带
723      * 
724      * @return
725      */
726     public static String getSDRoot() {
727         return Environment.getExternalStorageDirectory().getAbsolutePath()
728                 + File.separator;
729     }
730     
731     /**
732      * 2013-10-8 by yichou
733      * 
734      * 检查一个文件本地是否存在,通过名称,长度,如果2者都符合,返回 true,否则返回 false
735      * 
736      * @param file
737      * @param size
738      */
739     public static boolean checkExistBySize(File file, long size) {
740         if(!file.exists() || !file.isFile() || (file.length() != size))
741             return false;
742         
743         return true;
744     }
745     
746     //public static native int setPermissions(String file, int mode, int uid, int gid);
747     public static int setPermissions(String file, int mode) {
748         return setPermissions(file, mode, -1, -1);
749     }
750     
751     private static final Class<?>[] SIG_SET_PERMISSION = 
752         new Class<?>[]{String.class, int.class, int.class, int.class};
753     public static int setPermissions(String file, int mode, int uid, int gid) {
754         try {
755             Class<?> clazz = Class.forName("android.os.FileUtils");
756             Method method = clazz.getDeclaredMethod("setPermissions", SIG_SET_PERMISSION);
757             method.setAccessible(true);
758             return (Integer) method.invoke(null, file, mode, uid, gid);
759         } catch (Exception e) {
760         }
761         
762         return -1;
763     }
764     
765     /**
766      * 把 sd卡上 src 目录 链接到 私有目录 dst
767      * 
768      * <p>例:createLink("/mnt/sdcard/freesky", "/data/data/com.test/app_links/free")
769      * 之后 /data/data/com.test/app_links/free -> /mnt/sdcard/freesky
770      * 
771      * @param src 源目录,在SD卡上
772      * @param dst 目标路径完整
773      * @return
774      */
775     public static boolean createLink(String src, String dst) {
776         try {
777             String command = String.format("ln -s %s %s", src, dst);
778             Runtime runtime = Runtime.getRuntime();
779             Process ps = runtime.exec(command);
780             InputStream in = ps.getInputStream();
781             
782             int c;
783             while ((c = in.read()) != -1) {
784                 System.out.print(c);// 如果你不需要看输出,这行可以注销掉
785             }
786             
787             in.close();
788             ps.waitFor();
789             
790             return true;
791         } catch (Exception e) {
792         }
793         
794         return false;
795     }
796     
797     /**
798      * 读取输入流全部内容到字节数组
799      * 
800      * @param is 输入流,传入者关闭
801      * 
802      * @return 成功 数组,失败 null
803      * 
804      * 2014-9-26
805      */
806     public static ByteArrayBuffer streamToByteArray(InputStream is) {
807         try {
808             byte[] buf = new byte[256];
809             int read = 0;
810             ByteArrayBuffer array = new ByteArrayBuffer(1024);
811             
812             while ((read = is.read(buf)) != -1) {
813                 array.append(buf, 0, read);
814             }
815             
816             return array;
817         } catch (Exception e) {
818             setLastException(e);
819         }
820         
821         return null;
822     }
823     
824     /**
825      * 读取输入流全部,转为字符串
826      * 
827      * @param is 输入流,传入者关闭
828      * 
829      * @return 成功 字串,失败 null
830      * 
831      * 2014-9-26
832      */
833     public static String streamToString(InputStream is) {
834         ByteArrayBuffer buffer = streamToByteArray(is);
835         if(buffer != null)
836             return new String(buffer.buffer(), 0 , buffer.length());
837         
838         return null;
839     }
840     
841     public static void printLastException() {
842         Exception e = getLastException();
843         if(e != null)
844             e.printStackTrace();
845     }
846     
847     private static void setLastException(Exception e) {
848         exReference = new WeakReference<Exception>(e);
849     }
850     
851     public static Exception getLastException() {
852         return exReference!=null? exReference.get() : null;
853     }
854 }
原文地址:https://www.cnblogs.com/yichouangle/p/3150603.html