Crosswalk 學(xué)習(xí)筆記

簡介

CrossWalk是一款為HTML應(yīng)用提供運行時環(huán)境的開源項目魄眉,從Android開發(fā)的角度講Crosswalk可以用來替代WebView顯示網(wǎng)頁兢哭,在應(yīng)用重度依賴網(wǎng)頁時可以使用Crosswalk解決設(shè)備碎片化問題。直接將Crosswalk的庫包導(dǎo)入到項目中即可像使用WebView一樣使用,這種模式在Crosswalk項目中叫內(nèi)嵌模式,但這樣有一個問題由于Crosswalk庫包太大集成后的應(yīng)用體積太大始赎,為此Crosswalk為Android設(shè)備提供了另外兩種使用方式檬某,共享模式下載模式。雖然Crosswalk項目
已經(jīng)在17年停止維護枫甲,但它這種動態(tài)加載使用的方式仍值得我學(xué)習(xí)。本文將介紹Crosswalk的三種加載方式。

使用

內(nèi)嵌模式

Crosswalk在它的下載地址提供了很多資源想幻,使用內(nèi)嵌模式只需要下載對應(yīng)的aar包粱栖,官方提供的aar包分為兩種,一種是32位的另一種是64位脏毯,32位的aar中只包含x86和armeabi-v7a兩種so文件闹究,同理64位的包中只包含x86_64和arm64-v8a兩種so文件,由于arm64-v8a平臺能兼容32位的so文件抄沮、x86_64也能兼容32位的x86 so文件跋核,在不考慮性能(暫時未知性能問題)的情況下就可以直接集成32位的aar包岖瑰。需要在build.gradle文件中指定只打包32位的so文件叛买,防止有其他依賴項目有64位的so文件,造成在運行時沒有對應(yīng)的crosswalk包蹋订。

android {
defaultConfig {
    ndk {
        abiFilters 'armeabi-v7a','x86'
    }
}
}

以上是內(nèi)嵌模式下的一種集成方式率挣。由于官方只提供以上2種aar,如果只想集成arm平臺下的so文件或者一定要不同的平臺加載對應(yīng)的so文件露戒,那只有自己新建Android library module了椒功,到官網(wǎng)下載crosswalk.xx.zip和crosswalk.xx-64bit.zip,解壓后如圖把xwalk_core_library\libs目錄下的對應(yīng)so文件復(fù)制到新建的module的jniLibs下、把xwalk_core_library.jar復(fù)制到libs下智什,最后把xwalk_core_library\res目錄下所有資源都復(fù)制到module中


xwalk_core_library\libs目錄下
復(fù)制到module后

由于module要對外提供xwalk_core_library.jar中的api所以要將build.gradle中依賴方式改為api

dependencies {
    api fileTree(dir: 'libs', include: ['*.jar'])
}

兩種內(nèi)嵌集成方式都可行动漾,集成后就可以使用crosswalk了,不做任何額外操作的情況下默認(rèn)是內(nèi)嵌模式。在XWalkView的構(gòu)造方法中會加載so文件荠锭,之后就可以使用了旱眯,和使用WebView一樣XWalkUIClient對應(yīng)WebView的WebChromeClient,XWalkResourceClient對應(yīng)WebViewClient

共享模式

顧名思義共享模式就是和其他應(yīng)用共用一個XWalkRuntime证九,XWalkRuntimeLib并沒有被內(nèi)嵌到自己的應(yīng)用中删豺,而是當(dāng)應(yīng)用要用到XWalkView時去加載包名為org.xwalk.core的應(yīng)用的代碼,如果沒有安裝就去下載XWalkRuntimeLib.APK安裝愧怜,官方提供了兩種下載APK的方式呀页,一種是跳轉(zhuǎn)Google Play Store下載,一種是在manifest文件中自定義下載鏈接的meta-data拥坛,到自己指定的地址去下載APK蓬蝶。第一種方式不可行,在大部分手機中market://details?id=xxx 這種URI跳轉(zhuǎn)的是自家的應(yīng)用商店猜惋,并沒有XWalkRuntimeLib.APK疾党。由于Crosswalk項目已經(jīng)停止更新了,
第二種方式會因為文件權(quán)限問題在Android 7.0之后的手機上拋錯惨奕。雖然這種模式基本被廢了雪位,但這種方式與下載模式大同小異,也值得分析下梨撞。

需要在官網(wǎng)下載crosswalk-shared-xx.aar并將其導(dǎo)入到項目雹洗,讓Activity繼承XWalkActivity并復(fù)寫它的onXWalkReady()方法香罐,在這個方法中使用XWalkView加載網(wǎng)頁。繼承了XWalkActivity的情況下时肿,如果不做任何配置默認(rèn)的是共享模式并且是第一種方式:跳轉(zhuǎn)Google Play Store下載APK庇茫。

