Android 初探微信 JSBridge

monkeytest · 2016年02月14日 · 10825 次阅读

最近也是心血来潮顺便看了下WeiXin的,这里分享下给大家

解压缩

当然第一步肯定还是先解压缩两者,微信和支付宝钱包还是有很大不同的。 微信解压缩之后我们能够在在/assets/目录下面看到没有混淆的 JS 文件,我们多少可以通过这个文件来窥探一下。

钱包解压缩之后同样的目录下我们并找不到对外暴露的容器相关的文件,但其实钱包也是有办法窥探的,只是不在这个位置,碍于身份我就不能多说了,有兴趣的同学自行探索。 当然了,解压缩之后,class.dex的反编译也是不能少的,那么首先一切就绪了。

wxjs.js

这个文件将近 3000 行,当然还是搜索bridge进行搜索,找到了如下的方法

(function() {
    if (window.WeixinJSBridge) {

    };

var __WeixinJSBridge = {
    // public
    invoke:_call,
    call:_call,
    on:_onfor3rd,
    env:_env,
    log:_log,
    // private
    // _test_start:_test_start,
    _fetchQueue: _fetchQueue,
    _handleMessageFromWeixin: _handleMessageFromWeixin,
    _hasInit: false,
    _hasPreInit: false,
    _continueSetResult: _continueSetResult
};

到了这里我们看到了首先微信在初始化的时候会判断有没有WeixinJSBridge,然后我们也看到了这个Bridge定义的时候包含的参数。接下来就一个一个来看了,看完不知道能不能明白微信的逻辑。

_call

function _call(func,params,callback) {
    var curFuncIdentifier = __WeixinJSBridge.call;
    if (curFuncIdentifier !== _callIdentifier) {
        return;
    }
    if (!func || typeof func !== 'string') {
        return;
    };
    if (typeof params !== 'object') {
        params = {};
    };

    var callbackID = (_callback_count++).toString();

    if (typeof callback === 'function') {
      _callback_map[callbackID] = callback;
    };

    var msgObj = {'func':func,'params':params};
    msgObj[_MESSAGE_TYPE] = 'call';        
    msgObj[_CALLBACK_ID] = callbackID;

    _sendMessage(JSON.stringify(msgObj));
}

这里的方法看到了funcparamscallback三个参数,分别

  • func对应调用方法的一个tag,看上去像是一个标志

*params从调用的情况来看,会传入一个对象或者一个映射表,经过_call之后会被转换成一个JSON放入消息队列中

*callback,从调用情况来看,传入回调方法之后,callbackID首先先自增 1,如果传入的是一个方法的话,,那么就会把这个 callbackID 放入原本空的_callback_map,经过搜索看到了之前会先定义一个空的_callback_map

_handleMessageFromWeixin

调查_callback_map的时候,查到了_handleMessageFromWeixin,那么就继续来看这个方法。首先微信是在handle层来接受方法的,部分代码如下:

switch(msgWrap[_MESSAGE_TYPE]){
  case 'callback':
  {       
    if(typeof msgWrap[_CALLBACK_ID] === 'string' && typeof _callback_map[msgWrap[_CALLBACK_ID]] === 'function'){
      var ret = _callback_map[msgWrap[_CALLBACK_ID]](msgWrap['__params']);
      delete _callback_map[msgWrap[_CALLBACK_ID]]; 
      _setResultValue('SCENE_HANDLEMSGFROMWX', JSON.stringify(ret));
      return JSON.stringify(ret);
    }
  _setResultValue('SCENE_HANDLEMSGFROMWX', JSON.stringify({'__err_code':'cb404'}));
    return JSON.stringify({'__err_code':'cb404'});          
  }

这里参数传入的是一个message,同时我们看到了在_handleMessageFromWeixin里面进行了消息类型的判断。首先是callback类型,不过通过代码的分析,暂时我还不知道怎么算是callback类型,暂时我们先看下去。从实现来看,只要调用_call这个方法的并且传入callback参数的消息都算是callback类型的消息,这里微信做了一次delete,应该是说方法只使用一次,用完删除。如果判断失败就会给 404,这个逻辑还是很清楚的。

case 'event':
{
  if (typeof msgWrap[_EVENT_ID] === 'string' ) {
       if (typeof _event_hook_map_for3rd[msgWrap[_EVENT_ID]] === 'function' && _isIn3rdApiList(msgWrap[_EVENT_ID])) {                                                                                                                                                                                       
           var ret = _event_hook_map_for3rd[msgWrap[_EVENT_ID]](msgWrap['__params']);
           _setResultValue('SCENE_HANDLEMSGFROMWX', JSON.stringify(ret));
           return JSON.stringify(ret);
       } else if(typeof _event_hook_map[msgWrap[_EVENT_ID]] === 'function') {
           var ret = _event_hook_map[msgWrap[_EVENT_ID]](msgWrap['__params']);
           _setResultValue('SCENE_HANDLEMSGFROMWX', JSON.stringify(ret));
           return JSON.stringify(ret);
       }

  }
  //window.JsApi && JsApi.keep_setReturnValue && window.JsApi.keep_setReturnValue('SCENE_HANDLEMSGFROMWX', JSON.stringify({'__err_code':'ev404'}));
  _setResultValue('SCENE_HANDLEMSGFROMWX', JSON.stringify({'__err_code':'ev404'}));
  return JSON.stringify({'__err_code':'ev404'});
}

查遍了 js 文件没有看到_EVENT_ID类型是啥,不过从判断来看应该是给外部第三方应用来使用的,逻辑同上。

var ret;
var msgWrap
if (_isUseMd5 === 'yes') {
  var realMessage = message[_JSON_MESSAGE];
  var shaStr = message[_SHA_KEY];
  var arr = new Array;
  arr[0] = JSON.stringify(realMessage);
  arr[1] = _xxyy;
  var str = arr.join("");
  var msgSha = '';
    var shaObj = CryptoJS.SHA1(str);
    msgSha = shaObj.toString();
    if (msgSha !== shaStr) {
        _log('_handleMessageFromWeixin , shaStr : ' + shaStr + ' , str : ' + str + ' , msgSha : ' + msgSha);
        return '{}';

    }
    msgWrap = realMessage;
}
else {
  msgWrap = message;
}

这段其实简单来看就是作了一个校验的过程,这段代码是在拿到message最开始就做的,详细加密的算法可见base64encode(js:1854)方法。

_setResultValue && _continueSetResult

var _setResultQueue = [];
var _setResultQueueRunning = false;

function _setResultValue(scene, result) {
    if (result === undefined) {
        result = 'dummy';
    }
    _setResultQueue.push(scene + '&' + base64encode(UTF8.encode(result)));
    if (!_setResultQueueRunning) {
        _continueSetResult();
    }
}

这里可以看到扔到了一个_setResultQueue队列里,队列原本为空。接下来关键就是_continueSetResult(),事实证明这的确是一个很重要的方法。

function _continueSetResult() {
    var result = _setResultQueue.shift();
    if (result === undefined) {
        _setResultQueueRunning = false;
    } else {
        _setResultQueueRunning = true;
        _setResultIframe.src = 'weixin://private/setresult/' + result;
    }
}

虽然我不知道 js 中的shift()啥意思,但大概能够明白,将一个iframesrc赋予以 weixin://private/setresult/加上一个结果的 URI 地址,到了这里就感觉到了一些眉目了。

_createQueueReadyIframe

_setResultIframe = doc.createElement('iframe');

在这个方法里我们可以清楚的看到了_setResultIframe在这里被初始化了,创建了一个新的iframe。返回了一个已经准备好接受消息的iframe

_on

继续翻阅的时候发现了这个很神奇的方法,猜想可能是给 3rd 用的,结果还真是如此。

function _on(event,callback){
  if (!event || typeof event !== 'string') {
      return;
  };

  if (typeof callback !== 'function') {
    return;
  };

  _event_hook_map[event] = callback;
}

_call一样,只不过将传入的 callback 方法放入另外一个 map 表中,类型为event,到这里我们大概能够明白了。的确,_event_hook_map是给外部应用所存储的类型表。调用到的地方有

    function _on(event,callback){
...
}

      _on('menu:share:timeline',function(argv){...}
      _on('menu:share:qq', function(argv){...}
      _on('menu:share:weiboApp',function(argv){...}
      _on('menu:share:QZone', function(argv){...}
      _on('menu:share:appmessage',function(argv){...}
      _on('menu:share:email',function(argv) {...}

结论

反编译的class.dex还没有看。但从上面的分析来看基本上可以理解为调用微信 webview 基本上是这样一些特点

*微信很好的区分了内部调用和外部调用

*同样的有自定义的 URI 规则,这应该每家公司都是一样的

*js 调用 native 的时候对IFrame的 src 属性的变更,增加了队列机制

*_handleMessageFromWeixin方法为 js 监听 native 的方法

扔了class.dex,不知道这个里面还能看到什么不,有空再看了

原文:https://testerhome.com/topics/4083

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号