我们使用weex开发Android项目时,会遇到难以真机调试的问题,开发起来会很头疼,难以跟踪bug。weex提供了一个工具来调试weex开发的代码,weex devtool与weex的一个app应用playground。可是这样调试不仅麻烦费时费力,而且并不是真的真机调试,与用户实际使用的情况不一样。因此,我们的项目将playground应用中的扫码调试功能,集成到了我们的项目中,这样就可以直接使用我们的项目二维码扫码连接weex devtool,进行调试。官网文档有集成方式:http://weex.apache.org/cn/guide/integrate-devtool-to-android.html。可是这里有很多坑,所以下面我就从这篇文档的基础上进行完善。
官网文档中,有两种接入方式,一种是直接在代码上修改,写入weex devtool的url进行连接,一种是进行扫码动态连接,个人推荐扫码动态连接,原因在介绍完第一种方式后解释。
一、接入坑
1)、直接在代码上修改,接入weex devtool
1、通过在 XXXApplication
中设置开关打开调试模式
public class MyApplication extends Application {
public void onCreate() {
super.onCreate();
initDebugEnvironment(true, "xxx.xxx.xxx.xxx"/*"DEBUG_SERVER_HOST"*/);//initDebugEnvironment(boolean enable, String host) enable是否开启debug模式,weex devtool的url
}
}
initDebugEnvironment(boolean enable, String host) enable是否开启debug模式,weex devtool的url
}
}
private void initDebugEnvironment(boolean enable, String host) {
WXEnvironment.sRemoteDebugMode = enable;
WXEnvironment.sRemoteDebugProxyUrl = "ws://" + host + ":8088/debugProxy/native/"+"这里填你打开的weex debuge时产生的weex id";//这里是官网的一个坑,没有告诉你要接一个weex devtool的id
}
Weex SDK 的 WXEnvironment
类里有一对静态变量标记了 Weex 当前的调试模式是否开启分别是:
public static boolean sRemoteDebugMode; // 是否开启 debug 模式,默认关闭
public static String sRemoteDebugProxyUrl; // DebugServer的websocket地址
无论在App 中无论以何种方式设置 Debug 模式,都必须设置 WXEnvironment.sRemoteDebugMode
和 WXEnvironment.sRemoteDebugProxyUrl。
2、修改
sRemoteDebugMode
后一定要调用WXSDKEngine.reload()。
一般來說,在修改了 WXEnvironment.sRemoteDebugMode
以后调用了 WXSDKEngine.reload()
方法才能够使 Debug模式生效。WXSDKEngine.reload()
用来重置 Weex 的运行环境上下文,在切换调试模式时需要调用此方法来创建新的 Weex 运行时和 DebugBridge 并将所有的 JS 调用桥接到调试服务器执行。
可以把这句放到mainActivity的初始过程中,比如onResume()回调方法中。
3、通过响应 ACTION_DEBUG_INSTANCE_REFRESH
广播及时刷新。(一般不用写,weex中的AbsWeexActivity已经写好了,只要加载weex的界面是用WXPageActivity加载的就可以)
广播 ACTION_DEBUG_INSTANCE_REFRESH
在调试模式切换和 Chrome 调试页面刷新时发出,主要用来通知当前的 Weex容器以 Debug 模式重新加载当前页。在 playground 中的处理过程如下:
|
如果接入方的容器未对该广播做处理,那么将不支持刷新和调试过程中编辑代码时的 watch 功能。
第一种接入方式已介绍完成,我们可以看到,在接入过程中,我们将weex debug的调试网址用代码的形式写入了app,从而实现联调,这就导致每次启动一次weex debug,都要改一次代码。因此我推荐第二种方式,动态接入。
2)、二维码扫描,动态接入weex devtool
第二种接入方式与第一种只在第一步有差别,第二与第三步一样,原理是写一个二维码扫描,获取每次启动weex debug的新url,然后赋值给weexSDK,交给他去联调即可。代码如下:
先写一个全局悬浮按钮,以便启动二维码扫描在每个界面都支持debug调试,点击事件启动扫描:
public class FloatingBtnService extends Service {
protected static final String TAG = "ShowFloatingBtnService";
public static String STATAG = "stop";
private LinearLayout view;
// 获取到手机的窗体管理器
private WindowManager wm;
private WindowManager.LayoutParams params;
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
*把一个view对象显示到手机窗体上
*/
public void showAddress() {
LayoutInflater inflater = LayoutInflater.from(getApplication());
view = (LinearLayout) inflater.inflate(R.layout.toast_location, null);
// 给窗体上的view对象注册点击事件
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//二维码扫描
IntentIntegrator integrator = new IntentIntegrator(getCurrentActivity());
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES);
integrator.setPrompt("Scan a barcode");
//integrator.setCameraId(0); // Use a specific camera of the device
integrator.setBeepEnabled(true);
integrator.setOrientationLocked(false);
integrator.setBarcodeImageEnabled(true);
integrator.initiateScan();
}
});
// 给窗体上的view对象注册触摸事件
view.setOnTouchListener(new View.OnTouchListener() {
int startX;
int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "呼叫界面,更改位置+摸到");
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "呼叫界面,更改位置+移动");
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
int dx = newX - startX;
int dy = newY - startY;
// 更改view对象在窗体上显示的位置.
params.x += dx;
params.y += dy;
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}
if (params.x > wm.getDefaultDisplay
().getWidth()) {
params.x = wm.getDefaultDisplay
().getWidth();
}
if (params.y > wm.getDefaultDisplay
().getHeight()) {
params.y = wm.getDefaultDisplay
().getHeight();
}
wm.updateViewLayout(view, params);
// 重新初始化手指的位置
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
int lastx = params.x;
int lasty = params.y;
SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putInt("lastx", lastx);
editor.putInt("lasty", lasty);
editor.commit();
break;
}
return false;
}
});
// tv = (ImageView) view.findViewById
// (R.id.tv_toast_address);
// tv.setText(address);
SharedPreferences sp = getSharedPreferences("config",
MODE_PRIVATE);
params = new WindowManager.LayoutParams();
params.gravity = Gravity.TOP | Gravity.LEFT;
int lastx = sp.getInt("lastx", 0);
int lasty = sp.getInt("lasty", 0);
params.x = lastx;
params.y = lasty;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
// 定义控件 可以触摸 删除一个flag
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
// 定义窗体的类型 TYPE_PRIORITY_PHONE
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
wm.addView(view, params);
}
@Override
public void onCreate() {
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
showAddress();
STATAG = "start";
super.onCreate();
}
@Override
public void onDestroy() {
if (view != null) {
wm.removeView(view);
view = null;
}
STATAG = "stop";
super.onDestroy();
}
}
然后,在每个Activity都要继承的基Activity类里重写 onActivityResult(int requestCode, int resultCode, Intent data),接收weex debug的url,并处理,启动app weex debug模式,打开新的WeexActivity,并加载:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (result != null) {
handleDecodeInternally(result.getContents());
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 处理devtool返回的DebugProxyUrl,WX启动devtool模式 by leon.wen
*
* @param code
*/
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(String code) {
if (!TextUtils.isEmpty(code)) {
Uri uri = Uri.parse(code);
if (uri.getQueryParameterNames().contains("bundle")) {
WXEnvironment.sDynamicMode = uri.getBooleanQueryParameter("debug", false);
WXEnvironment.sDynamicUrl = uri.getQueryParameter("bundle");
String tip = WXEnvironment.sDynamicMode ? "Has switched to Dynamic Mode" : "Has switched to Normal Mode";
Toast.makeText(this, tip, Toast.LENGTH_SHORT).show();
finish();
return;
} else if (uri.getQueryParameterNames().contains("_wx_devtool")) {
WXEnvironment.sRemoteDebugProxyUrl = uri.getQueryParameter("_wx_devtool");
WXEnvironment.sDebugServerConnectable = true;
WXSDKEngine.reload();
Toast.makeText(this, "devtool", Toast.LENGTH_SHORT).show();
return;
} else if (code.contains("_wx_debug")) {
uri = Uri.parse(code);
String debug_url = uri.getQueryParameter("_wx_debug");
// todo 新的weexCore没有这个方法,可删除
switchDebugModel(true, debug_url);
finish();
} else {
refreshWeexUrl(code);
}
}
}
/**
* 处理weexdebug 模式下,扫描单个weex.vue时的刷新界面
*
* @param code vue的路径
*/
public void refreshWeexUrl(String code) {
Toast.makeText(this, code, Toast.LENGTH_SHORT)
.show();
Intent intent = new Intent(BaseActivity.this, WXPageActivity.class);
intent.setPackage(getPackageName());
intent.setData(Uri.parse(code));
startActivity(intent);
}
二、SDK版本坑
当你写好代码以后就以为万事大吉了么!no!!!后面的坑很大!等你的app跑起来你会发现,无法联调,app甚至闪退!为什么呢?经过查看weex sdk与weex_inspector两个sdk源码有冲突!这他妈阿里坑爹啊!没有说清楚相对应的版本库。weex sdk是weex加载界面的核心库,weex_inspector是负责app和浏览器联调的sdk,js的发送依赖他。经过我一个个版本的比较,得出以下结论:
推荐
weex_sdk 0.19.0.4 对应 weex_inspector 0.18.68
weex_sdk 0.18.0 对应 weex_inspector 0.13.4