import org.xwalk.core.XWalkActivity;
import org.xwalk.core.XWalkView;

public class MainActivity extends XWalkActivity {

    XWalkView xWalkView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        xWalkView = findViewById(R.id.xwalk);
    }

    @Override
    protected void onXWalkReady() {
        xWalkView.loadUrl("https//:www.baidu.com");

    }


}

第二種加載方式與第一種方式一樣需要繼承XWalkActivity,之后在官網(wǎng)下載4個平臺的APK并部署到自己的服務(wù)器上螃成,在manifest文件中配置下載路徑的meta-data旦签。

<meta-data
  android:name="xwalk_apk_url"
  android:value="http://192.168.1.236:8080/so/XWalkRuntimeLib.apk" />

crosswalk會自動判斷手機的平臺并在下載路徑后添加cpuArch=xxx鍵值對,取值有x86寸宏、x86_64宁炫、armeabi-v7a、arm64-v8a,服務(wù)器根據(jù)不同值返回相應(yīng)的APK就OK了氮凝。

下載模式

和共享模式差不多羔巢,只是共享模式是把APK下載下來當(dāng)成一個應(yīng)用安裝到手機上,而下載模式干脆把APK下載到自己的私有目錄下罩阵,把所有的so文件竿秆、資源解壓出來保存到自己的內(nèi)部私有目錄下只供自己使用。在manifest文件中增加配置xwalk_download_mode為enable稿壁,共享模式秒變下載模式幽钢。

 <meta-data
    android:name="xwalk_apk_url"
    android:value="http://192.168.1.236:8080/video/so/XWalkRuntimeLib.apk" />
 <meta-data
    android:name="xwalk_download_mode"
    android:value="enable" />

除了像共享模式那樣繼承XWalkActivity,下載模式下還可以使用XWalkInitializer傅是,這樣就可以讓Activity更靈活了匪燕,需要注意的是如果在布局中就使用了XWalkView則必須在setContentView()方法之前就要新建mXWalkInitializer對象,如果不這樣做會拋錯落午,原因是setContentView()加載布局時會初始化XWalkView谎懦,在XWalkView的構(gòu)造方法中會反射加載初始化相關(guān)類,可是還沒有下載相關(guān)類啊溃斋,只有拋錯了界拦。而XWalkInitializer的構(gòu)造方法中會調(diào)用preInit(),這個方法就是告訴加載框架我知道還沒有下載相關(guān)的類梗劫,我先在你這里注冊下占個坑享甸,如果下載初始化好了就通知我繼續(xù)初始化XWalkView,共享模式中事先也沒有相關(guān)的類怎么沒這樣的限制呢梳侨?其實在父類XWalkActivity的onCreate()方法中就幫忙注冊了蛉威。下面是使用XWalkInitializer的代碼:

public class MainActivity extends AppCompatActivity  implements XWalkInitializer.XWalkInitListener,
        XWalkUpdater.XWalkBackgroundUpdateListener {

    private static final String TAG = "SplashActivity";
    XWalkInitializer mXWalkInitializer;
    XWalkUpdater mXWalkUpdater;
    XWalkView mXWalkView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mXWalkInitializer = new XWalkInitializer(this, this);
        mXWalkInitializer.initAsync();
        setContentView(R.layout.activity_splash);
    }

    @Override
    public void onXWalkInitStarted() {
    }

    @Override
    public void onXWalkInitCancelled() {
        finish();
    }

    @Override
    public void onXWalkInitFailed() {
        // Initialization failed. Trigger the Crosswalk runtime download
        if (mXWalkUpdater == null) {
            mXWalkUpdater = new XWalkUpdater(this, this);
        }
        mXWalkUpdater.updateXWalkRuntime();
    }

    @Override
    public void onXWalkInitCompleted() {
        // Initialization successfully, ready to invoke any XWalk embedded API
        Log.d(TAG,"onXWalkInitCompleted");
        mXWalkView =  findViewById(R.id.xwalk);
        mXWalkView.loadUrl("https://www.baidu.com");
    }

    @Override
    public void onXWalkUpdateStarted() {
    }

    @Override
    public void onXWalkUpdateProgress(int percentage) {
        Log.d(TAG, "XWalkUpdate progress: " + percentage);
    }

    @Override
    public void onXWalkUpdateCancelled() {
        finish();
    }

    @Override
    public void onXWalkUpdateFailed() {
        finish();
    }

    @Override
    public void onXWalkUpdateCompleted() {
        // Crosswalk Runtime update finished, re-init again.
        mXWalkInitializer.initAsync();
    }
}

Crosswalk加載代碼淺析

Crosswalk的全部加載代碼在其Github項目地址的runtime/android/core/路徑下,如圖所示

全部加載代碼

