导读

背景

  • 回顾2018.目前.小程序大概有几家比较主流的,微信,百度,支付宝,头条…..微信小程序,暂时在小程序被使用体量最大.另外,还有类似的安卓九大厂商联合的轻应用.从种种迹象表明,基于js的动态化,占用比重越来越大,app原生需求,可能会进一步下降.这里刚好有时间,就从原理上,了解一ios端上的小程序,实现逻辑.

  • 这里选择逆向百度,研究一下小程序的实现.本来是打算使用微信的,奈何修改bundle id后,登录了一次就提示微信号违规,有被封可能.而头条的,ios端刚好又被下架了.

  • 下文所说的 masterWebView或逻辑容器等价,slaveWebView或渲染容器等价,逻辑容器主要处理逻辑,渲染容器只做界面处理,这里可能有部分观点描述不恰当,请见谅…

image

需要技能

  • 1.js基础,ios开发基础.

  • 2.会使用Xcode,safari,有基础js编写能力.

MonkeyDev-自动化,非越狱,逆向神器

逆向ipa

  • 这里不细说了,使用monkeydev,非常简单.上网资源一搜一大堆,另外,本人之前也写了一个简单的使用说明.

  • 另外,再次集成了flex,这个机器上调试神器.

从小程序角度分析

获取具体页面js,css,swan,json文件 (这里文件命名只对应,百度小程序,其他小程序估计可以类比)

  • app使用felx查看路径

image

  • 导出到mac后的汇总路径 image

分析文件结构

初步分析目录结构
  • 初步目录分析,其实从这里看,每个详情页的js代码,都被打包到一个,pages.js文件,这里可以猜测,百度小程序的逻辑都是单独在一个wkwebview,运行的,下面还有详细说明,证实这个情况. image
从page.js文件,思考情页逻辑引入机制
  • 其实从这个文件可以看出,每个详情页的处理逻辑都通过这个引入,封装引入通过一个JSCore调度,实现与原生交互,window.define这部分,由于本人js只懂皮毛,初步整理一下整理一下,以后再深究 另外,我猜猜其中的n是指swan的全局对象
window.define("pages/custom/custom", function (t, e, a, o, n, s, i, d, r, c, u, l, f, h, g, m, p)
{
	//省略一万字
}),
, window.define("pages/custom/custom", function (t, e, a, o, n, s, i, d, r, c, u, l, f, h, g, m, p) {
	//省略一万字
})

image

  • page.js文件,末尾是一个全局路由表,猜测这个是用来做做页面跳转时,切换逻辑处理的索引
    window.__swanRoute = "pages/index/index", window.usingComponents = [], require("pages/index/index"), 
    window.__swanRoute = "pages/custom/custom", window.usingComponents = [], require("pages/custom/custom"), 
    window.__swanRoute = "pages/search/search", window.usingComponents = [], require("pages/search/search"), 
    window.__swanRoute = "pages/detail/detail", window.usingComponents = [], require("pages/detail/detail"), 
    window.__swanRoute = "pages/mycolumn/mycolumn", window.usingComponents = [], require("pages/mycolumn/mycolumn"), 
    window.__swanRoute = "pages/hotspotdetail/hotspotdetail", window.usingComponents = [], require("pages/hotspotdetail/hotspotdetail"),
    window.__swanRoute = "pages/specialdetail/specialdetail", window.usingComponents = [], require(
        "pages/specialdetail/specialdetail"), 
    window.__swanRoute = "pages/hotconcept/hotconcept", window.usingComponents = ["components/ec-canvas/ec-canvas"], require("pages/hotconcept/hotconcept"), 
    window.__swanRoute = "pages/personalcenter/personalcenter", window.usingComponents = [], require("pages/personalcenter/personalcenter"),
    window.__swanRoute = "pages/download/download", window.usingComponents = [], require("pages/download/download"),
    window.__swanRoute = "pages/mycoupon/mycoupon", window.usingComponents = [], require("pages/mycoupon/mycoupon"),
    window.__swanRoute = "pages/comment/comment", window.usingComponents = [], require("pages/comment/comment"), 
    window.__swanRoute = "pages/version/version", window.usingComponents = [], require("pages/version/version"), 
    window.__swanRoute = "pages/webview/webview", window.usingComponents = [], require("pages/webview/webview"), 
    window.__swanRoute = "pages/minigramcode/minigramcode", window.usingComponents = [], require("pages/minigramcode/minigramcode"), 
    window.__swanRoute = "pages/miniqrcode/miniqrcode", window.usingComponents = [], require("pages/miniqrcode/miniqrcode"), 
    window.__swanRoute = "pages/push/push", window.usingComponents = [], require("pages/push/push");
从page.js文件,分析app请求
  • 请求对比
//这是百度小程序的官网请求demo
swan.request({
    url: 'https://smartprogram.baidu.com/xxx', // 仅为示例,并非真实的接口地址
    method: 'GET',
    dataType: 'json',
    data: {
        key: 'value'
    },
    header: {
        'content-type': 'application/json' // 默认值
    },
    success: function (res) {
        console.log(res.data);
    },
    fail: function (err) {
        console.log('错误码:' + err.errCode);
        console.log('错误信息:' + err.errMsg);
    }
});

//这是逆向出来的请求

L = D.getSelectNewUrl,

n.request({
                    url: L,
                    data: {
                        fromversion: 530
                    },
                    method: "GET",
                    success: function (e) {
                        if (e.data.status) {
                            var a = e.data.info,
                                o = a.toplist.concat(a.subscribelist);
                            o.unshift({
                                channelId: "006688",
                                title: "专栏"
                            }), o.splice(3, 0, {
                                channelId: "779900",
                                title: "热点"
                            }), console.log("menuListData:", o);
                            for (var n = 0; n < o.length; n++) R[n] = new Array, 5 == o[n].type &&
                                o.splice(n, 1);
                            t.setData({
                                menuList: o,
                                allArticles: R
                            })
                        }
                        t.loadHeadLineArticles()
                    },
                    fail: function () {
                        n.showLoading({
                            title: "网络出现故障",
                            duration: 1500
                        })
                    }
                })
从common.js文件,分析app请求

//截取其中一段细看,可以看出和讯网的app所有请求路径都封装在同一个文件中,通过这个文件,基本就可以分析出,请求的地址了

