0%

Crosswalk的使用与源码分析

简介

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中
xwalk_core_library\libs目录下

复制到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() {
// 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的生命周期结合起来;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();
}
//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();
//分别确认版本和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;
}

内嵌模式的加载就这些,一句话:在第一次新建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);
}
}
//XWalkActivityDelegate的构造方法
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集合(之后需要执行的初始化方法的反射)
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的具体过程:

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();
}

//XWalkView的构造方法会调用reflectionInit方法,这时虽然调用了
initEmbeddedMode,但sReservedActivities不为空直接返回了;虽然调用了
若干XWalkCoreWrapper静态方法,但是设计的原则是开始初始化才会新建私有的
XWalkCoreWrapper对象、初始化完才会暴露这个私有对象,这时并没有初始化内核
XWalkCoreWrapper.getInstance()为空,
不会继续初始化XWalkView了,XWalkCoreWrapper.reserveReflectObject(this)注册占坑,等待下载内核。
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,为什么取最近的呢?是因为默认继承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
//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之后会调用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;
}
//拼接intent,查看是否有应用商店
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架构的Android也就只有平板或者模拟器了
如果是IA架构包名也得改
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架构的了
packageName = "org.xwalk.core64";//64位的
} else {
packageName = "org.xwalk.core";//32位的
}
Log.d(TAG, "Package name of Crosswalk to download: " + packageName);

//之后跳转下载google play下载安装
......
}

这里判断架构、位数的代码值得借鉴。分为两个方法,一个是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();
}
//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之后的手机
//相当于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、自定义下载链接

流程与跳转商店下载是一样的,只是到了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;
}
//findSharedCore查找包名为org.xwalk.core的应用
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;
}

之后调用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());
}
//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);
}

看了这个流程大概明白了为什么代码要混淆了,如果不混淆在知道详细代码的情况下就可以反射调用代码。

下载模式

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;
}
}

解压后的extracted_xwalkcore

2、使用XWalkInitializer

XWalkInitializer与XWalkActivityDelegate的作用是一样的,只是后者把所有的逻辑
都封装了,并与Activity生命周期绑定,而XWalkInitializer由于不与Activity绑定,需要自己去写XWalkUpdater以及一些回调.

Crosswalk的学习笔记完,如有不正确的地方,欢迎多多指教!