代碼不多,XWalkActivity暴露出來用于繼承走哺;XWalkActivityDelegate聚合在XWalkActivity中蚯嫌,將加載的邏輯和Activity的生命周期結(jié)合起來;XWalkCoreWrapper負(fù)責(zé)加載初始化so文件;官網(wǎng)還提供壓縮版的APK择示,XWalkDecompressor負(fù)責(zé)解壓壓縮的so文件束凑、資源;XWalkDialogManager在共享模式下為用戶提供提示栅盲、選擇彈窗汪诉;XWalkEnvironment提供運行環(huán)境信息,比如cpuArch谈秫、manifest中配置的參數(shù)扒寄;XWalkLibraryLoader是最主要的類,管理協(xié)調(diào)整個加載過程拟烫。而實際加載so文件该编、調(diào)用native方法的類在/runtime/android/core_internal/目錄下,這也就是上文提到XWalk初始化相關(guān)類构灸,這些類與so文件上渴、資源一起出現(xiàn)岸梨,比如為內(nèi)嵌模式提供的jar喜颁、aar中有internal包,而為共享曹阔、下載模式提供的包中就沒有半开。

內(nèi)嵌模式

在XWalkView的構(gòu)造方法中會調(diào)用reflectionInit()方法,顧名思義也就是要去使用反射初始化類了赃份,方法中調(diào)用XWalkCoreWrapper.initEmbeddedMode(),這個方法中會新建一個XWalkCoreWrapper對象寂拆,進而調(diào)用實例的findEmbeddedCore(),其中分別調(diào)用checkCoreVersion()抓韩、checkCoreArchitecture()確認(rèn)內(nèi)核的版本纠永、so文件是否與cpuArch匹配,在checkCoreVersion()方法中調(diào)用getBridgeClass("XWalkCoreVersion")去反射加org.xwalk.core.internal.XWalkCoreVersion,前面提到了internal包與內(nèi)核同時出現(xiàn)谒拴,如果加載失敗了就證明“XWalk core not found”報錯尝江。XWalkCoreVersion保存的是內(nèi)核版本信息、XWalkAppVersion保存的是集成的aar或jar包的版本信息英上,在內(nèi)嵌模式中這兩個沒區(qū)別的炭序。在checkCoreArchitecture()方法中加載org.xwalk.core.internal.XWalkViewDelegate的loadXWalkLibrary()方法,這個方法中才會去真正的加載so文件苍日,由于是內(nèi)嵌模式so文件的位置由系統(tǒng)決定惭聂,放在了data/app/package name/lib/目錄下,直接調(diào)用的是 System.loadLibrary(lib)加載相恃,最后加載成功后在XWalkView中反射初始化XWalkViewBridge以及確立XWalkView中的方法與XWalkViewBridge反射方法對應(yīng)關(guān)系辜纲,比如XWalkView.loadUrl()對應(yīng)的XWalkViewBridge中的loadUrlSuper()方法,完畢后就可以愉快的使用了。

  public XWalkView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ......
        this.reflectionInit();
    }
    void reflectionInit() {
        XWalkCoreWrapper.initEmbeddedMode();
    }
    //XWalkCoreWrapper中
    public static void initEmbeddedMode() {
        if (sInstance != null || !sReservedActivities.isEmpty()) return;

        Log.d(TAG, "Init embedded mode");
        XWalkCoreWrapper provisionalInstance = new XWalkCoreWrapper(null, -1);
        //查找so文件
        if (!provisionalInstance.findEmbeddedCore()) {
            throw new RuntimeException(
                    "Please have your activity extend XWalkActivity for shared mode");
        }

        sInstance = provisionalInstance;
        sInstance.initCoreBridge();
    }

    private boolean findEmbeddedCore() {
        mBridgeContext = null;
     
        mBridgeLoader = XWalkCoreWrapper.class.getClassLoader();
        //分別確認(rèn)版本和so文件的abi
        if (!checkCoreVersion() || !checkCoreArchitecture()) {
            mBridgeLoader = null;
            return false;
        }

        Log.d(TAG, "Running in embedded mode");
        mCoreStatus = XWalkLibraryInterface.STATUS_MATCH;
        return true;
    }


    private boolean checkCoreVersion() {
        Log.d(TAG, "[Environment] SDK:" + Build.VERSION.SDK_INT);
        Log.d(TAG, "[App Version] build:" + XWalkAppVersion.XWALK_BUILD_VERSION
                + ", api:" + mApiVersion + ", min_api:" + mMinApiVersion);

        try {
            Class<?> clazz = getBridgeClass("XWalkCoreVersion");
            String buildVersion = "";
            try {
                buildVersion = (String) new ReflectField(clazz, "XWALK_BUILD_VERSION").get();
            } catch (RuntimeException e) {
            }
            int libVersion = (int) new ReflectField(clazz, "API_VERSION").get();
            int minLibVersion = (int) new ReflectField(clazz, "MIN_API_VERSION").get();
            Log.d(TAG, "[Lib Version] build:" + buildVersion
                    + ", api:" + libVersion + ", min_api:" + minLibVersion);

            if (XWalkEnvironment.isDownloadMode() && XWalkEnvironment.isDownloadModeUpdate()
                    && !buildVersion.isEmpty()
                    && !buildVersion.equals(XWalkAppVersion.XWALK_BUILD_VERSION)) {
                mCoreStatus = XWalkLibraryInterface.STATUS_RUNTIME_MISMATCH;
                return false;
            }

            if (mMinApiVersion > libVersion) {
                mCoreStatus = XWalkLibraryInterface.STATUS_OLDER_VERSION;
                return false;
            } else if (mApiVersion < minLibVersion) {
                mCoreStatus = XWalkLibraryInterface.STATUS_NEWER_VERSION;
                return false;
            }
        } catch (RuntimeException e) {
            Log.d(TAG, "XWalk core not found");
            mCoreStatus = XWalkLibraryInterface.STATUS_NOT_FOUND;
            return false;
        }

        Log.d(TAG, "XWalk core version matched");
        return true;
    }

    private boolean checkCoreArchitecture() {
        try {
            Class<?> clazz = getBridgeClass("XWalkViewDelegate");
            ReflectMethod method = new ReflectMethod(clazz, "loadXWalkLibrary",
                    Context.class, String.class);

            boolean architectureMatched = false;
            String libDir = null;
            if (mBridgeContext != null) {
                .......
            } else {//下載模式mBridgeContext為null
                try {
                    architectureMatched = (boolean) method.invoke(mBridgeContext, libDir);
                } catch (RuntimeException ex) {
                    Log.d(TAG, ex.getLocalizedMessage());
                }

                if (!architectureMatched && mWrapperContext != null) {
                    libDir = XWalkEnvironment.getPrivateDataDir();
                    architectureMatched = (boolean) method.invoke(mBridgeContext, libDir);
                }
            }

            if (!architectureMatched) {
                Log.d(TAG, "Mismatch of CPU architecture");
                mCoreStatus = XWalkLibraryInterface.STATUS_ARCHITECTURE_MISMATCH;
                return false;
            }
        } catch (RuntimeException e) {
            Log.d(TAG, e.getLocalizedMessage());
            if (e.getCause() instanceof UnsatisfiedLinkError) {
                mCoreStatus = XWalkLibraryInterface.STATUS_ARCHITECTURE_MISMATCH;
                return false;
            }
            mCoreStatus = XWalkLibraryInterface.STATUS_INCOMPLETE_LIBRARY;
            return false;
        }

        Log.d(TAG, "XWalk core architecture matched");
        return true;
    }