window.define("config",
function(t, e, n, i, a, r, o, s, l, u, c, h, d, f, p, g, m) {
    "use strict";
    var v, y, x, _, b, w, S = "nwapi.hexun.com",
    M = "wapi.hexun.com",
    v = "m.hexun.com",
    y = "wxin.hexun.com",
    x = "callvip.hexun.com",
    _ = "callwx.hexun.com",
    b = "redian.hexun.com",
    w = "api-stockmatch.hexun.com",
    I = {
        upDataWx: !1,
        appid: "wx25371c74b74ebb96",
        mHost: v,
        openToolHost: "opentool.hexun.com",
        newsSearchHost: "newssearch.hexun.com",
        callVipHost: y,
        callWxHost: _,
        regToolHost: "regtool.hexun.com",
        statiSticsHost: "anautrack.hexun.com",
        nwApiHost: S,
        wApiHost: M,
        stackRoomHost: b,
        hqwiApiHost: "hqwiapi.hexun.com",
        stockMatchHost: w,
        commentToolHost: "commenttool.hexun.com",
        statiSticsUrl: "https://anautrack.hexun.com/pubinterface/io/fileinterface.aspx",
        getSelectUrl: "https://" + v + "/api/getMsiteCategoryInfoList",
        getDuiBaLoginUrl: "https://" + v + "/duiba/getLoginUrl",
        getSelectNewUrl: "https://" + S + "/version/selectchannel",
        newsRecommendUrl: "https://opentool.hexun.com/MongodbNewsService/getDGBackupNewsListByPid.jsp",
        headLinesUrl: "https://" + M + "/Head_newsJson.cc?appId=1&pid=100234721&pc=20&pn=1&num=2&fromhost=ywkf",
        isStockOnOffUrl: "https://" + w + "/api/redis/isTransaction",
        stockGrailUrl: "https://hqwiapi.hexun.com/a/quotelist",
        getHotPointNewsUrl: "https://" + M + "/AppPopUp_getHotPointNews.cc",
        futuresUrl: "https://" + M + "/Mix_newsJson.cc",
        twentyFourHoursUrl: "https://" + S + "/liveNews/getNews",
        getNewsBriefingUrl: "https://" + S + "/NewsInfo/getNewsBriefing",
        twentyFourCountUrl: "https://" + S + "/liveNews/getNewNewsCount",
        newsListUrl: "https://" + v + "/api/getMoreNews",
        newsListNewUrl: "https://" + M + "/AppV5_newsJson_V5.cc",
        getMoreListNewUrl: "https://" + S + "/liveNews/getMoreNews",
        wxHeXunLoginUrl: "https://regtool.hexun.com/wapreg/wechat/miniapp.aspx",
        heXunJsCodeLoginUrl: "https://regtool.hexun.com/wapreg/wechat/jscode_login.aspx",
        checkBindMobileUrl: "https://regtool.hexun.com/wapreg/checkbindmobile.aspx",
        wxGoldRewardUrl: "https://" + _ + "/share/msite_api/first_add_gold",
        getAccReditUrl: "https://" + _ + "/share/platform_user/is_first_visit",
        wxGiveGoldUrl: "https://" + x + "/rpsapi/service",
        recordHXWShareInfoUrl: "https://" + y + "/hxwx-web/share/recordHXWShareInfo",
        getOpenIdUrl: "https://" + y + "/hxwx-web/get/openId",
        bindPhoneNumberUrl: "https://" + y + "/hxwx-web/decrypt/bindMoble",
        onOffPushSwitchUrl: "https://" + y + "/hxwx-web/pushReceiveIntention/modify",
        getPushStatusUrl: "https://" + y + "/hxwx-web//pushReceiveIntention/query",
        storageInviteUrl: "https://" + _ + "/share/sapi/interact_by_user",
        storageShareUrl: "https://" + _ + "/share/sapi/share_by_user",
        getHXWShareInfoUrl: "https://" + y + "/hxwx-web/share/getHXWShareInfo",
        getPayApiUrl: "https://" + x + "/payapi/service?service=query_balance",
        detailsUrl: "https://opentool.hexun.com/MongodbNewsService/forapp/gethtml.jsp",
        detailsNewsUrl: "https://" + v + "/api/getNewsContent",
        detailsColumnNewsUrl: "https://" + M + "/AppV5_commonNewsDetail.cc",
        detailsRecommendUrl: "https://opentool.hexun.com/MongodbNewsService/forapp/getrelativenewsJson.jsp",
        newsSearchUrl: "https://newssearch.hexun.com/nocallback/newsjson",
        setSwitchOnOff: "https://" + y + "/hxwx-web/comm/switch",
        wxImgCodeUrl: "https://" + y + "/hxwx-tools/img/getDetailCode",
        getCommentUrl: "https://commenttool.hexun.com/Comment/GetComment.do",
        getGiveLikeUrl: "https://commenttool.hexun.com/Comment/praise.do",
        getPostCommentUrl: "https://commenttool.hexun.com/Comment/PostComment.do",
        saveFormIdUrl: "https://" + y + "/hxwx-web/tmpMsg/saveFormId",
        saveReadNewsUrl: "https://" + y + "/hxwx-web/news/saveNewsClickRecord",
        getReadOrgNewsUrl: "https://" + y + "/hxwx-web/news/getRNewsInfoByUsrId",
        readArticleGoldUrl: "https://" + _ + "/share/msite_api/rader_add_gold",
        shareArticleGoldUrl: "https://" + _ + "/share/msite_api/share_article_add_gold",
        getMoreColumnUrl: "https://" + M + "/Column_columnMore.cc",
        getColumnInfoUrl: "https://" + M + "/Column_columnInfo.cc",
        getYetColumnListUrl: "https://" + M + "/Column_columnlist.cc",
        postAttentionColumnUrl: "https://" + M + "/Column_focustatus.cc",
        getColumnNewsUrl: "https://" + M + "/Column_columnNews.cc",
        openIdForAppUrl: "https://" + y + "/hxwx-web/get/openIdForApp",
        openIdForNewAppUrl: "https://" + y + "/hxwx-web/get/openIdForAppUseCookies",
        getHostIdeaListUrl: "https://" + b + "/concept/list",
        getHostDetailsUrl: "https://" + b + "/concept/detail/",
        getHostStocksUrl: "https://" + b + "/concept/stocks/",
        getHostNewsUrl: "https://" + b + "/concept/h5/news/",
        getHostChartUrl: "https://" + b + "/concept/chart/",
        getHotSpotDetailUrl: "https://" + b + "/concept/h5/newsdetail/",
        getCouponQueryUrl: "https://" + _ + "/cbsapi/api/couponQuery",
        useCouponUrl: "https://" + _ + "/cbsapi/api/cashCouponConvert",
        getCouponUrl: "https://" + _ + "/cbsapi/api/receiveCoupon",
        getVideoInfoUrl: "https://" + _ + "/wxitem/hxApplet/getVideoInfo",
        getAdInfoUrl: "https://" + _ + "/wxitem/hxApplet/getAdInfo",
        getAuthRedPacketUrl: "https://" + _ + "/wxitem/hxApplet/getAuthRedPacketInfo",
        getRedPacketInfoUrl: "https://" + _ + "/wxitem/hxApplet/getActivitInfo",
        getPushInfoUrl: "https://" + _ + "/wxitem/hxApplet/getPushInfo",
        getTextConfigUrl: "https://" + _ + "//wxitem/hxAppletTextConfig/getTextConfig",
        guessHeadBetGoldUrl: "https://" + _ + "/msite/gh/guess_headline",
        guessHeadListsUrl: "https://" + _ + "/msite/gh/list_headline",
        guessHeadHistoryListsUrl: "https://" + _ + "/msite/gh/history_headline",
        guessHeadSearchUseGoldUrl: "https://" + _ + "/msite/gh/user_usegold",
        guessHeadSearchTotalGoldUrl: "https://" + _ + "/msite/gh/user_totalgold",
        guessHeadGetServeDaterUrl: "https://" + _ + "/msite/gh/get_serverdate",
        guessHeadMyRecordUrl: "https://" + _ + "/msite/gh/guess_record"
    };
    e.exports = I
}),

