在上一篇文章《深入Weex系列(六)Weex渲染流程分析》中我们分析了Weex的渲染流程,但是实际上串起来的还是Module以及Component组件,加上Weex特有的渲染流程。
Module组件和Component组件对Weex Android SDK的意义非常大,属于没有这俩寸步难行的关系,包括今天我们要分析的Adapter也依赖于Module、Component组件,尤其是Js引擎与Module、Component交互的部分,Adapter表面上并不涉及,但是实际上息息相关。如果还有疑惑的话非常建议大家回过头再去看看之前的源码分析文章。
本篇文章我们就开始分析Weex中另一个组件Adapter,对Weex的设计理解更深一步。
在《Android 扩展》中我们可以看到Adapter的定位:
Adapter 扩展 Weex 对一些基础功能实现了统一的接口,可实现这些接口来定制自己的业务。例如:图片下载等。
此处可以看到:Weex对Adapter的定位是基础功能的定义,可以实现这些接口自己进行实现。
实际上在我的WeexList项目中已经有了关于Adapter的使用,因为Weex并没有实现默认的图片加载功能。
Adapter的注册:
InitConfig config = new InitConfig.Builder().setImgAdapter(new WeexImageAdapter()).build();
WXSDKEngine.initialize(this, config);
Adapter的实现:
public class WeexImageAdapter implements IWXImgLoaderAdapter {
@Override
public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) {
Glide.with(view.getContext())
.load(url)
.error(R.mipmap.me_image_man)
.into(view);
}
}
可以看到我们实现了Weex的IWXImgLoaderAdapter接口,自己实现了图片加载的能力。需要注意的是Adapter的注册和Module、Component的注册方式是不一样的。
我们知道Module的定位是非UI性质的功能组件,那和Adapter是不是有点冲突?为什么还多了一个Adapter组件呢?
不要Adapter组件可不可以呢?其实是可以的,要做的事情都通过Module来做。但是一些基础功能例如图片加载、网络请求等不同的应用有自己的实现方式、依赖库,如果Weex自己再加一套,就显得冗余而且这些基础能力Weex定义好接口让接入的应用来提供就好了。
这就是Adapter存在的意义,你看Weex团队是多么的贴心啊!
备注: 那么我们此时可以由Adapter和Module的战略意义不一样,猜到两个组件的地位也是不一样的。通过翻阅源码,在InitConfig中Weex使用建造者模式来提供各种各样的Adapter的自定义,但是却不支持自己新增Adapter的类型。此处也提现了Adapter的定位:基础功能实现了统一的接口。
可以看到:Adapter的注册实际上非常简单,只是通过WXSDKEngine初始化了一些配置信息而已,根本不需要像Module或者Component一样需要通过JsBridge也让Js引擎知道自己的存在。
再次看出Adapter的定位:基础功能实现了统一的接口,具体的交互交给Module或Component来做,然后Module或Component来调用我们实现的Adapter。
关于Adapter的调用就不再画图分析,因为实在不需要怎么分析。实际上对于Adapter的调用本质就是类的调用,因为刚才在注册的时候设置了各种各样的Adapter,然后直接调用接口即可。
对于Adapter的调用分为Component调用和Module调用(Adapter处于调用链的下端)
我们来介绍下实现图片加载能力的Adapter,这个Adapter没有被Weex默认实现。之前总结过组件的交互是通过JsBridge然后来调用Component被注解修饰的方法。
对于ImageView它在Weex里对应了WXImage这个Component。我们在Vue代码里写的src属性既然是需要通过Adapter实现的,那在WXImage中必定有方法对应src属性,果然我们发现了它。
@Component(lazyload = false)
public class WXImage extends WXComponent<ImageView> {
@WXComponentProp(name = Constants.Name.SRC)
public void setSrc(String src) {
if (src == null) {
return;
}
this.mSrc = src;
WXSDKInstance instance = getInstance();
Uri rewrited = instance.rewriteUri(Uri.parse(src), URIAdapter.IMAGE);
if (Constants.Scheme.LOCAL.equals(rewrited.getScheme())) {
setLocalSrc(rewrited);
} else {
int blur = 0;
if (getDomObject() != null) {
String blurStr = getDomObject().getStyles().getBlur();
blur = parseBlurRadius(blurStr);
}
setRemoteSrc(rewrited, blur);
}
}
private void setRemoteSrc(Uri rewrited, int blurRadius) {
·······
IWXImgLoaderAdapter imgLoaderAdapter = getInstance().getImgLoaderAdapter();
if (imgLoaderAdapter != null) {
imgLoaderAdapter.setImage(rewrited.toString(), getHostView(),
getDomObject().getAttrs().getImageQuality(), imageStrategy);
}
}
}
总结:
对于网络请求比较常用,Weex就做了默认的实现,但是之前在《Weex系列(四)之Module组件源码解析》分析过,实现比较简陋,最好重新实现。
我们先看下平时写Vue代码的时候使用网络请求是怎么做的:
var stream = weex.requireModule('stream')
stream.fetch
既然require的是名叫”stream”的Module,那么我们就在WXSDKEngine中找一下,果然在register()方法中找到了:
registerModule("stream", WXStreamModule.class);
接下来,我们不用猜测,坚信WXStreamModule类中必定存在一个fetch方法;
@JSMethod(uiThread = false)
public void fetch(String optionsStr, final JSCallback callback, JSCallback progressCallback){
JSONObject optionsObj = null;
try {
optionsObj = JSON.parseObject(optionsStr);
}catch (JSONException e){
WXLogUtils.e("", e);
}
boolean invaildOption = optionsObj==null || optionsObj.getString("url")==null;
if(invaildOption){
if(callback != null) {
Map<String, Object> resp = new HashMap<>();
resp.put("ok", false);
resp.put(STATUS_TEXT, Status.ERR_INVALID_REQUEST);
callback.invoke(resp);
}
return;
}
//此处可以看到Http请求的那些参数最终是被怎么取的,这样即便是文档没写的一些设置我们也可以根据取参数的方法反推出来怎么设置。
String method = optionsObj.getString("method");
String url = optionsObj.getString("url");
JSONObject headers = optionsObj.getJSONObject("headers");
String body = optionsObj.getString("body");
String type = optionsObj.getString("type");
int timeout = optionsObj.getIntValue("timeout");
if (method != null) method = method.toUpperCase();
Options.Builder builder = new Options.Builder()
.setMethod(!"GET".equals(method)
&&!"POST".equals(method)
&&!"PUT".equals(method)
&&!"DELETE".equals(method)
&&!"HEAD".equals(method)
&&!"PATCH".equals(method)?"GET":method)
.setUrl(url)
.setBody(body)
.setType(type)
.setTimeout(timeout);
extractHeaders(headers,builder);
final Options options = builder.createOptions();
// 真正请求网络去了,里面会调用IWXHttpAdapter
sendRequest(options, new ResponseCallback() {
@Override
public void onResponse(WXResponse response, Map<String, String> headers) {
if(callback != null) {
Map<String, Object> resp = new HashMap<>();
if(response == null|| "-1".equals(response.statusCode)){
resp.put(STATUS,-1);
resp.put(STATUS_TEXT,Status.ERR_CONNECT_FAILED);
}else {
int code = Integer.parseInt(response.statusCode);
resp.put(STATUS, code);
resp.put("ok", (code >= 200 && code <= 299));
if (response.originalData == null) {
resp.put("data", null);
} else {
String respData = readAsString(response.originalData,
headers != null ? getHeader(headers, "Content-Type") : ""
);
try {
resp.put("data", parseData(respData, options.getType()));
} catch (JSONException exception) {
WXLogUtils.e("", exception);
resp.put("ok", false);
resp.put("data","{'err':'Data parse failed!'}");
}
}
resp.put(STATUS_TEXT, Status.getStatusText(response.statusCode));
}
resp.put("headers", headers);
callback.invoke(resp);
}
}
}, progressCallback);
}
private void sendRequest(Options options,ResponseCallback callback,JSCallback progressCallback){
WXRequest wxRequest = new WXRequest();
wxRequest.method = options.getMethod();
wxRequest.url = mWXSDKInstance.rewriteUri(Uri.parse(options.getUrl()), URIAdapter.REQUEST).toString();
wxRequest.body = options.getBody();
wxRequest.timeoutMs = options.getTimeout();
if(options.getHeaders()!=null)
if (wxRequest.paramMap == null) {
wxRequest.paramMap = options.getHeaders();
}else{
wxRequest.paramMap.putAll(options.getHeaders());
}
IWXHttpAdapter adapter = (mAdapter==null && mWXSDKInstance != null) ? mWXSDKInstance.getWXHttpAdapter() : mAdapter;
if (adapter != null) {
// 调用了IWXHttpAdapter的sendRequest方法
adapter.sendRequest(wxRequest, new StreamHttpListener(callback,progressCallback));
}else{
WXLogUtils.e("WXStreamModule","No HttpAdapter found,request failed.");
}
}
然后我们看下DefaultWXHttpAdapter的默认网络请求实现;
public class DefaultWXHttpAdapter implements IWXHttpAdapter {
private static final IEventReporterDelegate DEFAULT_DELEGATE = new NOPEventReportDelegate();
private ExecutorService mExecutorService;
private void execute(Runnable runnable){
if(mExecutorService==null){
mExecutorService = Executors.newFixedThreadPool(3);
}
mExecutorService.execute(runnable);
}
@Override
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
if (listener != null) {
listener.onHttpStart();
}
execute(new Runnable() {
@Override
public void run() {
WXResponse response = new WXResponse();
IEventReporterDelegate reporter = getEventReporterDelegate();
try {
HttpURLConnection connection = openConnection(request, listener);
reporter.preConnect(connection, request.body);
Map<String,List<String>> headers = connection.getHeaderFields();
int responseCode = connection.getResponseCode();
if(listener != null){
listener.onHeadersReceived(responseCode,headers);
}
reporter.postConnect();
response.statusCode = String.valueOf(responseCode);
if (responseCode >= 200 && responseCode<=299) {
InputStream rawStream = connection.getInputStream();
rawStream = reporter.interpretResponseStream(rawStream);
response.originalData = readInputStreamAsBytes(rawStream, listener);
} else {
response.errorMsg = readInputStream(connection.getErrorStream(), listener);
}
if (listener != null) {
listener.onHttpFinish(response);
}
} catch (IOException|IllegalArgumentException e) {
e.printStackTrace();
response.statusCode = "-1";
response.errorCode="-1";
response.errorMsg=e.getMessage();
if(listener!=null){
listener.onHttpFinish(response);
}
if (e instanceof IOException) {
reporter.httpExchangeFailed((IOException) e);
}
}
}
});
}
}
可以看到Weex默认的网络请求是基于HttpURLConnection,一个核心池和最大池都是3的FixThreadPool。对于网络请求来说缺点显而易见:
但是对Weex来说实际上只是提供默认的简单实现,也没错,需要自己去重新定义。
接下来看图总结下:
总结:
Weex除了使用网络图片之外可以使用别的类型图片吗,例如直接使用drawable文件夹里的图片?
在刚开始接触到Weex的时候我内心的答案也是NO,毕竟drawable里的图片在常规的安卓开发中都是需要使用R文件来调用的。源码面前,了无秘密!我们就来看下WXImage的实现吧,在setSrc方法中有一个判断:
if (Constants.Scheme.LOCAL.equals(rewrited.getScheme())) {
setLocalSrc(rewrited);// 以local 开头的话则走到了这里
} else {
int blur = 0;
if(getDomObject() != null) {
String blurStr = getDomObject().getStyles().getBlur();
blur = parseBlurRadius(blurStr);
}
setRemoteSrc(rewrited, blur);
}
最终会走到这里:
public static Drawable getDrawableFromLoaclSrc(Context context, Uri rewrited) {
Resources resources = context.getResources();
List<String> segments = rewrited.getPathSegments();
if (segments.size() != 1) {
WXLogUtils.e("Local src format is invalid.");
return null;
}
int id = resources.getIdentifier(segments.get(0), "drawable", context.getPackageName());
return id == 0 ? null : ResourcesCompat.getDrawable(resources, id, null);
}
老司机们已经明白了吧:通过资源名生成uri,然后还是拿到了资源对应的id,获取的图片。
备注:
欢迎持续关注Weex源码分析项目:Weex-Analysis-Project