內(nèi)嵌模式的加載就這些耕腾,一句話:在第一次新建XWalkView對象時就去通過反射加載so文件屋摇。同一個包中為什么不直接調(diào)用,而用反射去加載幽邓?這就是為了和接下來要說的共享炮温、下載模式統(tǒng)一。

共享模式

1牵舵、跳轉(zhuǎn)Google Play 下載

共享模式需要繼承XWalkActivity柒啤,在onCreate()方法中會新建XWalkActivityDelegate對象
,加載的起端就在XWalkActivityDelegate的構(gòu)造方法中傻谁,它會調(diào)用XWalkLibraryLoader.prepareToInit(mActivity)钳榨,這就是前面說的這個預(yù)先初始化,過程如下:

public abstract class XWalkActivity extends Activity {
    private XWalkActivityDelegate mActivityDelegate;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivityDelegate = new XWalkActivityDelegate(this, cancelCommand, completeCommand);
    }
}
//XWalkActivityDelegate的構(gòu)造方法
public XWalkActivityDelegate(Activity activity,
        Runnable cancelCommand, Runnable completeCommand) {
    mActivity = activity;
    XWalkLibraryLoader.prepareToInit(mActivity);
}
//XWalkLibraryLoader的prepareToInit方法
public static void prepareToInit(Context context) {
    XWalkEnvironment.init(context);
    XWalkCoreWrapper.handlePreInit(context.getClass().getName());
}

//XWalkCoreWrapper中handlePreInit方法换棚,將反射方法保存到sReservedActions集合没炒,類名保存到sReservedActivities集合
public static void handlePreInit(String tag) {
    if (sInstance != null) return;

    Log.d(TAG, "Pre init xwalk core in " + tag);
    if (sReservedActions.containsKey(tag)) {
        sReservedActions.remove(tag);
    } else {
        sReservedActivities.add(tag);
    }

    sReservedActions.put(tag, new LinkedList<ReservedAction>());
}
//sReservedActions是一個map涛癌,鍵:類名、值:ReservedAction集合(之后需要執(zhí)行的初始化方法的反射)
private static HashMap<String, LinkedList<ReservedAction> > sReservedActions =
            new HashMap<String, LinkedList<ReservedAction> >();