image

总结
  • 通过上述分析,可以看出这个小程序大部分都没有加密的,随机抽取一个接口使用postman获取数据请求数据,这个其实也可以捉包查看的.

  • 在Safari的控制台中观察到,所有请求都没有在控制台中显示网络加载,所以猜测所有的app请求都没原生接管了,这里就可以通过原生,控制app允许访问的域名.

//测试请求域名
https://nwapi.hexun.com/version/selectchannel

image

从app角度分析

确认原生渲染使用是uiwebview还是wkwebview

  • reaveal查看,百度小程序,打开一个小程序时,发现在于windon下存在一个隐藏wkwebview.初步猜测,该单例专做js与原生交互并做逻辑更新处理,另外发现该渲染的控制器为,SWANSlaveWebViewController.

image

  • 分析SWANSlaveWebViewController.h,发现存在 masterWebView, webView , _webViewComponent 这个三个基于wkwebview的对象声明,所以基本可以肯定使用的wkwebview
@property(retain, nonatomic) WKWebView *masterWebView; // @synthesize masterWebView=_masterWebView;
@property(retain, nonatomic) WKWebView *webView; // @synthesize webView=_webView;
WKWebView *_webViewComponent;
#import <UIKit/UIViewController.h>

#import "BBAAutoKeyboardDelegate-Protocol.h"
#import "SWANCoverLayoutDelegate-Protocol.h"
#import "SWANSlaveWebViewControllerUIProtocol-Protocol.h"
#import "SWANVideoHandleDelegate-Protocol.h"
#import "WKNavigationDelegate-Protocol.h"
#import "WKUIDelegate-Protocol.h"

@class NSMutableDictionary, NSMutableSet, NSNumber, NSString, NSURLComponents, SWANCanvasComponent, SWANCircleRefreshHeader, SWANConfigWindow, SWANCoverComponent, SWANOpenFlowLogger, SWANSlaveWebViewFactory, UIBarButtonItem, UIButton, UILabel, UIProgressView, UIView, WKWebView;

@interface SWANSlaveWebViewController : UIViewController <BBAAutoKeyboardDelegate, SWANSlaveWebViewControllerUIProtocol, SWANVideoHandleDelegate, WKUIDelegate, WKNavigationDelegate, SWANCoverLayoutDelegate>
{
    _Bool _didNavigationBarShowLoading;
    _Bool _isFirstShow;
    _Bool _notSupportFullscreenPopGesture;
    _Bool _hasIninialAnimationVCAsChildVC;
    _Bool _navbarColorAnimationing;
    _Bool _isKeyboardShow;
    _Bool _isFisrtPlayVideo;
    NSString *_appId;
    long long _webViewID;
    SWANConfigWindow *_window;
    NSString *_URL;
    NSString *_bundlePath;
    SWANCircleRefreshHeader *_refreshHeader;
    NSString *_routeType;
    NSString *_liveId;
    SWANCanvasComponent *_canvasComponent;
    SWANCoverComponent *_coverComponent;
    WKWebView *_webView;
    WKWebView *_masterWebView;
    UILabel *_titleLabel;
    NSString *_pagePath;
    UIBarButtonItem *_backItem;
    UIButton *_backButton;
    UIButton *_closeButton;
    UIButton *_goHomeButton;
    UIBarButtonItem *_goHomeItem;
    UIButton *_moreMenuButton;
    UIView *_rightCustomView;
    UIView *_dividingLineView;
    WKWebView *_webViewComponent;
    UIProgressView *_webViewComponentProgressView;
    NSString *_passportRedirectURL;
    NSMutableDictionary *_viewMap;
    UIViewController *_initialAnimationController;
    UIView *_navbarAnimationView;
    long long _savedCurrentPlayerStatus;
    UIView *_headerView;
    NSURLComponents *_urlComponents;
    NSString *_swanCoreVersion;
    NSString *_swanCorePath;
    NSMutableSet *_dispatcherSet;
    NSMutableDictionary *_componentsViewMap;
    UIView *_safeAreaView;
    SWANSlaveWebViewFactory *_factory;
    SWANOpenFlowLogger *_loadingTimeFlow;
    struct CGRect _liveOriginFrame;
}

