需求:java
有時候應用修復了native層一個小BUG,應用須要更新了,可是用戶必須下載整個APK包進行安裝,而咱們須要的只是替換SOandroid
因而想,能不能加載自定義路徑下的 SO 文件呢apache
答案是徹底沒問題:數組
使用系統方法:服務器
void java.lang.System.load(String pathName)
可是有一點,pathName 路徑必須有執行權限,意思就是說咱們不能加載SD卡上的SO,由於沒有執行權限app
那也不要緊,咱們複製到應用私有目錄下就OK嘛。ide
看碼工具
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,而後加載,這樣就能夠避免用戶安裝新的應用,ui
要知道從新安裝應用的用戶體驗是不好的,要讓用戶無感知的更新他。
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 }