//sReservedActivities 保存類名
 private static LinkedList<String> sReservedActivities = new LinkedList<String>();

這樣preInit添加到sReservedActions后送火,setContentView中去初始化XWalkView時就不會報錯了拳话,為什么不報錯了?看下面初始化XWalkView的具體過程:

  public XWalkView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ......
        this.reflectionInit();
    }

//XWalkView的構(gòu)造方法會調(diào)用reflectionInit方法种吸,這時雖然調(diào)用了
initEmbeddedMode弃衍,但sReservedActivities不為空直接返回了;雖然調(diào)用了
若干XWalkCoreWrapper靜態(tài)方法坚俗,但是設(shè)計的原則是開始初始化才會新建私有的
XWalkCoreWrapper對象镜盯、初始化完才會暴露這個私有對象,這時并沒有初始化內(nèi)核
XWalkCoreWrapper.getInstance()為空猖败,
不會繼續(xù)初始化XWalkView了速缆,XWalkCoreWrapper.reserveReflectObject(this)注冊占坑,等待下載內(nèi)核恩闻。
void reflectionInit() {
    XWalkCoreWrapper.initEmbeddedMode();
    this.coreWrapper = XWalkCoreWrapper.getInstance();
    if (this.coreWrapper == null) {
         //添加到sReservedActions中
        XWalkCoreWrapper.reserveReflectObject(this);
    } else {
        ......
    }
 }

//XWalkCoreWrapper中initEmbeddedMode方法
public static void initEmbeddedMode() {
    if (sInstance != null || !sReservedActivities.isEmpty()) return;
}

//XWalkCoreWrapper中reserveReflectObject從sReservedActivities拿最近的
tag艺糜,為什么取最近的呢?是因為默認(rèn)繼承XWalkActivity的Activity中的onCreate方法
中調(diào)用了super()之后立即就setContentView了判呕,線程執(zhí)行的最小粒度就是方法了倦踢,
不可能在這時會有同一線程的其他方法向sReservedActivities添加tag。之后拿到tag后向sReservedActions添加反射類侠草,用于加載完內(nèi)核后執(zhí)行
public static void reserveReflectObject(Object object) {
    String tag = (String)sReservedActivities.getLast();
    Log.d("XWalkLib", "Reserve object " + object.getClass() + " to " + tag);
    ((LinkedList)sReservedActions.get(tag)).add(new XWalkCoreWrapper.ReservedAction(object));
}

上面實際上是初始化XWalkView未遂的過程辱挥,雖然XWalkView對象有了,但它只是一個沒有靈魂的殼,不能立即使用边涕,還得等待下載內(nèi)核晤碘,下載是在XWalkActivityDelegate的onResume()方法中開始的褂微,下面是XWalkActivityDelegate的onResume()方法:

//XWalkActivityDelegate的onResume()
public void onResume() {
    if (mIsXWalkReady) return;
    if (XWalkLibraryLoader.isInitializing() || XWalkLibraryLoader.isDownloading()) {
        Log.d(TAG, "Other initialization or download is proceeding");
        return;
    }
    Log.d(TAG, "Initialize by XWalkActivity");
    XWalkLibraryLoader.startDecompress(this);
}
//XWalkLibraryLoader中
public static void startDecompress(DecompressListener listener) {
    new DecompressTask(listener).execute();
}

DecompressTask之后會調(diào)用ActivateTask,內(nèi)核都沒下載這些都是徒勞园爷,最后會得一個
STATUS_NOT_FOUND的結(jié)論宠蚂,調(diào)用 mListener.onActivateFailed(),而這個mListener就是
XWalkActivityDelegate對象童社,所以看它的這個方法

    @Override
    public void onActivateFailed() {
        if (mXWalkUpdater == null) {
            if (XWalkEnvironment.isDownloadMode()) {//下載模式
                mXWalkUpdater = new XWalkUpdater(
                    new XWalkBackgroundUpdateListener() {
                        @Override
                        public void onXWalkUpdateStarted() {
                        }

                        @Override
                        public void onXWalkUpdateProgress(int percentage) {
                        }

                        @Override
                        public void onXWalkUpdateCancelled() {
                            mCancelCommand.run();
                        }

                        @Override
                        public void onXWalkUpdateFailed() {
                            mCancelCommand.run();
                        }

                        @Override
                        public void onXWalkUpdateCompleted() {
                            XWalkLibraryLoader.startActivate(XWalkActivityDelegate.this);
                        }
                    },
                    mActivity);
            } else {//共享模式
                mXWalkUpdater = new XWalkUpdater(
                    new XWalkUpdateListener() {
                        @Override
                        public void onXWalkUpdateCancelled() {
                            mCancelCommand.run();
                        }
                    },
                    mActivity, mDialogManager);
            }
        }
        if (mXWalkUpdater.updateXWalkRuntime() && !XWalkEnvironment.isDownloadMode()) {
                求厕。。扰楼。呀癣。。弦赖。
            }
        }
    }