@property(retain, nonatomic) SWANOpenFlowLogger *loadingTimeFlow; // @synthesize loadingTimeFlow=_loadingTimeFlow;
@property(retain, nonatomic) SWANSlaveWebViewFactory *factory; // @synthesize factory=_factory;
@property(retain, nonatomic) UIView *safeAreaView; // @synthesize safeAreaView=_safeAreaView;
@property(readonly, nonatomic) NSMutableDictionary *componentsViewMap; // @synthesize componentsViewMap=_componentsViewMap;
@property(retain, nonatomic) NSMutableSet *dispatcherSet; // @synthesize dispatcherSet=_dispatcherSet;
@property(copy, nonatomic) NSString *swanCorePath; // @synthesize swanCorePath=_swanCorePath;
@property(copy, nonatomic) NSString *swanCoreVersion; // @synthesize swanCoreVersion=_swanCoreVersion;
@property(retain, nonatomic) NSURLComponents *urlComponents; // @synthesize urlComponents=_urlComponents;
@property(nonatomic) _Bool isFisrtPlayVideo; // @synthesize isFisrtPlayVideo=_isFisrtPlayVideo;
@property(retain, nonatomic) UIView *headerView; // @synthesize headerView=_headerView;
@property(nonatomic) _Bool isKeyboardShow; // @synthesize isKeyboardShow=_isKeyboardShow;
@property(nonatomic) long long savedCurrentPlayerStatus; // @synthesize savedCurrentPlayerStatus=_savedCurrentPlayerStatus;
@property(nonatomic) _Bool navbarColorAnimationing; // @synthesize navbarColorAnimationing=_navbarColorAnimationing;
@property(nonatomic) __weak UIView *navbarAnimationView; // @synthesize navbarAnimationView=_navbarAnimationView;
@property(nonatomic) __weak UIViewController *initialAnimationController; // @synthesize initialAnimationController=_initialAnimationController;
@property(nonatomic) _Bool hasIninialAnimationVCAsChildVC; // @synthesize hasIninialAnimationVCAsChildVC=_hasIninialAnimationVCAsChildVC;
@property(nonatomic) _Bool notSupportFullscreenPopGesture; // @synthesize notSupportFullscreenPopGesture=_notSupportFullscreenPopGesture;
@property(nonatomic) _Bool isFirstShow; // @synthesize isFirstShow=_isFirstShow;
@property(nonatomic) struct CGRect liveOriginFrame; // @synthesize liveOriginFrame=_liveOriginFrame;
@property(readonly, nonatomic) NSMutableDictionary *viewMap; // @synthesize viewMap=_viewMap;
@property(copy, nonatomic) NSString *passportRedirectURL; // @synthesize passportRedirectURL=_passportRedirectURL;
@property(retain, nonatomic) UIProgressView *webViewComponentProgressView; // @synthesize webViewComponentProgressView=_webViewComponentProgressView;
@property(retain, nonatomic) WKWebView *webViewComponent; // @synthesize webViewComponent=_webViewComponent;
@property(retain, nonatomic) UIView *dividingLineView; // @synthesize dividingLineView=_dividingLineView;
@property(retain, nonatomic) UIView *rightCustomView; // @synthesize rightCustomView=_rightCustomView;
@property(retain, nonatomic) UIButton *moreMenuButton; // @synthesize moreMenuButton=_moreMenuButton;
@property(retain, nonatomic) UIBarButtonItem *goHomeItem; // @synthesize goHomeItem=_goHomeItem;
@property(retain, nonatomic) UIButton *goHomeButton; // @synthesize goHomeButton=_goHomeButton;
@property(retain, nonatomic) UIButton *closeButton; // @synthesize closeButton=_closeButton;
@property(retain, nonatomic) UIButton *backButton; // @synthesize backButton=_backButton;
@property(retain, nonatomic) UIBarButtonItem *backItem; // @synthesize backItem=_backItem;
@property(nonatomic) _Bool didNavigationBarShowLoading; // @synthesize didNavigationBarShowLoading=_didNavigationBarShowLoading;
@property(copy, nonatomic) NSString *pagePath; // @synthesize pagePath=_pagePath;
@property(retain, nonatomic) UILabel *titleLabel; // @synthesize titleLabel=_titleLabel;
@property(retain, nonatomic) WKWebView *masterWebView; // @synthesize masterWebView=_masterWebView;
@property(retain, nonatomic) WKWebView *webView; // @synthesize webView=_webView;
@property(retain, nonatomic) SWANCoverComponent *coverComponent; // @synthesize coverComponent=_coverComponent;
@property(retain, nonatomic) SWANCanvasComponent *canvasComponent; // @synthesize canvasComponent=_canvasComponent;
@property(copy, nonatomic) NSString *liveId; // @synthesize liveId=_liveId;
@property(copy, nonatomic) NSString *routeType; // @synthesize routeType=_routeType;
@property(retain, nonatomic) SWANCircleRefreshHeader *refreshHeader; // @synthesize refreshHeader=_refreshHeader;
@property(copy, nonatomic) NSString *bundlePath; // @synthesize bundlePath=_bundlePath;
@property(copy, nonatomic) NSString *URL; // @synthesize URL=_URL;
@property(copy, nonatomic) SWANConfigWindow *window; // @synthesize window=_window;
@property(nonatomic) long long webViewID; // @synthesize webViewID=_webViewID;
@property(retain, nonatomic) NSString *appId; // @synthesize appId=_appId;
- (void).cxx_destruct;
- (unsigned long long)supportedInterfaceOrientations;
- (_Bool)shouldAutorotate;
- (id)coverLayout:(id)arg1 fetchViewWithID:(id)arg2;
- (void)componentsMapRemoveAllSubviewsFromView:(id)arg1 viewID:(id)arg2;
- (void)componentsMapRemoveSubviewWithID:(id)arg1;
- (id)componentsMapGetViewWithID:(id)arg1;
- (_Bool)componentsMapSetSubview:(id)arg1 withID:(id)arg2;
- (void)webView:(id)arg1 decidePolicyForNavigationAction:(id)arg2 decisionHandler:(CDUnknownBlockType)arg3;
- (void)webView:(id)arg1 decidePolicyForNavigationResponse:(id)arg2 decisionHandler:(CDUnknownBlockType)arg3;
- (void)webView:(id)arg1 runJavaScriptConfirmPanelWithMessage:(id)arg2 initiatedByFrame:(id)arg3 completionHandler:(CDUnknownBlockType)arg4;
- (void)webView:(id)arg1 runJavaScriptAlertPanelWithMessage:(id)arg2 initiatedByFrame:(id)arg3 completionHandler:(CDUnknownBlockType)arg4;
- (void)webView:(id)arg1 runJavaScriptTextInputPanelWithPrompt:(id)arg2 defaultText:(id)arg3 initiatedByFrame:(id)arg4 completionHandler:(CDUnknownBlockType)arg5;
- (void)cancelLoadingTimeFLow;
- (_Bool)endLoadingTimeFLow:(id)arg1;
- (void)startLoadingTimeFlow;
- (void)dispatchToSlaveEventJS:(id)arg1 completionHandler:(CDUnknownBlockType)arg2;
- (void)dispatchToSlaveEventName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;
- (_Bool)currentViewsContainsVideoPlayerBaseView:(id)arg1;
- (void)dealloc;
- (void)viewWillLayoutSubviews;
- (void)viewDidDisappear:(_Bool)arg1;
- (void)viewWillDisappear:(_Bool)arg1;
- (void)resetScrollViewOffsetYZero;
- (void)viewDidAppear:(_Bool)arg1;
- (void)viewWillAppear:(_Bool)arg1;
- (struct CGRect)getWebViewFrame;
- (void)addComponent;
- (void)createWebView;
- (void)viewDidLoad;
- (void)addSlaveWebView;
- (void)updateViewsColor;
- (void)actionsAfterViewDidLoad;
- (id)dispatchPageReadyEventParams;
- (void)updateWebViewProperty;
- (void)dispatchPageReadyEvent;
@property(readonly, nonatomic) _Bool finishLoadWindowConfig;
- (void)loadView;
- (void)setupWithModel:(id)arg1;
- (id)initWithModel:(id)arg1;
- (void)sendDeallocEvent;
- (void)sendRouteEvent;
- (void)sendNavigationBackEvent:(id)arg1;
- (void)dispatchOnHideEvent;
- (void)dispatchOnShowEvent;
- (void)composeShortcut:(id)arg1;
- (id)keyCommands;
- (void)updateLiveWebViewContrainerFrame;
- (unsigned long long)supportedInterfaceOrientations;
- (_Bool)shouldAutorotate;
- (void)playerScreenChangeToFull:(_Bool)arg1 direction:(long long)arg2 liveId:(id)arg3;
- (void)livePlayerExitFullScreen;
- (void)livePlayerEnterFullScreen:(long long)arg1;
- (void)viewWillTransitionToSize:(struct CGSize)arg1 withTransitionCoordinator:(id)arg2;
- (void)salveWebViewFroLiveClose;
- (void)slaveWebViewForLiveDealloc;
- (void)slaveWebViewForLiveDidEnterBackground;
- (void)slaveWebViewForLiveWillEnterForeground;
- (void)slaveWebViewForLiveDidDisappear;
- (void)slaveWebViewForLiveWillDisappear;
- (void)slaveWebViewForLiveWillAppear;
- (void)slaveWebViewForLiveDidAppear;
- (void)handlekeyboardWillhide:(id)arg1;
- (void)handlekeyboardWillShow:(id)arg1;
- (void)handleStatusBarClicked:(id)arg1;
- (void)handleApplicationDidEnterBackground:(id)arg1;
- (void)handleApplicationWillEnterForeground:(id)arg1;
- (void)addNotificationObserver;
- (_Bool)webViewDidTerminate;
- (void)setWebViewDidTerminate:(_Bool)arg1;
- (void)reloadWKWebViewWhenOutOfMemory;
- (_Bool)masterWebViewIsValid;
- (void)recoverySlaveWebViewIfNeeded;
- (void)recoveryIfNeeded;
- (void)webViewWebContentProcessDidTerminate:(id)arg1;
- (void)textView:(id)arg1 updateTextPostionWithKeyboardHeight:(double)arg2;
- (void)textViewKeyboardWillHide:(id)arg1;
- (void)creatScrollViewHeaderView;
@property(retain, nonatomic) NSNumber *webViewYWhenKeyboardIsVisible;
- (void)webViewScrollEnabled:(_Bool)arg1;
- (void)fullscreenPopGestureDidCancelPopAnimation;
- (void)fullscreenPopGestureWillCancelPopAnimation;
- (void)recoverFullscreenPopGestureStopAction;
- (void)fullscreenPopGestureDidFinishPopAnimation;
- (void)fullscreenPopGestureWillFinishPopAnimation;
- (void)fullscreenPopGestureWillPopAnimation;
- (_Bool)edgePopGestureShouldRecognizeSimultaneously;
- (_Bool)supportEdgePopGesture;
- (_Bool)supportFullscreenPopGesture;
- (void)scrollViewDidScroll:(id)arg1;
- (void)openWebViewUserInteractionEnabled;
- (void)stopWebViewUserInteractionEnabled;
- (void)scrollViewDidEndDragging:(id)arg1 willDecelerate:(_Bool)arg2;
- (void)scrollViewWillBeginDragging:(id)arg1;
- (id)slaveContentScrollView;
- (void)enableWebViewScrollable;
- (void)disableWebViewScrollable;
- (id)findNativeViewWithViewID:(id)arg1;
- (void)removeNativeViewWithViewID:(id)arg1;
- (void)insertNativeView:(id)arg1 withViewID:(id)arg2;
- (void)removeNativeView:(id)arg1;
- (void)insertNativeView:(id)arg1;
- (void)adjustWebViewFrame;
- (void)layoutRightCustomViewIfNoNavigator;
- (double)mnpWebViewYDefaultValue;
- (_Bool)useCustomNavigationBar;
- (void)updateWebviewBackgroundStyle;
- (void)updateNavigationBarAppearance;
- (void)updateNavigationBarColorWithAnimation:(id)arg1;
- (void)changeLeftBarButtonItemsImage;
- (void)updateNavigationBarColor;
- (void)setStyleRightButtonView:(id)arg1;
- (void)loadRightCustomView;
- (void)updateNavigationRightBarButtonItems;
- (void)showLeftGoHomeBtnsIsFirstVC:(_Bool)arg1;
- (void)showLeftBackBtnsIsFirstVC:(_Bool)arg1;
- (void)updateNavigationLeftBarButtonItems;
- (void)updateRefreshHeaderCircleStyle;
- (void)addRefreshHeaderForWebView:(id)arg1;
- (void)replaceGlobalWindowData;
- (void)realUpdateNavBarTitle;
- (void)updateNavigationBarTitle;
- (void)updateNavigationBarLoading;
- (void)hideNavigationBarLoading;
- (void)showNavigationBarLoading;
- (void)resetNavigationItemTitleView:(id)arg1;
- (void)setSlaveStatusBarStyle;
- (id)getTextStyleColorForStyle:(id)arg1;
- (void)generateTitleLabelForRect:(struct CGRect)arg1;
- (void)showMoreMenu:(id)arg1;
- (void)cleanReportCountAndTimer;
- (void)setMoreMenuClickCount:(long long)arg1;
- (long long)moreMenuClickCount;
- (void)setReportTriggerTimer:(id)arg1;
- (id)reportTriggerTimer;
- (void)clickGoHome:(id)arg1;
- (_Bool)shouldShowComeBackGuideView;
- (void)close;
- (void)close:(id)arg1;
- (void)back:(id)arg1;
- (void)createButtonsAndItems;
- (void)viewSafeAreaInsetsDidChange;
- (void)setScrollViewDelegate;
- (void)panGestureEnabledBBAPlayer:(_Bool)arg1;
- (_Bool)stopPlayWhenDidDisappear;
- (void)slaveWebViewForVideoDidAppear;
- (void)slaveWebViewForVideoWillDisappear;
- (void)playerVidWillChanged:(id)arg1;
- (void)playerDelegateWillChanged:(id)arg1 vid:(id)arg2;
- (void)playerViewDidRemovedFromSurperView:(id)arg1 vid:(id)arg2;
- (void)playerViewDidAddedToSurperView:(id)arg1 vid:(id)arg2;
- (_Bool)isNeedClearDraftAfterSendClick;
- (_Bool)isNeedUpdateBarrageListFromServer;
- (_Bool)playerShouldShowBarrageComponent:(long long)arg1 withPlayerMode:(unsigned long long)arg2;
- (void)playerSendBarrage:(id)arg1 playAtTime:(id)arg2 videoId:(id)arg3;
- (void)videoPlayerUpdateTimeProgress:(double)arg1 duration:(double)arg2 videoId:(id)arg3;
- (void)playerScreenChangeToNewMode:(unsigned long long)arg1 oldModel:(unsigned long long)arg2 videoId:(id)arg3 frame:(struct CGRect)arg4;
- (void)triggerEventWithType:(id)arg1 vid:(id)arg2 data:(id)arg3;
- (void)triggerEventWithType:(id)arg1 vid:(id)arg2;
- (void)playerDidFailed:(id)arg1 errorInfo:(id)arg2;
- (void)playerBufferEmpty:(id)arg1;
- (void)playerDidEnd:(id)arg1;
- (void)playerDidPause:(id)arg1;
- (void)playerDidPlaySuccess:(id)arg1;
- (void)playerDidPlay:(id)arg1;
- (void)reloadWebViewComponentVideoPage;
- (void)pauseWebViewComponentVideo;
- (void)webView:(id)arg1 didFinishNavigation:(id)arg2;
- (void)observeValueForKeyPath:(id)arg1 ofObject:(id)arg2 change:(id)arg3 context:(void *)arg4;
- (void)removeWebViewComponent;
- (_Bool)updateWebViewComponentForURL:(id)arg1 optionsDict:(id)arg2;
- (_Bool)gestureRecognizerShouldBegin:(id)arg1;
- (void)panGestureRecognizerAction:(id)arg1;
- (id)webView:(id)arg1 createWebViewWithConfiguration:(id)arg2 forNavigationAction:(id)arg3 windowFeatures:(id)arg4;
- (_Bool)insertWebViewComponentForURL:(id)arg1 optionsDict:(id)arg2;
- (void)stopCamera;
- (void)dismissTabBarWithAnimation:(_Bool)arg1;
- (void)showTabBarWithAnimation:(_Bool)arg1;
- (id)fakeTabBar;
- (void)setFakeTabBar:(id)arg1;
- (id)isInAnimation;
- (void)setIsInAnimation:(id)arg1;

// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;

@end

分析Safari,思考页面切换时,webview处理

  • 在首页时,小程序只有一个webview容器,在二级页面时, webview容器增加.且jscontext容器,数量不变.
  • 优点,正常情况下,wkwebview加载页面时,可能出现的性能问题,直接通过webview预加载,或者返回时,直接使用上一层已加载过的webview,所以小程序性能接近原生
  • 缺点,内存资源消耗会比正常页面多.

image

分析Safari,渲染时的html

  • html标准标签看,明显右边的格式和一个正常网页类似,但是标签又不是通用标签,也不是.swan标签,猜测是,存在一个三方工具,动态解析百度自定义的.swan内使用的标签,转为一种html的自定义标签,猜测是使用 “HTML Imports” 然后百度了一下,苹果的浏览器不支持.所以有可能是使用WebComponent的 document.registerElement来自定义标签.

customelements-国外作者

如何创建普通的HTML元素-简书

image

  • 通过具体定位,还是可以看出部分页面,还是使用了原生的html标记,详见下图.例如,养老这个标签,还是使用span标签的

image

  • 可以看出播放器时使用原生控件,SWANContainer使用这个类,而其他部分还是使用wkcompositingview渲染,另外选择中播放器元素时,并没有发生高亮,所以,基本可以确定,存在一个占位层,而原生播放器就放在占位层上.另外,当列表滚动时,播放器不跟随列表滚动,猜测可能是滚动换算,原生坐标可能存在难点.

