1. 程式人生 > >Android中的ClassLoader與dex檔案加密實現分析

Android中的ClassLoader與dex檔案加密實現分析

Android中的ClassLoader

這裡寫圖片描述

BaseDexClassLoader

Dex類載入器的基類,包含Dex類載入器之間通用功能的實現。

DexClassLoader

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

一個可以從包含classes.dex實體的.jar或.apk檔案中載入classes的類載入器。可以用於實現dex的動態載入、程式碼熱更新等等。

This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

這個類載入器必須要一個app的私有、可寫目錄來快取經過優化的classes(odex檔案),使用Context.getDir(String, int)方法可以建立一個這樣的目錄。 
示例: File dexOutputDir = context.getDir(“dex”, 0);

PathClassLoader

Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).

提供一個簡單的ClassLoader實現,可以操作在本地檔案系統的檔案列表或目錄中的classes,但不可以從網路中載入classes。

DexClassLoader與PathClassLoader

DexClassLoader與PathClassLoader的區別就是DexClassLoader可以載入本地或者網路的classes,而PathClassLoader只能載入本地的classes。

DexClassLoader原始碼分析

以下是DexClassLoader的建構函式,原始碼是來自Android2.3的系統原始碼,Dex檔案的載入就是在建構函式中實現的。接收3個引數dexPath、dexOutputDir、libPath、parent。

  • dexPath:dex檔案路徑列表,多個路徑使用”:”分隔
  • dexOutputDir:經過優化的dex檔案(odex)檔案輸出目錄
  • libPath:動態庫路徑(將被新增到app動態庫搜尋路徑列表中)
  • parent:這個一個ClassLoader,這個引數的主要作用是保留java中ClassLoader的委託機制(優先父類載入器載入classes,由上而下的載入機制,防止重複載入類位元組碼)。
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * The path lists are separated using the character specified by
     * the "path.separator" system property, which defaults to ":".
     *
     * @param dexPath
     *  the list of jar/apk files containing classes and resources
     * @param dexOutputDir
     *  directory where optimized DEX files should be written
     * @param libPath
     *  the list of directories containing native libraries; may be null
     * @param parent
     *  the parent class loader
     */
    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
        ClassLoader parent) {

        super(parent);

        if (dexPath == null || dexOutputDir == null)
            throw new NullPointerException();

        mRawDexPath = dexPath;
        mDexOutputPath = dexOutputDir;
        mRawLibPath = libPath;

        String[] dexPathList = mRawDexPath.split(":");
        int length = dexPathList.length;

        //System.out.println("DexClassLoader: " + dexPathList);
        mFiles = new File[length];
        mZips = new ZipFile[length];
        mDexs = new DexFile[length];

        /* open all Zip and DEX files up front */
        for (int i = 0; i < length; i++) {
            //System.out.println("My path is: " + dexPathList[i]);
            File pathFile = new File(dexPathList[i]);
            mFiles[i] = pathFile;

            if (pathFile.isFile()) {
                try {
                    mZips[i] = new ZipFile(pathFile);
                } catch (IOException ioex) {
                    // expecting IOException and ZipException
                    System.out.println("Failed opening '" + pathFile
                        + "': " + ioex);
                    //ioex.printStackTrace();
                }

                /* we need both DEX and Zip, because dex has no resources */
                try {
                    String outputName =
                        generateOutputName(dexPathList[i], mDexOutputPath);
                    mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
                } catch (IOException ioex) {
                    // might be a resource-only zip
                    System.out.println("Failed loadDex '" + pathFile
                        + "': " + ioex);
                }
            } else {
                if (VERBOSE_DEBUG)
                    System.out.println("Not found: " + pathFile.getPath());
            }
        }

        /*
         * Prep for native library loading.
         */
        String pathList = System.getProperty("java.library.path", ".");
        String pathSep = System.getProperty("path.separator", ":");
        String fileSep = System.getProperty("file.separator", "/");

        if (mRawLibPath != null) {
            if (pathList.length() > 0) {
                pathList += pathSep + mRawLibPath;
            }
            else {
                pathList = mRawLibPath;
            }
        }

        mLibPaths = pathList.split(pathSep);
        length = mLibPaths.length;

        // Add a '/' to the end so we don't have to do the property lookup
        // and concatenation later.
        for (int i = 0; i < length; i++) {
            if (!mLibPaths[i].endsWith(fileSep))
                mLibPaths[i] += fileSep;
            if (VERBOSE_DEBUG)
                System.out.println("Native lib path " +i+ ":  " + mLibPaths[i]);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

以上程式碼大概可以總結為以下幾個步驟: 
1. super(parent),呼叫父類建構函式,關聯父類ClassLoader。 
2. 對dexPath進行分割,得到dex檔案路徑列表dexPathList。 
3. 迭代dexPathList,呼叫DexFile的靜態方法loadDex載入dex檔案。 
4. 通過System.getProperty(“Java.library.path”, “.”)獲取app動態庫搜尋路徑列表,並把libPath新增其後。

使用DexClassLoader載入dex檔案

把jar轉換為dex

這裡寫圖片描述

1.首先我編寫了一個Test類,注意我這裡讓Test繼承自Date,並匯出為jar2dex.jar

package linchaolong.jar2dex.test;

import java.util.Date;

public class Test extends Date{

    @Override
    public String toString() {
        return "linchaolong";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

dex2jar裡一個指令碼d2j-jar2dex可以將jar轉換為dex

2.把jar拷貝到dexjar解壓目錄下執行命令:d2j-jar2dex xxx.jar -o xxx.dex(把xxx.jar轉換為xxx.dex) 
這裡寫圖片描述

這裡寫圖片描述

實現dex檔案的加密解密

dex檔案的加密解密演算法使用C/C++實現,使用NDK編譯成動態庫,通過jni呼叫加密解密演算法對dex檔案實現加密、解密。

以下是java層中加密、解密演算法的介面

package linchaolong.utils;

/**
 * 資料加密解密工具
 * 
 * @author linchaolong
 *
 */
public class DataProtector {

    static{
        // 載入動態庫,資料的加密解密演算法實現在動態庫中
        System.loadLibrary("dataProtector");  
    }

    /**
     * 加密資料
     * 
     * @param buff 資料
     * @param size  資料大小
     * @return 加密後的資料
     */
    public native static byte[] encrypt(byte[] buff, int size);

    /**
     * 解密資料
     * 
     * @param buff 資料
     * @param size  資料大小
     * @return  解密後的資料
     */
    public native static byte[] decrypt(byte[] buff, int size);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

解密dex檔案實現(注意:這裡的解密實現是比較簡單的,實際應用中應該做程式碼混淆或放到C/C++中實現,而且注意解密檔案的保護)

package linchaolong.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;

/**
 * Dex檔案保護工具類
 * 
 * @author linchaolong
 *
 */
public class DexProtector {

    private static final int BUFF_SIZE = 1024*1024;
    private static final String TAG = "DexProtector";

    /**
     * 解密Dex檔案
     * 
     * @param in 加密檔案輸入流
     * @param outFile   輸出解密檔案
     * @return  是否解密成功
     */
    public static boolean decryptDex(InputStream in, File outFile){
        // 加密dex檔案所在目錄
        File outDir = outFile.getParentFile();

        // 如果目錄不存在則建立
        if (!outDir.exists() && outDir.isDirectory()) {
            outDir.mkdirs();
        }
        try {
            if (outFile.exists()) {
                outFile.delete();
            }

            FileOutputStream out = new FileOutputStream(outFile);

            byte[] buff = new byte[BUFF_SIZE];
            int len = 0;

            while((len = in.read(buff)) != -1){
                // 呼叫native方法解密dex檔案資料
                byte[] decryptBuff = DataProtector.decrypt(buff, len);
                out.write(decryptBuff, 0, len);
            }

            // 釋放資源
            in.close();
            out.close();

            return true;

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    public static boolean decryptDex(File encryptFile, File outFile){

        if (!encryptFile.exists()) {
            Log.e(TAG, "加密檔案 '" + encryptFile.getPath() + " ''不存在");
            return false;
        }

        try {
            return decryptDex(new FileInputStream(encryptFile), outFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

實現dex檔案的動態載入

1.新建一個Application,並重寫onCreate方法,用於在應用啟動的時候解密並載入dex檔案。

我這裡把加密的test.dex放到了assets目錄下,並命名為encrypt.dex 
這裡寫圖片描述

以下是Application的原始碼:

package linchaolong.dexproctor;

import java.util.Date;
import linchaolong.utils.DataProtector;
import linchaolong.utils.DexProtector;
import android.app.Application;
import android.content.res.AssetManager;
import android.util.Log;
import dalvik.system.DexClassLoader;

public class DexApplicatoin extends Application{

    private static final String TAG = "DexApplicatoin";

    @Override
    public void onCreate() {
        testDexLoader();
        super.onCreate();
    }

    /**
     *  動態載入dex實現
     */
    private void testDexLoader() {
        try {
            // 解密dex檔案的File物件
            File decryptFile = new File(getDir("dex",MODE_PRIVATE), "test.dex");
            // 經過優化的dex輸出目錄
            File odexDir = getDir("odex_dir", MODE_PRIVATE);

            try {
                // 讀取assets目錄下的encrypt.dex並解密
                InputStream encryptDexIn = getAssets().open("encrypt.dex");
                DexProtector.decryptDex(encryptDexIn, decryptFile);
            } catch (IOException e1) {
                e1.printStackTrace();
                return;
            }

            // 建立類載入器,載入解密後的dex檔案
            ClassLoader dexClassLoader = new DexClassLoader(decryptFile.getPath(), odexDir.getPath(), null, getClassLoader());

            // 載入Test類
            String className = "linchaolong.jar2dex.test.Test";
            Class<?> testClass = dexClassLoader.loadClass(className);

            if (testClass == null) {
                Log.e(TAG,"ClassNotFoundException : can not found class " + className);
            }else{
                try {
                    // 建立Test物件,Test繼承自Date,這裡用Date引用Test物件
                    Date testObj = (Date) testClass.newInstance();
                    if (testObj == null) {
                        Log.e(TAG,"testObj is null ");
                    }else{
                        // 呼叫Test物件的toStirng方法
                        Log.e("Test", "testObj.toString() = " + testObj.toString()); 
                    }
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

2.在AndroidManifest.xml中配置Application。 
這裡寫圖片描述

執行結果: 
這裡寫圖片描述 
最終列印結果與Test中toString方法實現一致,表示dex解密並載入成功了

相關推薦

AndroidClassLoaderdex檔案加密實現分析

Android中的ClassLoader BaseDexClassLoader Dex類載入器的基類,包含Dex類載入器之間通用功能的實現。 DexClassLoader A class loader that loads classes from .jar

AndroidWebview原生介面互動及二維碼掃描功能實現

最近專案中有一個新的需求,大致是這樣的:APP中通過WebView展示一個第三方的HTML5介面,使用者可以在HTML5介面中呼叫Android攝像頭進行二維碼掃描,並將掃描結果顯示在HTML5介面。這顯然涉及到了Android原生與WebView之前的傳值

Android三種onClick事件的實現對比

在達內培訓,剛接觸android不久,對於button的onclick事件有點糾結。因為上了兩三天就換了個老師,兩個老師講onclick事件用了兩種不同的實現方法,我才發現可以有好幾種方式實現。 方式一:在activity的onCreate()方法中,嵌入如下程式碼: Bu

shape檔案用法二:在Android,用XML檔案實現圓角的效果

需求:好多時候,需要給元件加入圓角的效果,如下圖所示: 現在,要做的就是,實現 圖中所示的圓角效果。 第一步:在 res/drawable檔案下新增 ,檔案: btn_background_shape.xml 檔案。 /TestShape2/res/drawable/

AndroidXposed框架篇-微信實現本地視頻發布到朋友圈功能

快速定位 adb 本地 ref jad jadx mps 頁面 視頻 微信非常龐大,還好有一些強大的工具,下面就來總結收獲的知識。 一、使用adb shell dumpsys activity top命令快速定位頁面 二、使用Jadx進行方法跟蹤時候如果發現沒有結

AndroidwindowTranslucentStatuswindowTranslucentNavigation的一些設置(轉)

windows 屏幕 contex 拉伸 新的 system 整體 navbar 額外 在iOS中,你可能發現頁面會整體拉升到狀態欄,整個頁面效果就會顯得更加的高端大氣上檔次,在Android4.4以後其實也有這種效果的實現,下面我就說一下在進行這種效果實現時碰到的一些坑,

JAVA所有集合有關的實現類都是這六個接口的實現

length 數字 pack 有關 結構 [] rgs val 無序 JAVA中所有與集合有關的實現類都是這六個接口的實現類。 Collection接口:集合中每一個元素為一個對象,這個接口將這些對象組織在一起,形成一維結構。 List接口代表按照元素一定的相關順序

iOSHTMLOC的互動實現

   近日,由於開發需求,涉及到書寫HTML與OC互動的問題。特此做了一番研究,學習到了一點,所以,也就當是做個簡單的筆記。希望可以對有需求的人有幫助,就像我自己學習的時候,總是要到處翻書,到處搜尋。把這些問題解決掉。     以下為正文內容: &

轉:AndroidIntentServiceService的區別

https://blog.csdn.net/matrix_xu/article/details/7974393 Android中的Service是用於後臺服務的,當應用程式被掛到後臺的時候,問了保證應用某些元件仍然可以工作而引入了Service這個概念,那麼這裡面要強調的是Service不是獨立

androidokhttpwebview的cookie共享

轉載請註明出處:https://blog.csdn.net/u011038298/article/details/84551136   1.在WebView中同步cookie import android.os.Build; import android.text.Text

針對記憶體完整的dex檔案的ida除錯脫殼思路

使用IDA從記憶體中dump指定的dex 雖然自己編譯了一套能夠簡單夠脫殼的壞境,不過使用上總感覺比較重量級。 今天只想把APK中某個動態解密,載入的dex搞出來,用IDA輕快很多。 步驟1: 首先通過cat /proc/pid/maps檢視目標dex檔案所在的記憶體地址: 可以

JavaClassLoaderClass.forName的區別

Java中ClassLoader與Class.forName的區別 package com.demo.classLoader; public class Main { public static void main(String[] args) {

AndroidonTouchonClick兩種監聽的完全解析

之前專案中做一個豎直方向的ViewPager效果(詳見我的另一篇博文),這幾天做了幾個改動,突然發現我設定的OnTouchListener對觸控事件的監聽突然不起作用了,琢磨了半天覺得問題就出在onTouch的返回值true還是false上,後來自己測試的時候發現不光與這個有關,與OnClickLi

android的跨程序通訊的實現(一)——遠端呼叫過程和aidl

android在設計理念上強調元件化,元件之間的依賴性很小。我們往往發一個intent請求就可以啟動另一個應用的activity,或者一個你不知道在哪個程序的service,或者可以註冊一個廣播,只要有這個事件發生你都可以收到,又或者你可以查詢一個contentProvider獲得你想要的資料,這其

Android 介面卡fragment或者activity的回撥使用

如何使用介面回撥       使用場景:在activity或者fragment與adapter的回撥中        介面卡中使用: public OnUpdat

java檔案,class檔案dex檔案的轉化。(詳細教程)

筆者這兩天整理的關於安卓逆向的一些小知識:教你如何在這三種檔案中來去自如: .java檔案 Java原始檔 .class檔案 Java位元組碼檔案,是一種能夠被Java虛擬機器(JVM:Java Virtual Machine)識別,載入並且執行的檔案格式。 .

詳解Android的build.gradle檔案

一、什麼是Gradle 簡單的說,Gradle是一個構建工具,它是用來幫助我們構建app的,構建包括編譯、打包等過程。我們可以為Gradle指定構建規則,然後它就會根據我們的“命令”自動為我們構建app。Android Studio中預設就使用Gradle來完成應用的構建。有些同學可能會有疑問:”我用AS不記

[譯] 最佳安全實踐:在 Java 和 Android 使用 AES 進行對稱加密

原文地址:Security Best Practices: Symmetric Encryption with AES in Java and Android 原文作者:Patrick Favre-Bulle 最佳安全實踐:在 Java 和 Android 中使用 AES 進行

Android儲存圖片到本地功能實現

文章轉載自http://blog.csdn.net/ccpat/article/details/45314175  感謝原作者~ 本文描述將一個Bitmap物件儲存為一個圖片檔案的主要步驟。儲存的圖片檔案能夠立刻在系統相簿和相簿中找到。 我使用的是一張drawabl

AndroidWebviewjs互動

1.js呼叫Android程式碼Android端:webView.addJavascriptInterface(new WebHost(this),"js");向WebView註冊一個名叫“js”的物件,然後在JS中可以訪問js這個物件,呼叫這個物件裡的一些方法。 publi