這是共享模式项栏,新建XWalkUpdater對象調(diào)用它的updateXWalkRuntime()下載,共享模式會彈一個窗告訴用戶去下載蹬竖,ok后調(diào)用downloadXWalkApk()沼沈。下面是XWalkUpdater的updateXWalkRuntime()

public boolean updateXWalkRuntime() {
    if (mUpdateListener != null) {
        mDownloadCommand = new Runnable() {
            @Override
            public void run() {
                downloadXWalkApk();
            }
        };
        mCancelCommand = new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "XWalkUpdater cancelled");
                mUpdateListener.onXWalkUpdateCancelled();
            }
        };

        mDialogManager.showInitializationError(status, mCancelCommand, mDownloadCommand);
    } else if (mBackgroundUpdateListener != null) {
        String url = XWalkEnvironment.getXWalkApkUrl();
        XWalkLibraryLoader.startHttpDownload(new BackgroundListener(), mContext, url);
    } else {
        throw new IllegalArgumentException("Update listener is null");
    }
    return true;
}

下載APK,如果在meta-data中配置了下載地址就使用這個地址下載APK币厕,否則
開啟應(yīng)用市場下載,這里沒有配置下載URL列另,使用應(yīng)用市場下載,過程如下:

private void downloadXWalkApk() {
    String url = XWalkEnvironment.getXWalkApkUrl();
    if (!url.isEmpty()) {
        XWalkLibraryLoader.startDownloadManager(new ForegroundListener(), mContext, url);
        return;
    }
    //拼接intent,查看是否有應(yīng)用商店
    String packageName = "org.xwalk.core";
    final Intent intent = new Intent("android.intent.action.VIEW");
    intent.setData(Uri.parse("market://details?id=" + packageName));
    List<ResolveInfo> infos = mContext.getPackageManager().queryIntentActivities(
            intent, PackageManager.MATCH_ALL);

    StringBuilder supportedStores = new StringBuilder();
    boolean hasGooglePlay = false;
    ......
    //IA 是 Intel Architecture的縮寫,IA架構(gòu)的Android也就只有平板或者模擬器了
    如果是IA架構(gòu)包名也得改
    if (!hasGooglePlay && XWalkEnvironment.isIaDevice()) {
         //64位的
        if (XWalkEnvironment.is64bitApp()) {
            packageName = "org.xwalk.core64.ia";
        } else {//32位的
            packageName = "org.xwalk.core.ia";
        }
    } else if (XWalkEnvironment.is64bitApp()) {//除此之外就是ARM架構(gòu)的了
        packageName = "org.xwalk.core64";//64位的
    } else {
        packageName = "org.xwalk.core";//32位的
    }
    Log.d(TAG, "Package name of Crosswalk to download: " + packageName);

    //之后跳轉(zhuǎn)下載google play下載安裝
    ......
}

這里判斷架構(gòu)劈榨、位數(shù)的代碼值得借鑒访递。分為兩個方法晦嵌,一個是getRuntimeAbi()
獲取運行時的ABI同辣,一個是getDeviceAbi() 獲取設(shè)備的ABI,為什么要分兩個惭载?
大概是因為有時候在64位的機器上會運行32位應(yīng)用旱函,防止去下載64位的APK,
具體實現(xiàn)如下描滔,細節(jié)不太懂

public static String getRuntimeAbi() {
    if (sRuntimeAbi == null) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                throw new NoSuchFieldError();
            }
            //5.0之前的手機直接獲取
            String abi = Build.CPU_ABI.toLowerCase();
            switch (abi) {
                case "armeabi":
                case "armeabi-v7a":
                    sRuntimeAbi = "armeabi-v7a";
                    break;
                case "arm64-v8a":
                    sRuntimeAbi = "arm64-v8a";
                    break;
                case "x86":
                    sRuntimeAbi = "x86";
                    break;
                case "x86_64":
                    sRuntimeAbi = "x86_64";
                    break;
                default:
                    throw new RuntimeException("Unexpected CPU_ABI: " + abi);
            }
        } catch (NoSuchFieldError e) {
           //5.0之后的手機
          //相當(dāng)于lunix cmd輸入uname -m
            String arch = System.getProperty("os.arch").toLowerCase();
            switch (arch) {
                case "x86":
                case "i686":
                case "i386":
                case "ia32":
                    sRuntimeAbi = "x86";
                    break;
                case "x64":
                case "x86_64":
                    if (is64bitDevice()) {
                        sRuntimeAbi = "x86_64";
                    } else {
                        sRuntimeAbi = "x86";
                    }
                    break;
                case "armv7l":
                case "armeabi":
                case "armeabi-v7a":
                    sRuntimeAbi = "armeabi-v7a";
                    break;
                case "aarch64":
                case "armv8":
                case "armv8l":
                case "arm64":
                    if (is64bitDevice()) {
                        sRuntimeAbi = "arm64-v8a";
                    } else {
                        sRuntimeAbi = "armeabi-v7a";
                    }
                    break;
                default:
                    throw new RuntimeException("Unexpected os.arch: " + arch);
            }
        }

        // The value may be ARM on Houdini devices.
        if (sRuntimeAbi.equals("armeabi-v7a")) {
            if (isIaDevice()) {
                sRuntimeAbi = "x86";
            }
        } else if (sRuntimeAbi.equals("arm64-v8a")) {
            if (isIaDevice()) {
                sRuntimeAbi = "x86_64";
            }
        }
        Log.d(TAG, "Runtime ABI: " + sRuntimeAbi);
    }
    return sRuntimeAbi;
}