image

image

总结

  • 小程序最根源也是使用wkwebview,使用web的标准实现基础功能,另外还有,播放器之类的就是,是在wkwebview上嵌套一个原生控件实现的并在底层放置占位的div,所以控件只能在最顶或最底层

image

从原生开发角度思考

更具百度小程序官方分析,概况为下图

image

hook (SWANSlaveWebViewController) 方法继续分析

  • hook以下方法,并debug,经分析基本可以确定, SWANSlaveWebViewController为单个展示小程序页面的容器
//hook这堆方法
//- (void)webView:(id)arg1 decidePolicyForNavigationAction:(id)arg2 decisionHandler:(CDUnknownBlockType)arg3;
//- (void)webView:(id)arg1 decidePolicyForNavigationResponse:(id)arg2 decisionHandler:(CDUnknownBlockType)arg3;
//- (void)webView:(id)arg1 runJavaScriptConfirmPanelWithMessage:(id)arg2 initiatedByFrame:(id)arg3 completionHandler:(CDUnknownBlockType)arg4;
//- (void)webView:(id)arg1 runJavaScriptAlertPanelWithMessage:(id)arg2 initiatedByFrame:(id)arg3 completionHandler:(CDUnknownBlockType)arg4;
//- (void)webView:(id)arg1 runJavaScriptTextInputPanelWithPrompt:(id)arg2 defaultText:(id)arg3 initiatedByFrame:(id)arg4 completionHandler:(CDUnknownBlockType)arg5;
//- (void)dispatchToSlaveEventJS:(id)arg1 completionHandler:(CDUnknownBlockType)arg2;
//- (void)dispatchToSlaveEventName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;
小程序文件,及对应内核关系
  • 可以看出swan-core是可以根据app动态下发的,用app版本是可以存在多个小程序内核

  • 而真正小程序会根据具配置动态下发代码

image

debug,对应接口的log
  • 这个函数就是 逻辑容器中,js执行setdata更新ui时,通知渲染容器更新对应ui的
  • 分析消息体,如下.这个涉及到数据的单向更新,逻辑容器执行js->渲染容器更新展示
//这是个捉更新ui时的通讯数据
{
    "type": "setData", //对应js更新方法名
    "slaveId": "1", //对应渲染webview的编号
    "setObject": {  //具体跟新的数值
        "tipsVisible": true
    },
    "pageUpdateStart": "1548991012832" //触发事件的时间戳
}

//苏宁小程序 切换顶部tab时捉出的通讯数据
{
"type": "querySlaveSelector",
"value": {
"selector": ".detail-tab-warpper",
"queryType": "select",
"index": 0,
"operation": "boundingClientRect",
"fields": {},
"execId": 99,
"contextId": null
}
}
 //============================================
 //原生函数 
- (void)dispatchToSlaveEventName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;

//部分参数log
2019-02-01 11:16:52.852357+0800 BaiduBoxApp[1364:197984] dispatchToSlaveEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"setData","slaveId":"1","setObject":{"tipsVisible":true},"pageUpdateStart":"1548991012832"}"
} 
 arg3:(null)
 
