简介 CrossWalk是一款为HTML应用提供运行时环境的开源项目,从Android开发的角度讲Crosswalk可以用来替代WebView显示网页,在应用重度依赖网页时可以使用Crosswalk解决设备碎片化问题。直接将Crosswalk的库包导入到项目中即可像使用WebView一样使用,这种模式在Crosswalk项目中叫内嵌模式 ,但这样有一个问题由于Crosswalk库包太大集成后的应用体积太大,为此Crosswalk为Android设备提供了另外两种使用方式,共享模式 和下载模式 。虽然Crosswalk项目 已经在17年停止维护 ,但它这种动态加载使用的方式仍值得我学习。本文将介绍Crosswalk的三种加载方式。
使用 内嵌模式 Crosswalk在它的下载地址 提供了很多资源,使用内嵌模式只需要下载对应的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文件,造成在运行时没有对应的crosswalk包。
1 2 3 4 5 6 7 android { defaultConfig { ndk { abiFilters 'armeabi-v7a' ,'x86' } } }
以上是内嵌模式下的一种集成方式。由于官方只提供以上2种aar,如果只想集成arm平台下的so文件或者一定要不同的平台加载对应的so文件,那只有自己新建Android library module了,到官网下载crosswalk.xx.zip和crosswalk.xx-64bit.zip,解压后如图把xwalk_core_library\libs目录下的对应so文件复制到新建的module的jniLibs下、把xwalk_core_library.jar复制到libs下,最后把xwalk_core_library\res目录下所有资源都复制到module中
由于module要对外提供xwalk_core_library.jar中的api所以要将build.gradle中依赖方式改为api
1 2 3 dependencies { api fileTree (dir: 'libs' , include: ['*.jar' ]) }
两种内嵌集成方式都可行,集成后就可以使用crosswalk了,不做任何额外操作的情况下默认是内嵌模式。在XWalkView的构造方法中会加载so文件,之后就可以使用了,和使用WebView一样XWalkUIClient对应WebView的WebChromeClient,XWalkResourceClient对应WebViewClient
共享模式 顾名思义共享模式就是和其他应用共用一个XWalkRuntime,XWalkRuntimeLib并没有被内嵌到自己的应用中,而是当应用要用到XWalkView时去加载包名为org.xwalk.core的应用的代码,如果没有安装就去下载XWalkRuntimeLib.APK安装,官方提供了两种下载APK的方式,一种是跳转Google Play Store下载,一种是在manifest文件中自定义下载链接的meta-data,到自己指定的地址去下载APK。第一种方式不可行,在大部分手机中market://details?id=xxx 这种URI跳转的是自家的应用商店,并没有XWalkRuntimeLib.APK。由于Crosswalk项目已经停止更新了, 第二种方式会因为文件权限问题在Android 7.0之后的手机上抛错。虽然这种模式基本被废了,但这种方式与下载模式大同小异,也值得分析下。
需要在官网下载crosswalk-shared-xx.aar并将其导入到项目,让Activity继承XWalkActivity并复写它的onXWalkReady()方法,在这个方法中使用XWalkView加载网页。继承了XWalkActivity的情况下,如果不做任何配置默认的是共享模式并且是第一种方式:跳转Google Play Store下载APK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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,之后在官网下载4个平台的APK并部署到自己的服务器上,在manifest文件中配置下载路径的meta-data。
1 2 3 <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,服务器根据不同值返回相应的APK就OK了。
下载模式 和共享模式差不多,只是共享模式是把APK下载下来当成一个应用安装到手机上,而下载模式干脆把APK下载到自己的私有目录下,把所有的so文件、资源解压出来保存到自己的内部私有目录下只供自己使用。在manifest文件中增加配置xwalk_download_mode为enable,共享模式秒变下载模式。
1 2 3 4 5 6 <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的构造方法中会反射加载初始化相关类,可是还没有下载相关类啊,只有抛错了。而XWalkInitializer的构造方法中会调用preInit(),这个方法就是告诉加载框架我知道还没有下载相关的类,我先在你这里注册下占个坑,如果下载初始化好了就通知我继续初始化XWalkView,共享模式中事先也没有相关的类怎么没这样的限制呢?其实在父类XWalkActivity的onCreate()方法中就帮忙注册了。下面是使用XWalkInitializer的代码:
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 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 () { if (mXWalkUpdater == null ) { mXWalkUpdater = new XWalkUpdater(this , this ); } mXWalkUpdater.updateXWalkRuntime(); } @Override public void onXWalkInitCompleted () { 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 () { mXWalkInitializer.initAsync(); } }
Crosswalk加载代码浅析 Crosswalk的全部加载代码在其Github项目地址的runtime/android/core/ 路径下,如图所示
代码不多,XWalkActivity暴露出来用于继承;XWalkActivityDelegate聚合在XWalkActivity中,将加载的逻辑和Activity的生命周期结合起来;XWalkCoreWrapper负责加载初始化so文件;官网还提供压缩版的APK,XWalkDecompressor负责解压压缩的so文件、资源;XWalkDialogManager在共享模式下为用户提供提示、选择弹窗;XWalkEnvironment提供运行环境信息,比如cpuArch、manifest中配置的参数;XWalkLibraryLoader是最主要的类,管理协调整个加载过程。而实际加载so文件、调用native方法的类在/runtime/android/core_internal/ 目录下,这也就是上文提到XWalk初始化相关类,这些类与so文件、资源一起出现,比如为内嵌模式提供的jar、aar中有internal包,而为共享、下载模式提供的包中就没有。
内嵌模式 在XWalkView的构造方法中会调用reflectionInit()方法,顾名思义也就是要去使用反射初始化类了,方法中调用XWalkCoreWrapper.initEmbeddedMode(),这个方法中会新建一个XWalkCoreWrapper对象,进而调用实例的findEmbeddedCore(),其中分别调用checkCoreVersion()、checkCoreArchitecture()确认内核的版本、so文件是否与cpuArch匹配,在checkCoreVersion()方法中调用getBridgeClass(“XWalkCoreVersion”)去反射加org.xwalk.core.internal.XWalkCoreVersion,前面提到了internal包与内核同时出现,如果加载失败了就证明“XWalk core not found”报错。XWalkCoreVersion保存的是内核版本信息、XWalkAppVersion保存的是集成的aar或jar包的版本信息,在内嵌模式中这两个没区别的。在checkCoreArchitecture()方法中加载org.xwalk.core.internal.XWalkViewDelegate的loadXWalkLibrary()方法,这个方法中才会去真正的加载so文件,由于是内嵌模式so文件的位置由系统决定,放在了data/app/package name/lib/目录下,直接调用的是 System.loadLibrary(lib)加载,最后加载成功后在XWalkView中反射初始化XWalkViewBridge以及确立XWalkView中的方法与XWalkViewBridge反射方法对应关系,比如XWalkView.loadUrl()对应的XWalkViewBridge中的loadUrlSuper()方法,完毕后就可以愉快的使用了。
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 public XWalkView (Context context, AttributeSet attrs) { super (context, attrs); ...... this .reflectionInit(); } void reflectionInit () { XWalkCoreWrapper.initEmbeddedMode(); } public static void initEmbeddedMode () { if (sInstance != null || !sReservedActivities.isEmpty()) return ; Log.d(TAG, "Init embedded mode" ); XWalkCoreWrapper provisionalInstance = new XWalkCoreWrapper(null , -1 ); 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(); 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 { 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 ; }
内嵌模式的加载就这些,一句话:在第一次新建XWalkView对象时就去通过反射加载so文件。同一个包中为什么不直接调用,而用反射去加载?这就是为了和接下来要说的共享、下载模式统一。
共享模式 1、跳转Google Play 下载 共享模式需要继承XWalkActivity,在onCreate()方法中会新建XWalkActivityDelegate对象 ,加载的起端就在XWalkActivityDelegate的构造方法中,它会调用XWalkLibraryLoader.prepareToInit(mActivity),这就是前面说的这个预先初始化,过程如下:
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 public abstract class XWalkActivity extends Activity { private XWalkActivityDelegate mActivityDelegate; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); mActivityDelegate = new XWalkActivityDelegate(this , cancelCommand, completeCommand); } } public XWalkActivityDelegate (Activity activity, Runnable cancelCommand, Runnable completeCommand) { mActivity = activity; XWalkLibraryLoader.prepareToInit(mActivity); } public static void prepareToInit (Context context) { XWalkEnvironment.init(context); XWalkCoreWrapper.handlePreInit(context.getClass().getName()); } 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>()); } private static HashMap<String, LinkedList<ReservedAction> > sReservedActions = new HashMap<String, LinkedList<ReservedAction> >(); private static LinkedList<String> sReservedActivities = new LinkedList<String>();
这样preInit添加到sReservedActions后,setContentView中去初始化XWalkView时就不会报错了,为什么不报错了?看下面初始化XWalkView的具体过程:
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 public XWalkView (Context context, AttributeSet attrs) { super (context, attrs); ...... this .reflectionInit(); } initEmbeddedMode,但sReservedActivities不为空直接返回了;虽然调用了 若干XWalkCoreWrapper静态方法,但是设计的原则是开始初始化才会新建私有的 XWalkCoreWrapper对象、初始化完才会暴露这个私有对象,这时并没有初始化内核 XWalkCoreWrapper.getInstance()为空, 不会继续初始化XWalkView了,XWalkCoreWrapper.reserveReflectObject(this )注册占坑,等待下载内核。 void reflectionInit () { XWalkCoreWrapper.initEmbeddedMode(); this .coreWrapper = XWalkCoreWrapper.getInstance(); if (this .coreWrapper == null ) { XWalkCoreWrapper.reserveReflectObject(this ); } else { ...... } } public static void initEmbeddedMode () { if (sInstance != null || !sReservedActivities.isEmpty()) return ; } tag,为什么取最近的呢?是因为默认继承XWalkActivity的Activity中的onCreate方法 中调用了super ()之后立即就setContentView了,线程执行的最小粒度就是方法了, 不可能在这时会有同一线程的其他方法向sReservedActivities添加tag。之后拿到tag后向sReservedActions添加反射类,用于加载完内核后执行 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对象有了,但它只是一个没有灵魂的壳,不能立即使用,还得等待下载内核,下载是在XWalkActivityDelegate的onResume()方法中开始的,下面是XWalkActivityDelegate的onResume()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ); } public static void startDecompress (DecompressListener listener) { new DecompressTask(listener).execute(); }
DecompressTask之后会调用ActivateTask,内核都没下载这些都是徒劳,最后会得一个 STATUS_NOT_FOUND的结论,调用 mListener.onActivateFailed(),而这个mListener就是 XWalkActivityDelegate对象,所以看它的这个方法
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 @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对象调用它的updateXWalkRuntime()下载,共享模式会弹一个窗告诉用户去下载,ok后调用downloadXWalkApk()。下面是XWalkUpdater的updateXWalkRuntime()
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 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,否则 开启应用市场下载,这里没有配置下载URL,使用应用市场下载,过程如下:
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 private void downloadXWalkApk () { String url = XWalkEnvironment.getXWalkApkUrl(); if (!url.isEmpty()) { XWalkLibraryLoader.startDownloadManager(new ForegroundListener(), mContext, url); return ; } 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架构包名也得改 if (!hasGooglePlay && XWalkEnvironment.isIaDevice()) { if (XWalkEnvironment.is64bitApp()) { packageName = "org.xwalk.core64.ia" ; } else { packageName = "org.xwalk.core.ia" ; } } else if (XWalkEnvironment.is64bitApp()) { packageName = "org.xwalk.core64" ; } else { packageName = "org.xwalk.core" ; } Log.d(TAG, "Package name of Crosswalk to download: " + packageName); ...... }
这里判断架构、位数的代码值得借鉴。分为两个方法,一个是getRuntimeAbi() 获取运行时的ABI,一个是getDeviceAbi() 获取设备的ABI,为什么要分两个? 大概是因为有时候在64位的机器上会运行32位应用,防止去下载64位的APK, 具体实现如下,细节不太懂
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 98 99 100 public static String getRuntimeAbi () { if (sRuntimeAbi == null ) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { throw new NoSuchFieldError(); } 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) { 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); } } 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、自定义下载链接 流程与跳转商店下载是一样的,只是到了downloadXWalkApk()方法时url不为空了, XWalkLibraryLoader启动DownloadManagerTask下载,下载完后调用安装,这时7.0以上手机 会因为文件权限问题崩溃
安装成功后,手机上就会有一个包名为org.xwalk.core的应用了,这个应用没有提供入口,桌面没图标。这时重新打开自己的应用还是会走上面的流程,只是到了ActiveTask时会执行XWalkCoreWrapper.attachXWalkCore(),过程如下:
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 public static int attachXWalkCore () { ...... 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; } private boolean findSharedCore (String packageName) { if (!checkCorePackage(packageName)) return false ; 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) { 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 ; } } 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 ; }
之后调用checkCoreVersion()、checkCoreArchitecture()确认版本、加载so文件,完毕后回到ActiveTask,激活成功了调用XWalkLibraryLoader.finishInit(this.mActivity) ,这里就是在填刚才留下的坑,这个方法会再次调用XWalkView的reflectionInit()重新init一次,这时就会走else的流程了,建立方法对应关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void finishInit (Context context) { XWalkCoreWrapper.handlePostInit(context.getClass().getName()); } 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); }
看了这个流程大概明白了为什么代码要混淆了,如果不混淆在知道详细代码的情况下就可以反射调用代码。
下载模式 1、继承XWalkActivity 这种方式与共享模式是一样的,onActivateFailed()方法中会判断是否是下载模式,依据就是manifest文件中xwalk_download_mode=enable,是下载模式就不弹窗了直下载,最后调用HttpDownloadTask将APK下载到内部私有目录xwalk_download下,下载完毕后把APK中的classes.dex、资源使用XWalkDecompressor对象解压到内部私有目录extracted_xwalkcore下,解压完毕继续走ActiveTask的流程,和上面一样了,只是ClassLoader不通过context创建,而是新建DexClassLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 ; } }
2、使用XWalkInitializer XWalkInitializer与XWalkActivityDelegate的作用是一样的,只是后者把所有的逻辑 都封装了,并与Activity生命周期绑定,而XWalkInitializer由于不与Activity绑定,需要自己去写XWalkUpdater以及一些回调.
Crosswalk的学习笔记完,如有不正确的地方,欢迎多多指教!