public static String getDeviceAbi() {
    if (sDeviceAbi == null) {
        try {
            sDeviceAbi = Build.SUPPORTED_ABIS[0].toLowerCase();
        } catch (NoSuchFieldError e) {
            try {
                Process process = Runtime.getRuntime().exec("getprop ro.product.cpu.abi");
                InputStreamReader ir = new InputStreamReader(process.getInputStream());
                BufferedReader input = new BufferedReader(ir);
                sDeviceAbi = input.readLine().toLowerCase();
                input.close();
                ir.close();
            } catch (IOException ex) {
                throw new RuntimeException("Can not detect device's ABI");
            }
        }
        Log.d(TAG, "Device ABI: " + sDeviceAbi);
    }
    return sDeviceAbi;
}

2棒妨、自定義下載鏈接

流程與跳轉(zhuǎn)商店下載是一樣的,只是到了downloadXWalkApk()方法時url不為空了含长,
XWalkLibraryLoader啟動DownloadManagerTask下載券腔,下載完后調(diào)用安裝,這時7.0以上手機
會因為文件權(quán)限問題崩潰

安裝成功后拘泞,手機上就會有一個包名為org.xwalk.core的應(yīng)用了纷纫,這個應(yīng)用沒有提供入口,桌面沒圖標(biāo)陪腌。這時重新打開自己的應(yīng)用還是會走上面的流程辱魁,只是到了ActiveTask時會執(zhí)行XWalkCoreWrapper.attachXWalkCore()烟瞧,過程如下:

public static int attachXWalkCore() {
    //判斷不是內(nèi)嵌模式、不是下載模式染簇,那就是共享模式了
    ......
    sProvisionalInstance = new XWalkCoreWrapper(XWalkEnvironment.getApplicationContext(), 1);

    if (XWalkEnvironment.is64bitDevice()) {
        if (!sProvisionalInstance.findSharedCore("org.xwalk.core") && !sProvisionalInstance.findSharedCore("org.xwalk.core64") && XWalkEnvironment.isIaDevice()) {
            sProvisionalInstance.findSharedCore("org.xwalk.core64.ia");
        }
    } else if (!sProvisionalInstance.findSharedCore("org.xwalk.core") && XWalkEnvironment.isIaDevice()) {
        sProvisionalInstance.findSharedCore("org.xwalk.core.ia");
    }
    return sProvisionalInstance.mCoreStatus;
}
//findSharedCore查找包名為org.xwalk.core的應(yīng)用
private boolean findSharedCore(String packageName) {
    if (!checkCorePackage(packageName)) return false;
    //賦值mBridgeLoader
    mBridgeLoader = mBridgeContext.getClassLoader();
    if (!checkCoreVersion() || !checkCoreArchitecture()) {
        mBridgeContext = null;
        mBridgeLoader = null;
        return false;
    }

    Log.d(TAG, "Running in shared mode");
    mCoreStatus = XWalkLibraryInterface.STATUS_MATCH;
    return true;
}