2019-02-01 11:16:52.852879+0800 BaiduBoxApp[1364:197984] dispatchToSlaveEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"setData","slaveId":"1","setObject":{"isRequesting":false},"pageUpdateStart":"1548991012832"}"
} 
 arg3:(null)
 
 2019-02-01 13:51:37.186025+0800 BaiduBoxApp[1627:245407] dispatchToSlaveEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"querySlaveSelector","value":{"selector":".detail-tab-warpper","queryType":"select","index":0,"operation":"boundingClientRect","fields":{},"execId":99,"contextId":null}}"
} 
  • 这个暂时未看出,但是好似每次初始化一个新的小程序页面都会触发如下,感觉是加载url时,拦截链接,并通过指定回调传给js.这里这么做的原材,猜测为小程序共享一个js容器,这个在渲染容器直接拦截这个结果,能减少逻辑容器和渲染容器的通讯.
getSlaveIdSync?callback=__jsna_6,getAppInfoSync?callback=__jsna_7,getSystemInfoSync?callback=__jsna_8
  • salaves.html js不太熟悉,暂时未看出具体实现功能,猜测是生成一个全局交互对象.方便原生和js交互
//slaves.html 原文件
//file:///var/mobile/Containers/Data/Application/B75FC88D-09AE-47F9-A436-79BBD0C2504C/Documents/SwanCaches/swan-core/preset/3.15.3/slaves/slaves.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>世界很复杂,百度更懂你</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <script type="text/javascript">
    (function (global) {
            // global._naSwan = undefined;
            global.swanGlobal = undefined;
            global.swanVersion = '3.15.4';
            global.feSlavePreloadStart = Date.now();
        })(window);

    </script>
    <link href="./styles_slaves.css" rel="stylesheet">
    
</head>

<body>
    <script type="text/javascript" src="./index.js"></script>
    <script type="text/javascript">
    (function (global) {
        global.feSlavePreloadEnd = Date.now();
    })(window);
    </script>
</body>

</html>
 //xcode 中打印函数
 //============================================
 //原生函数
- (void)webView:(id)arg1 runJavaScriptTextInputPanelWithPrompt:(id)arg2 defaultText:(id)arg3 initiatedByFrame:(id)arg4 completionHandler:(CDUnknownBlockType)arg5;

 //部分参数log