private boolean checkCorePackage(String packageName) {
    //如果在manifest中配置要驗證APK
    if (XWalkAppVersion.VERIFY_XWALK_APK) {
        try {
            PackageInfo packageInfo = mWrapperContext.getPackageManager().getPackageInfo(
                    packageName, PackageManager.GET_SIGNATURES);
            if (!verifyPackageInfo(packageInfo,
                    XWalkAppVersion.XWALK_APK_HASH_ALGORITHM,
                    XWalkAppVersion.XWALK_APK_HASH_CODE)) {
                Log.d(TAG, packageName + " signature verification failed");
                mCoreStatus = XWalkLibraryInterface.STATUS_SIGNATURE_CHECK_ERROR;
                return false;
            }
        } catch (NameNotFoundException e) {
            Log.d(TAG, packageName + " not found");
            return false;
        }
    }
    //加載這個包的Conetxt并賦值給全局變量mBridgeContext
    try {
        mBridgeContext = mWrapperContext.createPackageContext(packageName,
                Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
    } catch (NameNotFoundException e) {
        Log.d(TAG, packageName + " not found");
        return false;
    }

    Log.d(TAG, "Created package context for " + packageName);
    return true;
}

之后調(diào)用checkCoreVersion()参滴、checkCoreArchitecture()確認(rèn)版本、加載so文件锻弓,完畢后回到ActiveTask砾赔,激活成功了調(diào)用XWalkLibraryLoader.finishInit(this.mActivity)
,這里就是在填剛才留下的坑青灼,這個方法會再次調(diào)用XWalkView的reflectionInit()重新init一次过蹂,這時就會走else的流程了,建立方法對應(yīng)關(guān)系聚至。

public static void finishInit(Context context) {
    XWalkCoreWrapper.handlePostInit(context.getClass().getName());
}
//XWalkCoreWrapper中
public static void handlePostInit(String tag) {
    Log.d(TAG, "Post init xwalk core in " + tag);
    if (!sReservedActions.containsKey(tag)) {
        return;
    }

    LinkedList<ReservedAction> reservedActions = sReservedActions.get(tag);
    for (ReservedAction action : reservedActions) {
        if (action.mObject != null) {
            Log.d(TAG, "Init reserved object: " + action.mObject.getClass());
            new ReflectMethod(action.mObject, "reflectionInit").invoke();
        }
        ...
         action.mMethod.invoke(args);
        }
    }

    sReservedActions.remove(tag);
    sReservedActivities.remove(tag);
}

看了這個流程大概明白了為什么代碼要混淆了酷勺,如果不混淆在知道詳細代碼的情況下就可以反射調(diào)用代碼。

下載模式

1扳躬、繼承XWalkActivity

這種方式與共享模式是一樣的脆诉,onActivateFailed()方法中會判斷是否是下載模式,依據(jù)就是manifest文件中xwalk_download_mode=enable贷币,是下載模式就不彈窗了直下載击胜,最后調(diào)用HttpDownloadTask將APK下載到內(nèi)部私有目錄xwalk_download下,下載完畢后把APK中的classes.dex役纹、資源使用XWalkDecompressor對象解壓到內(nèi)部私有目錄extracted_xwalkcore下,解壓完畢繼續(xù)走ActiveTask的流程,和上面一樣了,只是ClassLoader不通過context創(chuàng)建,而是新建DexClassLoader

    private boolean findDownloadedCore() {
        String libDir = XWalkEnvironment.getExtractedCoreDir();
        String dexPath = libDir + File.separator + "classes.dex";
        String dexOutputPath = XWalkEnvironment.getOptimizedDexDir();
        ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
        this.mBridgeLoader = new DexClassLoader(dexPath, dexOutputPath, libDir, localClassLoader);
        if (this.checkCoreVersion() && this.checkCoreArchitecture()) {
            Log.d("XWalkLib", "Running in downloaded mode");
            this.mCoreStatus = 1;
            return true;
        } else {
            this.mBridgeLoader = null;
            return false;
        }
    }
解壓后的extracted_xwalkcore
2偶摔、使用XWalkInitializer

XWalkInitializer與XWalkActivityDelegate的作用是一樣的,只是后者把所有的邏輯
都封裝了,并與Activity生命周期綁定,而XWalkInitializer由于不與Activity綁定,需要自己去寫XWalkUpdater以及一些回調(diào).

Crosswalk的學(xué)習(xí)筆記完,如有不正確的地方促脉,歡迎多多指教辰斋!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瘸味,隨后出現(xiàn)的幾起案子宫仗,更是在濱河造成了極大的恐慌,老刑警劉巖旁仿,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕夫,死亡現(xiàn)場離奇詭異,居然都是意外死亡枯冈,警方通過查閱死者的電腦和手機毅贮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尘奏,“玉大人滩褥,你說我怎么就攤上這事∽锛龋” “怎么了铸题?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵铡恕,是天一觀的道長。 經(jīng)常有香客問我丢间,道長探熔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任烘挫,我火速辦了婚禮诀艰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饮六。我一直安慰自己其垄,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布卤橄。 她就那樣靜靜地躺著绿满,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窟扑。 梳的紋絲不亂的頭發(fā)上喇颁,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音嚎货,去河邊找鬼橘霎。 笑死,一個胖子當(dāng)著我的面吹牛殖属,可吹牛的內(nèi)容都是我干的姐叁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼洗显,長吁一口氣:“原來是場噩夢啊……” “哼外潜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起墙懂,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤橡卤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后损搬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡柜与,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年巧勤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弄匕。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡颅悉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迁匠,到底是詐尸還是另有隱情剩瓶,我是刑警寧澤驹溃,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站延曙,受9級特大地震影響豌鹤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枝缔,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一布疙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愿卸,春花似錦灵临、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至发钝,卻和暖如春睁搭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笼平。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工园骆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寓调。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓锌唾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夺英。 傳聞我的和親對象是個殘疾皇子晌涕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容