2019-02-01 11:20:31.909926+0800 BaiduBoxApp[1364:197984] runJavaScriptTextInputPanelWithPrompt
 arg1:<SWANSlaveWebView: 0x10bab2a00; frame = (0 64; 375 603); opaque = NO; autoresize = W+H; layer = <CALayer: 0x280e178e0>> 
 arg2:{"func":"dispatch","args":["baiduboxapp://v19/swan/getSlaveIdSync?callback=__jsna_6"]} 
 arg3: 
 arg4:<WKFrameInfo: 0x10b397600; webView = 0x10bab2a00; isMainFrame = YES; request = <NSMutableURLRequest: 0x280c73280> { URL: file:///var/mobile/Containers/Data/Application/B75FC88D-09AE-47F9-A436-79BBD0C2504C/Documents/SwanCaches/swan-core/preset/3.15.3/slaves/slaves.html }> 
 arg5:(null)
 
2019-02-01 11:20:31.965279+0800 BaiduBoxApp[1364:197984] runJavaScriptTextInputPanelWithPrompt
 arg1:<SWANSlaveWebView: 0x10bab2a00; frame = (0 64; 375 603); opaque = NO; autoresize = W+H; layer = <CALayer: 0x280e178e0>> 
 arg2:{"func":"dispatch","args":["baiduboxapp://v19/swan/getAppInfoSync?callback=__jsna_7"]} 
 arg3: 
 arg4:<WKFrameInfo: 0x120b7d940; webView = 0x10bab2a00; isMainFrame = YES; request = <NSMutableURLRequest: 0x280c6c870> { URL: file:///var/mobile/Containers/Data/Application/B75FC88D-09AE-47F9-A436-79BBD0C2504C/Documents/SwanCaches/swan-core/preset/3.15.3/slaves/slaves.html }> 
 arg5:(null)
 
2019-02-01 11:20:31.974673+0800 BaiduBoxApp[1364:197984] runJavaScriptTextInputPanelWithPrompt
 arg1:<SWANSlaveWebView: 0x10bab2a00; frame = (0 64; 375 603); opaque = NO; autoresize = W+H; layer = <CALayer: 0x280e178e0>> 
 arg2:{"func":"dispatch","args":["baiduboxapp://v19/utils/getSystemInfoSync?callback=__jsna_8"]} 
 arg3: 
 arg4:<WKFrameInfo: 0x10a61d750; webView = 0x10bab2a00; isMainFrame = YES; request = <NSMutableURLRequest: 0x280c6d810> { URL: file:///var/mobile/Containers/Data/Application/B75FC88D-09AE-47F9-A436-79BBD0C2504C/Documents/SwanCaches/swan-core/preset/3.15.3/slaves/slaves.html }> 
 arg5:(null)
 
  • 这个页面是我点击,一个含有原生播放器是我页面,且在播放中时的log,可以看出原生播放,定时触发一个播放时间更新的通知,渲染容器生成一个js格式的消息,逻辑容器接收js消息后,更新我们小程序层面代码的数值.请注意其中的vtype字段
 //============================================
 //原生函数
 - (void)dispatchToSlaveEventJS:(id)arg1 completionHandler:(CDUnknownBlockType)arg2;

//部分参数log
2019-02-01 14:19:11.214991+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:var event = new Event("message");event.message={"type":"abilityMessage","slaveId":"2","value":{"type":"video","params":{"id":"detailVideo","action":"timeupdate","e":{"wvID":"2","vtype":"timeupdate","data":"{\"videoId\":\"detailVideo\",\"duration\":206,\"currentTime\":0.13800000000000001}"}}}};document.dispatchEvent(event); 
 arg2:(null)
2019-02-01 14:19:12.212894+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:var event = new Event("message");event.message={"type":"abilityMessage","slaveId":"2","value":{"type":"video","params":{"id":"detailVideo","action":"timeupdate","e":{"wvID":"2","vtype":"timeupdate","data":"{\"videoId\":\"detailVideo\",\"duration\":206,\"currentTime\":1.133}"}}}};document.dispatchEvent(event); 
 arg2:(null)
2019-02-01 14:19:13.211944+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:var event = new Event("message");event.message={"type":"abilityMessage","slaveId":"2","value":{"type":"video","params":{"id":"detailVideo","action":"timeupdate","e":{"wvID":"2","vtype":"timeupdate","data":"{\"videoId\":\"detailVideo\",\"duration\":206,\"currentTime\":2.145}"}}}};document.dispatchEvent(event); 
 arg2:(null)
update_total_flow_bytes() called input bytes: 12669826,add after: 126698262019-02-01 14:19:14.212116+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:var event = new Event("message");event.message={"type":"abilityMessage","slaveId":"2","value":{"type":"video","params":{"id":"detailVideo","action":"timeupdate","e":{"wvID":"2","vtype":"timeupdate","data":"{\"videoId\":\"detailVideo\",\"duration\":206,\"currentTime\":3.1419999999999999}"}}}};document.dispatchEvent(event); 
 arg2:(null)
2019-02-01 14:19:15.186957+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:var event = new Event("message");event.message={"type":"abilityMessage","slaveId":"2","value":{"type":"video","params":{"id":"detailVideo","action":"pause","e":{"wvID":"2","vtype":"pause","data":"{\"videoId\":\"detailVideo\"}"}}}};document.dispatchEvent(event); 
 
2019-02-01 14:45:52.650876+0800 BaiduBoxApp[1757:256196] dispatchToSlaveEventJS 
 arg1:document.body.style.pointerEvents = 'auto' 
 arg2:(null)

hook (SWANApp) 方法继续分析

//- (void)dispatchToMasterLifeCycleName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;
//- (void)dispatchToMasterEventName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;
//- (void)dispatchToMasterEventJS:(id)arg1 completionHandler:(CDUnknownBlockType)arg2;
  • 疑似管理类,触发masterwebview原生方法执行js环境下的,生命周期函数
//原生函数
- (void)dispatchToMasterLifeCycleName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;

//log
 2019-02-01 16:58:57.998478+0800 BaiduBoxApp[384:20174] dispatchToMasterLifeCycleName 
 arg1:onShow 
 arg2:{
	"wvID" : "1"
} 
 arg3:(null)
 
2019-02-01 16:58:58.164016+0800 BaiduBoxApp[384:20174] dispatchToMasterLifeCycleName 
 arg1:onAppShow 
 arg2:{
	"appId" : "TbUaiGOSOjo23SOxWDA5UkjvIGNLOHwE",
	"extraData" : {
	},
	"mtjCuid" : "AE2408DA37BF7CD89FA09BE25712A00DCEFB4BF51OHCTLQKKLN",
	"cuid" : "AE2408DA37BF7CD89FA09BE25712A00DCEFB4BF51OHCTLQKKLN",
	"clkid" : "",
	"scene" : "1201004410071000"
} 
  • 疑似管理类,触发masterwebview原生方法,把整个_devtool注入js
//原生函数
- (void)dispatchToMasterEventJS:(id)arg1 completionHandler:(CDUnknownBlockType)arg2;

//log
 2019-02-01 16:58:58.147050+0800 BaiduBoxApp[384:20174] dispatchToMasterEventJS 
 arg1:window.__san_devtool__.data 
 arg2:(null)
  • 暂时未猜到
//原生函数
- (void)dispatchToMasterEventName:(id)arg1 eventParam:(id)arg2 completionHandler:(CDUnknownBlockType)arg3;

//部分参数log
2019-02-01 16:58:57.550525+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:AppReady 
 arg2:{
	"root" : "",
	"extraData" : {
	},
	"wvID" : 1,
	"devhook" : "false",
	"pageUrl" : "pages/index/index",
	"appPath" : "/var/mobile/Containers/Data/Application/40889C2C-FBCC-414A-AE1E-D658E60ABD8A/Documents/SwanCaches/0f745bd975d2735b397186bf0f2e17f0/86",
	"appConfig" : "{"pages":["pages\/index\/index","pages\/programActivity\/index","pages\/activity\/index","pages\/lottery\/index","pages\/program\/index","pages\/detail\/index","pages\/vote\/index","pages\/comment\/index","pages\/map\/index"],"networkTimeout":{"request":10000,"downloadFile":10000},"debug":true,"splitAppJs":true,"window":{"navigationBarTitleText":"触电短视频","backgroundTextStyle":"light","navigationBarTextStyle":"white","navigationBarBackgroundColor":"#ff6666"}}"
} 
 arg3:(null)
2019-02-01 16:58:57.795054+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"slaveLoaded","value":{"status":"loaded"},"slaveId":"1"}"
} 
 arg3:(null)
2019-02-01 16:58:57.998208+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:route 
 arg2:{
	"toTabIndex" : -1,
	"routeType" : "init",
	"toPage" : "pages/index/index",
	"toId" : "1",
	"fromId" : "-1"
} 
 arg3:(null)
2019-02-01 16:58:58.144832+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"setMasterRainMonitorEndtime","value":{}}"
} 
 arg3:(null)
2019-02-01 16:58:58.145077+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"sendLogMessage","value":{"perfLogList":[{"actionId":"fe_slave_dispatch_start","timestamp":1549011537624},{"actionId":"fe_start_load_css","timestamp":1549011537629},{"actionId":"fe_start_load_app_css","timestamp":1549011537629},{"actionId":"fe_start_load_page_css","timestamp":1549011537629},{"actionId":"fe_end_load_app_css","timestamp":1549011537683},{"actionId":"fe_end_load_page_css","timestamp":1549011537683},{"actionId":"fe_end_load_css","timestamp":1549011537683},{"actionId":"fe_start_load_swan_js","timestamp":1549011537683},{"actionId":"slave_first_loaded","timestamp":1549011537793},{"actionId":"slave_js_parsed","timestamp":1549011537793},{"actionId":"fe_end_load_swan_js","timestamp":1549011537793},{"actionId":"fe_end_init_swan_js","timestamp":1549011537793},{"actionId":"fe_end_parse_swan_js","timestamp":1549011537728},{"actionId":"fe_slave_real_get_data","timestamp":1549011538079},{"actionId":"slave_first_recieve_data","timestamp":1549011538079},{"actionId":"fe_first_render_start","timestamp":1549011538082},{"actionId":"slave_first_rendered","timestamp":1549011538141},{"actionId":"fe_slave_preload_start","timestamp":1549010848871},{"actionId":"fe_slave_preload_end","timestamp":1549010850982}]}}"
} 
 arg3:(null)
2019-02-01 16:58:58.147983+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"abilityMessage","value":{"type":"rendered","params":[]},"slaveId":"1"}"
} 
 arg3:(null)
2019-02-01 16:58:58.148324+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"abilityMessage","value":{"type":"nextTickReach","params":{}},"slaveId":"1"}"
} 
 arg3:(null)
2019-02-01 16:58:58.149230+0800 BaiduBoxApp[384:20174] dispatchToMasterEventName 
 arg1:message 
 arg2:{
	"message" : "{"type":"slaveAttached","slaveId":"1"}"
} 
 arg3:(null)