评论

从源码角度理解 wx.reLaunch 执行过程

从源码角度梳理 wx.reLaunch 的执行过程,并总结在开发过程中需要注意的一些坑点。

正文稍显啰嗦,可直接拖到文末看总结的执行流程图以及在开发过程中需要注意的一些坑点。

相信老司机们看到这个标题就笑了,wx.reLaunch有啥好解释的,顾名思义就是:「重新启动小程序」。重启这个词,对于工程师最熟悉不过了,很多问题一出现,往往第一反应就是“重启”一下就能解决掉,能重启解决的问题,那都不是“大问题”。

wx.reLaunch === 重启?

那这样来理解wx.reLaunch的话,那就应该是“微信会 kill 掉当前这个小程序进程,然后像第一次加载小程序那样,重新打开小程序”。

如果是这样的话,那bug应该离你就不远了。事实并非如此。

微信文档怎么说

进一步,那不是这样的话,那我们查查微信文档怎么说的?wx.reLaunch(Object object)

关闭所有页面,打开到应用内的某个页面

看到这里,老司机们都拍了拍大腿,原来是这样:清空所有路由,并打开新的页面。我知道了,也很简单嘛。ok,理解了的话,那我再追问几个问题吧:

  1. 关闭所有页面会执行什么操作?
  2. 打开新页面的过程中,除了新页面 Page 的各生命周期外,全局 js 会重新执行吗(加载时即执行)?
  3. 如果 reLaunch 的目标页是分包内的页面呢?执行过程又是怎样的

写段代码测试下

事实胜于雄辩,依赖文档来编程是合理的,但是如果我们要继续深入来探讨代码的执行,还是远远不够的。

我们先来写段代码片段测试一下,链接:https://developers.weixin.qq.com/s/tis8tVmX7ShS(需要真机调试需要填写 appId)。

有 p0 - p4 共 5 个页面,我们的测试路径是这样的,打开小程序-p0-p1-p2,然后 reLaunch 到 p3,看控制台输出:

这里我们能看到,“关闭所有页面”,对应会依次会把当前页面栈的页面出栈,并触发相应的 unUnload回调。

就不耽误大家的时间,经过反复测试,最终得出在 wx.reLaunch的执行过程如下:

追溯源码

其实到了这一步,相信大家对 wx.reLaunch有了进一步的认识,而不是像开始那样觉得就是单纯的重启了。但是到这里就结束了吗?如果说有一样东西能够 100% 让人信服,单纯测试是不够的,总可能会有遗漏的测试用例,无法保证结论的正确性。so…

Talk is cheap, show me your code.

我们下一步打算从源码层去看一看 wx.reLaunch到底做了些什么。

什么?你不知道源码怎么看… (嗯,网上其实有文章,介绍怎么去找到小程序框架层的源码)

我的方法是这样的:在控制台输入wx.reLaunch()并执行,我们看到会抛出一个错误,我们点开错误堆栈:

WAService.js 就是小程序框架的源码了,cmd+f,点进去输入关键词reLaunch搜索,我们会找到关键代码,也就是搜索结果的 9/40 和 40/40,代码如下:

9/40

b = function(e) {
    var t;
    "active" === a.default.runningStatus || "ios" !== c.PLATFORM ? Object(u.beforeInvoke)("reLaunch", e, {
        url: ""
    }) && (!v("reLaunch", e) || (t = g("reLaunch", e, !1)) && (e.url = t,
    e.url = Object(i.encodeUrlQuery)(e.url),
    h("reLaunch", e.url, e) && (a.default.navigatorLock = !0,
    Object(u.invokeMethod)("reLaunch", e, {
        afterFail: function() {
            a.default.navigatorLock = !1
        }
    }),
    p.emit({
        type: "reLaunch",
        start: Date.now()
    })))) : Object(u.beforeInvokeFail)("reLaunch", e, "can not invoke reLaunch in background")
}

40/40

"reLaunch" === r || "autoReLaunch" === r ?
function(t, e, n, r, o, i) {
    __appServiceSDK__.traceBeginEvent("Framework", "onReLaunch");
    var a = !1;
    for ("reLaunch" === o && (a = !0); 0 < lt.length;) {
        var s = !0,
        a = a && (s = !1);
        Pt(lt[lt.length - 1], t, !1, s)
    }
    Object.keys(st).forEach(function(e) {
        Pt(st[e], t, !1, !0)
    }),
    le(t),
    jt(t, e, n, r, {
        isMainTabBarPage: xt({
            route: t
        }),
        initialRenderingCacheData: i
    }),
    __appServiceSDK__.traceEndEvent()
} (e, C, t, n, r, i)

然后,我们开启漫长的 debug 的过程,因为我们对其内部实现并不熟悉,所以最开始我们找到入口,然后逐步调试。

我们第一步其实就有发现:

"active" === a.default.runningStatus || "ios" !== c.PLATFORM
  ? ...
  : Object(u.beforeInvokeFail)("reLaunch", e, "can not invoke reLaunch in background")

这段代码其实一眼看上去就能猜测到,wx.reLaunch在非 iOS 设备下,如果小程序不在前台,那执行会报错。我们看社区里反馈了很多类似的问题,最常见的一个就是「在微信支付后为什么页面没有跳转」,其实都可以从这行代码找到问题的原因(https://developers.weixin.qq.com/community/develop/doc/000caaf5e9cf486e710aaf18751800)。

这里其实我们会想为什么微信会有这个限制,我猜测可能和 wx.reLaunch执行过程中,有一些操作在非 iOS 设备下是无法完成的。(在使用小程序过程中,我们会发现安卓下的小程序和微信进程是相互独立的,但是在 iOS 下,小程序进程和微信进程是同一个。参见小程序运行环境

我们继续往下,又发现一个关键信息:

这就验证了如果 reLaunch 目标页是分包代码代码时, 会先加载分包代码的结论了。而且事实上继续往下单步执行,在控制台也会先执行 p4/index.js 中 Page 外面写的 console 语句。

接下来,我们先把我们之前发现的第 2 段关键代码打上断点,因为发现如果不断的话会直接执行掉,这里应该是被另外一层函数封装了,没进到里面的代码。我们接下来分析wx.reLaunch定义的核心代码会执行什么逻辑:

代码结构是非常清晰的,是一个 IIFE,函数主体被包裹在了 begin 和 end 中,赞一个。

"reLaunch" === r || "autoReLaunch" === r ?
function(t, e, n, r, o, i) {
    __appServiceSDK__.traceBeginEvent("Framework", "onReLaunch");
    // ...
    __appServiceSDK__.traceEndEvent()
} (e, C, t, n, r, i)

入参的 6 个变量值分别是:

我们分为 5 句代码:

第一句:

var a = !1;

这里定义了一个局部变量 a,值为 false

第二句:

for ("reLaunch" === o && (a = !0); 0 < lt.length;) {
  var s = !0,
      a = a && (s = !1);
  Pt(lt[lt.length - 1], t, !1, s)
}

看到这里,我们会看到有 3 个变量,我们没办法直接看出分别代表什么:

  • s,局部变量,初始值为 true,如果 a 为 true 的话,s 会变成 false。s 作为第 4 个变量传入 Pt 函数中

  • lt,通过 watch,猜测 lt 即为页面栈

  • Pt,直接通过 watch,没办法看出来这个函数是什么,不过我们可以点击下面的 [[FunctionLocation]] 查看函数定义的位置。

function Pt(e, t, n, r) {
  __appServiceSDK__.traceBeginEvent("Framework", "unloadPage"),
    Ze(e.webviewId),
    e.page.__toRoute__ = t,
    e.page.__isBack__ = n,
    e.page.__notReportHide__ = r,
    e.page.__callPageLifeTime__("onUnload"),
    e.node && Y.destroy(e.page),
    Object(L.isDevTools)() && (delete __wxAppData[e.route],
                               __appServiceSDK__.publishUpdateAppData()),
    delete st[e.webviewId],
    (lt = lt.slice(0, lt.length - 1)).length ? gt(lt[lt.length - 1].route) : gt(""),
    ze("pageUnload", e.page),
    ze("leavePage", e.page),
    __appServiceSDK__.traceEndEvent(),
    __appServiceSDK__.uploadUserLogOnHide(e.route)
}

这里,我们就能知道了,Pt 函数做的事情就是对传入的页面执行其 onUnload生命周期函数。

这段代码是**对页面栈进行遍历,依次触发页面的onUnload**回调。

第三句:

Object.keys(st).forEach(function(e) {
  Pt(st[e], t, !1, !0)
}),

我们先看看 st 是什么?

发现 st 是以 webviewId 为 key,以 page 对象及一些其他带有 page 特性的标识字段组合 为 value 的结构。

所以这段代码仍然是遍历页面(这里我理解是无序的,因为 Object.keys 首先就不能保证有序,参考Object.keys(…)对象属性的顺序?),依次触发其 onUnload回调。

这里先抛出两个问题:

  1. 第 2 句代码执行后,这段代码还有什么作用?
  2. 重复触发 onUnload,不出意外,框架内部会根据 webviewId 来判断能否执行。即 __callPageLifeTime__函数内部逻辑。

暂时先跳过,回到主流程。

第四句:

le(t)

同样的方式,我们找到 le 函数的源码:

// line 94280
var ue = !1;
function le(e) {
  var t = __appServiceSDK__.isIsolatedSubpackage(e);	// 是否独立分包
  !ue && t ? ge() : ue && !t && he(),
  ue = t
}

function he(t) {
	t = t || pe,
	__appServiceSDK__.emitIsloatedAppShow(t),
	de.forEach(function(e) {
		e.preventOnShow || e.app.onShow(t),
		e.preventOnShow = !1
	}),
	pe = t
}
function ge(t) {
	__appServiceSDK__.emitIsloatedAppHide(),
	de.forEach(function(e) {
		7 === __wxConfig.appType ? e.app.onHide(t) : e.app.onHide()
	})
}

我们找到微信文档对于独立分包的定义:独立分包。从独立分包中页面进入小程序时,不需要下载主包,可以很大程度上提升分包页面的启动速度。

配置方式是在分包配置里加上independent字段。因为我们这里没考虑到这种情况,感觉这里不会影响主流程,先跳过。

第五句:

jt(t, e, n, r, {
  isMainTabBarPage: xt({
    route: t
  }),
  initialRenderingCacheData: i
})

通过前面,我们不难推测出,这段代码应该是“打开目标页面”。通过参数发现,目标页面对于 tab 页和非 tab 页有区别对待,继续找到 jt 函数:

function jt(e, t, n, r, o) {
    var i = 4 < arguments.length && void 0 !== o ? o: {};
    __appServiceSDK__.traceBeginEvent("Framework", "openNewPage"),
    se(te);
    var a = ot;
    ot = void 0;
    var s = null;
    Ye.call(ct, e) ? s = ct[e] : console.info('Page "' + e + '" has not been registered yet.');	// 检查是否注册
    var c = bt(e);
    wt.newPageTime = Date.now(),
    gt(e);
    var u = c ? Y.create(n, e) : q.create(n, e, s || {}),	// 初始化 page
    l = u.page,
    d = b(r); (nt = {	// nt,新页面对象
        page: l,
        webviewId: n,
        route: e,
        rawPath: t,
        lastRoute: nt ? nt.route: "",
        lastQuery: nt ? nt.page.options: {},
        node: u.node
    }).isTabBarPage = xt(nt),	// 是否 tabBar
    nt.isMainTabBarPage = i.isMainTabBarPage || !1,	// 是否主 tabBar
    lt.push(nt),	// 页面栈
    l.__exitState__ = a;
    var f, p, h = u.node,
    g = {},
    v = !1;
    Object.keys(d).forEach(function(e) {
        exparser.Component.hasProperty(h, e) && (g[e] = decodeURIComponent(d[e]), v = !0)
    }),
    v && h.setData(g),
    __virtualDOM__.attachView(n),	// attachView
    /^__wx__\//.test(e) && (f = {},
    /^__wx__\/open-api-redirecting-page/.test(e) ? f = me || {}: !/^__wx__\/functional-page/.test(e) || (p = _e()) && p.functionalPage && (f = Object.assign({},
    p.functionalPage, {
        accountInfo: __wxConfig.accountInfo
    })), l.setData(f)),
    l.options = d,
    Lt(nt, n, wt.newPageTime, void 0, !1),	// Lt 函数
    Object(L.isDevTools)() && (__wxAppData[e] = l.data, __wxAppData[e].__webviewId__ = n, __appServiceSDK__.publishUpdateAppData()),
    i.initialRenderingCacheData && l.setData(i.initialRenderingCacheData);
    var _ = __appServiceSDK__._getOpenerEventChannel();
    _ && (nt.eventChannel = _),
    l.__callPageLifeTime__("onLoad", r),	// 1.触发 onLoad
    l.__callPageLifeTime__("onShow"),			// 2.触发 onShow
    st[n] = {
        page: l,
        route: e,
        rawPath: t,
        webviewId: n,
        statesData: null,
        node: c ? u.node: void 0
    },	// webviewId 为 key 的特殊结构
    ze("pageLoad", l),
    ze("enterPage", l),
    vt("appRoute2newPage", wt.appRouteTime, wt.newPageTime),
    __appServiceSDK__.traceEndEvent()
}

我们在里面发现了我们熟悉的 lt、st 变量,并且在函数内被初始化。其中有一个 Lt 函数,我们找到其函数定义。

var Lt = _(function(e, t, n, r, o) {
    __appServiceSDK__.traceBeginEvent("Framework", "publishInitData"),
    j("Update view with init data");
    var i = e.page,
    a = {};
    a.wechatLibVersion = ("undefined" != typeof __libVersionInfo__ ? __libVersionInfo__.version: "") || "",
    a.webviewId = t,
    a.enablePullUpRefresh = _t(i, "onReachBottom"),
    a.enablePageScroll = _t(i, "onPageScroll"),
    a.onReachBottomDistance = function(e) {
        try {
            if ("number" == typeof __wxConfig.page[e + ".html"].window.onReachBottomDistance) return __wxConfig.page[e + ".html"].window.onReachBottomDistance
        } catch(e) {
            return $.DEFAULT_ON_REACH_BOTTOM_DISTANCE
        }
        return $.DEFAULT_ON_REACH_BOTTOM_DISTANCE
    } (i.__route__),
    a.statesData = r,
    a.scene = rt,
    a.route = i.__route__,
    a.query = i.options,
    a.lastRoute = e.lastRoute,
    a.lastQuery = e.lastQuery,
    a.wxConfig = {
        accountInfo: __wxConfig && __wxConfig.accountInfo || {},
        appContactInfo: __wxConfig && __wxConfig.appContactInfo || {},
        appLaunchInfo: __wxConfig && __wxConfig.appLaunchInfo || {},
        plugins: __wxConfig && __wxConfig.plugins || {}
    },
    a.windowConfig = __wxConfig && __wxConfig.page && __wxConfig.page[i.__route__ + ".html"] && __wxConfig.page[i.__route__ + ".html"].window || {},
    a.debug = __wxConfig && __wxConfig.debug,
    a.appId = __wxConfig && __wxConfig.accountInfo && __wxConfig.accountInfo.appId,
    a.appLaunchTime = wt.appLaunchTime,
    a.appFgTime = wt.appFgTime,
    a.isTabBarPage = e.isTabBarPage,
    a.isMainTabBarPage = e.isMainTabBarPage,
    a.navigationStyle = __wxConfig && __wxConfig.global && __wxConfig.global.window && __wxConfig.global.window.navigationStyle,
    a.packageType = function(e) {
        if (__wxConfig && __wxConfig.subPackages && __wxConfig.subPackages.length) {
            for (var t = 0; t < __wxConfig.subPackages.length; t++) {
                var n = __wxConfig.subPackages[t];
                if (0 === e.indexOf(n.root)) return n.independent ? "independent": "normal"
            }
            return "main"
        }
        return "none"
    } (i.__route__),
    a.needGetSubjectInfo = !At.isInit,
    a.subPackages = __wxConfig.subPackages,
    a.perfData = (ie[ne] = Date.now(), ie[X] = ae ? 1 : 0, ie[oe] = __wxConfig.isSubContext ? 1 : 0, ie[Q] = __wxConfig.onReadyStart, ie[re] = __wxConfig.onReadyEnd || 0, ae = !1, ie),
    a.isReload = o,
    a.adInfo = {
        preloadVideoAdUnitIds: __appServiceSDK__.getPreloadVideoAdUnitIds()
    },
    a.permissionBytes = __appServiceSDK__.getPermissionBytes(),
    a.fontFaceRecords = __appServiceSDK__.fontFaceRecords,
    a.fontSizeSetting = Object(L.getCachedSystemInfo)().fontSizeSetting;
    var s = {
        ext: a,
        options: {
            firstRender: !0,
            timestamp: n,
            path: i.__route__
        }
    };
    if (r) {
        var c = JSON.stringify(s),
        u = c.length;
        if (262144 < u) {
            for (var l = [], d = 0; d < u;) l.push(c.substr(d, 262144)),
            d += 262144;
            for (var f = ++$e,
            p = 0,
            h = l.length; p < h; p++) qe.emit({
                isSplitData: !0,
                splitInfo: {
                    id: f,
                    index: p + 1,
                    total: h,
                    data: l[p]
                }
            },
            t);
            return ze("pageReady", i),
            void __appServiceSDK__.traceEndEvent()
        }
    }
    qe.emit(s, t),	// qe 函数,s 是组装 page 信息的对象
    ze("pageReady", i),
    __appServiceSDK__.traceEndEvent()
})

qe 函数:

var qe = function() {
    function e() {
        Object(g.
    default)(this, e)
    }
    return Object(r.
default)(e, null, [{
        key: "emit",
        value: function(e, t, n) {
            __appServiceSDK__.invokeWebviewMethod("appDataChange", e, [t], n)
        }
    }]),
    e
} ();

搜索 appDataChange,找不到其他地方定义。我们找到 __appServiceSDK__.invokeWebviewMethod 的定义:

function(e, t, n) {
    n.r(t),
    n.d(t, "invokeWebviewMethod",
    function() {
        return r
    });
    var c = n(0),
    u = n(3),
    l = 0,
    d = [],
    r = function(e, t, n, r) {
        var o = 1 < arguments.length && void 0 !== t ? t: {},
        i = 2 < arguments.length ? n: void 0,
        a = 3 < arguments.length ? r: void 0,
        s = l++;
        d[s] = a,
        Object(c.publish)("invokeWebviewMethod", {	// 发布
            name: e,
            args: o,
            callbackId: s
        },
        void 0 === i ? [u.
    default.currentWebviewId]:
        i)
    };
    Object(c.subscribe)("callbackWebviewMethod",	// 订阅
    function(e) {
        var t = e.res,
        n = e.callbackId,
        r = d[n];
        delete d[n],
        r && r(t)	// 执行
    })
}

最终发现以下代码会被执行:

__appServiceSDK__.onWebviewEvent(_(function(e) {
    __appServiceSDK__.traceBeginEvent("Framework", "onWebviewEvent");
    var t = e.webviewId,
    n = e.eventName,
    r = e.data,
    o = function(e, t, n, r) {
        if (Ye.call(st, e)) {
            var o = st[e],
            i = o.page;
            if (n === $.DOM_READY_EVENT) return wt.pageReadyTime = Date.now(),
            j("Invoke event onReady in page: " + o.route),
            i.__callPageLifeTime__("onReady"),	// 3.触发 onReady
            void vt("newPage2pageReady", wt.newPageTime, wt.pageReadyTime);
            if (r._requireActive) {
                var a = lt[lt.length - 1];
                if (!a || a.webviewId !== e) return
            }
            if (r._relatedInfo && F.DisplayReporter.setEventRelatedInfo(r._relatedInfo), t) {
                var s = __virtualDOM__.getNodeById(t, e);
                if (!s) return;
                var c = exparser.Element.getMethodCaller(s);
                return j("Invoke event " + n + " in component: " + s.is),
                _t(c, n) ? tt(c, n, r) : void x("事件警告", "Do not have " + n + " handler in component: " + s.is + ". Please make sure that " + n + " handler has been defined in " + s.is + ".")
            }
            if (j("Invoke event " + n + " in page: " + o.route), _t(i, n)) return tt(i, n, r);
            x("事件警告", "Do not have " + n + " handler in current page: " + o.route + ". Please make sure that " + n + " handler has been defined in " + o.route + ", or " + o.route + " has been added into app.json")
        }
    } (t, e.nodeId, n, r);
    return __appServiceSDK__.traceEndEvent(),
    o
},
"onWebviewEvent"))

说实话,这段代码没太看懂,对着 Page 的 生命周期 一起看理解起来会更清晰点,这里还是回到最开始,看传入的 isMainTabBarPage字段做了什么操作。

我们回到 jt 函数,发现关键语句:

var u = c ? Y.create(n, e) : q.create(n, e, s || {})	// 初始化 page

Y:

K = ["onLoad", "onReady", "onShow", "onRouteEnd", "onHide", "onUnload", "onResize"],
J = __appServiceSDK__.getLogManager(),
Y = function() {
    function e() {
        Object(g.
    default)(this, e)
    }
    return Object(r.
default)(e, null, [{
        key: "create",
        value: function(d, e) {
            var f = __virtualDOM__.addView(d, e),
            p = exparser.Element.getMethodCaller(f),
            u = __virtualDOM__.getOwnerPluginAppId(p);
            if (p.__wxWebviewId__ = d, p.__route__ = e, p.route = e, p.__displayReporter = new F.DisplayReporter(e, 2), f.__customConstructor__ === __virtualDOM__.Page) {
                var t = f.getRootBehavior().methods,
                n = p.__freeData__;
                for (var r in t) p[r] = t[r].bind(p);
                for (var o in n) p[o] = b(n[o])
            }
            var h = __appServiceSDK__.getSystemInfoSync().deviceOrientation;
            p.__callPageLifeTime__ = function(e) {
                var t = this[e] || I;
                Reporter.__route__ = this.__route__,
                Reporter.__method__ = e;
                for (var n, r, o, i, a, s = arguments.length,
                c = new Array(1 < s ? s - 1 : 0), u = 1; u < s; u++) c[u - 1] = arguments[u];
                "onLoad" === e && (n = p.__displayReporter).setQuery.apply(n, c),
                "onShow" === e ? (p.__displayReporter.reportShowPage(), Object(F.checkWebviewAlive)(d)) : "onReady" === e ? p.__displayReporter.setReadyTime(Date.now()) : "onHide" === e || "onUnload" === e ? (r = this.__toRoute__, o = this.__isBack__, i = this.__notReportHide__, delete this.__toRoute__, delete this.__isBack__, delete this.__notReportHide__, i || p.__displayReporter.reportHidePage(r, o), Object(F.stopCheckWebviewAlive)(d)) : "onResize" === e && (a = c[0] || {},
                h !== a.deviceOrientation && (h = a.deviceOrientation, p.__displayReporter.addOrientationChangeCount())),
                "onShow" === e ? f.triggerPageLifeTime("show", c) : "onHide" === e ? f.triggerPageLifeTime("hide", c) : "onResize" === e && f.triggerPageLifeTime("resize", c),
                j(this.__route__ + ": " + e + " have been invoked"),
                __appServiceSDK__.traceBeginEvent("LifeCycle", "Page." + e);
                var l = t.apply(this, c);
                return __appServiceSDK__.traceEndEvent(),
                Reporter.__route__ = Reporter.__method__ = "",
                l
            },
            K.forEach(function(s) {
                var c = p[s];
                p[s] = function() {
                    var e, t = c || I;
                    try {
                        for (var n = Date.now(), r = arguments.length, o = new Array(r), i = 0; i < r; i++) o[i] = arguments[i];
                        e = t.apply(this, o);
                        var a = Date.now() - n;
                        1e3 < a && Reporter.slowReport({
                            key: "pageInvoke",
                            cost: a,
                            extend: 'at "' + this.__route__ + '" page lifeCycleMethod ' + s + " function"
                        }),
                        J && J.logApiInvoke && J.log("page " + this.__route__ + " " + s + " have been invoked"),
                        __appServiceSDK__.nativeConsole.info("component page " + this.__route__ + " " + s + " have been invoked")
                    } catch(e) {
                        Reporter.thirdErrorReport({
                            source: u,
                            error: e,
                            extend: 'at "' + this.__route__ + '" page lifeCycleMethod ' + s + " function"
                        })
                    }
                    return e
                }.bind(p)
            });
            var i = "function" == typeof p.onShareAppMessage;
            i && __appServiceSDK__.showShareMenu();
            var a = "function" == typeof p.onShareTimeline;
            return i && a && __appServiceSDK__.showShareTimelineMenu(),
            {
                page: p,
                node: f
            }
        }
    },
    {
        key: "destroy",
        value: function(e) {
            __virtualDOM__.removeView(e.__wxWebviewId__)
        }
    }]),
    e
} ()

q:

var B = Object.assign,
L = n(4),
F = n(5),
W = ["onLoad", "onReady", "onShow", "onRouteEnd", "onHide", "onUnload", "onResize"],
U = function(e) {
    for (var t = 0; t < W.length; ++t) if (W[t] === e) return ! 0;
    return "data" === e
},
V = ["__wxWebviewId__", "__route__"],
G = ["route"],
z = function(e) {
    return - 1 !== V.indexOf(e)
},
H = __appServiceSDK__.getLogManager(),
q = function() {
    function h() {
        var t = this,
        c = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {},
        d = 1 < arguments.length ? arguments[1] : void 0,
        e = 2 < arguments.length ? arguments[2] : void 0;
        Object(g.
    default)(this, h);
        var n = {
            __wxWebviewId__: d,
            __route__: e
        };
        V.forEach(function(e) {
            Object.defineProperty(t, e, {
                set: function() {
                    x("关键字保护", "should not change the protected attribute " + e)
                },
                get: function() {
                    return n[e]
                }
            })
        });
        var r = __virtualDOM__.addView(d, e),
        o = exparser.Element.getMethodCaller(r),
        u = __virtualDOM__.getOwnerPluginAppId(o);
        this.__wxExparserNode__ = r,
        this.__wxComponentInst__ = o,
        exparser.Element.setMethodCaller(r, this),
        c.data = c.data || {},
        T(c.data) || A("Page data error", "Page.data must be an object");
        var i = JSON.stringify(c.data);
        this.data = JSON.parse(i),
        this.__viewData__ = JSON.parse(i),
        this.__displayReporter = new F.DisplayReporter(e, 1);
        var f = __appServiceSDK__.getSystemInfoSync().deviceOrientation;
        this.__callPageLifeTime__ = function(e) {
            var t = (this[e] || I).bind(this);
            Reporter.__route__ = this.__route__,
            Reporter.__method__ = e;
            for (var n, r, o, i, a, s = arguments.length,
            c = new Array(1 < s ? s - 1 : 0), u = 1; u < s; u++) c[u - 1] = arguments[u];
            "onLoad" === e && (n = this.__displayReporter).setQuery.apply(n, c),
            "onShow" === e ? (this.__displayReporter.reportShowPage(), Object(F.checkWebviewAlive)(d)) : "onReady" === e ? this.__displayReporter.setReadyTime(Date.now()) : "onHide" === e || "onUnload" === e ? (r = this.__toRoute__, o = this.__isBack__, i = this.__notReportHide__, delete this.__toRoute__, delete this.__isBack__, delete this.__notReportHide__, i || this.__displayReporter.reportHidePage(r, o), Object(F.stopCheckWebviewAlive)(d)) : "onResize" === e && (a = c[0] || {},
            f !== a.deviceOrientation && (f = a.deviceOrientation, this.__displayReporter.addOrientationChangeCount())),
            j(this.__route__ + ": " + e + " have been invoked"),
            __appServiceSDK__.traceBeginEvent("LifeCycle", "Page." + e);
            var l = t.apply(this, c);
            return __appServiceSDK__.traceEndEvent(),
            Reporter.__route__ = Reporter.__method__ = "",
            l
        },
        W.forEach(function(s) {
            t[s] = function() {
                var e, t = (c[s] || I).bind(this);
                try {
                    for (var n = Date.now(), r = arguments.length, o = new Array(r), i = 0; i < r; i++) o[i] = arguments[i];
                    e = t.apply(this, o);
                    var a = Date.now() - n;
                    1e3 < a && Reporter.slowReport({
                        key: "pageInvoke",
                        cost: a,
                        extend: "at " + this.__route__ + " page lifeCycleMethod " + s + " function"
                    }),
                    H && H.logApiInvoke && H.log("page " + this.__route__ + " " + s + " have been invoked"),
                    __appServiceSDK__.nativeConsole.info("page " + this.__route__ + " " + s + " have been invoked")
                } catch(e) {
                    Reporter.thirdErrorReport({
                        source: u,
                        error: e,
                        extend: "at " + this.__route__ + " page lifeCycleMethod " + s + " function"
                    })
                }
                return e
            }.bind(t)
        });
        for (var a in c) !
        function(a) {
            z(a) ? x("关键字保护", "Page's " + a + " is write-protected") : U(a) || ("Function" === k(c[a]) ? t[a] = function() {
                var e;
                Reporter.__route__ = this.__route__,
                Reporter.__method__ = a,
                __appServiceSDK__.traceBeginEvent("User Script", "Page." + a);
                try {
                    for (var t = Date.now(), n = arguments.length, r = new Array(n), o = 0; o < n; o++) r[o] = arguments[o];
                    e = c[a].apply(this, r);
                    var i = Date.now() - t;
                    1e3 < i && Reporter.slowReport({
                        key: "pageInvoke",
                        cost: i,
                        extend: "at " + this.__route__ + " page " + a + " function"
                    })
                } catch(e) {
                    Reporter.thirdErrorReport({
                        source: u,
                        error: e,
                        extend: "at " + this.__route__ + " page " + a + " function"
                    })
                }
                return __appServiceSDK__.traceEndEvent(),
                Reporter.__route__ = Reporter.__method__ = "",
                e
            }.bind(t) : t[a] = b(c[a]))
        } (a);
        var s = {
            route: e
        };
        G.forEach(function(e) {
            Object.prototype.hasOwnProperty.call(t, e) || (t[e] = s[e])
        });
        var l = "function" == typeof c.onShareAppMessage;
        l && __appServiceSDK__.showShareMenu();
        var p = "function" == typeof c.onShareTimeline;
        l && p && __appServiceSDK__.showShareTimelineMenu()
    }
    return Object(r.
default)(h, null, [{
        key: "create",
        value: function(e, t, n) {
            var r = new h(n, e, t),
            o = r.__wxExparserNode__;
            return delete r.__wxExparserNode__,
            {
                page: r,
                node: o
            }
        }
    },
    {
        key: "destroy",
        value: function(e) {
            __virtualDOM__.removeView(e.__wxWebviewId__)
        }
    }]),
    Object(r.
default)(h, [{
        key: "setData",
        value: function(c, e) {
            var u = this;
            try {
                var t = k(c);
                if ("Object" !== t) return void A("类型错误", "setData accepts an Object rather than some " + t);
                Object.keys(c).forEach(function(e) {
                    void 0 === c[e] && A("Page setData warning", 'Setting data field "' + e + '" to undefined is invalid.');
                    var t, n, r, o = M(e),
                    i = R(u.data, o),
                    a = i.obj,
                    s = i.key;
                    a && (a[s] = b(c[e])),
                    void 0 !== c[e] && (n = (t = R(u.__viewData__, o)).obj, r = t.key, n && (n[r] = b(c[e])))
                }),
                __appServiceSDK__.traceBeginEvent("Framework", "DataEmitter::emit"),
                this.__wxComponentInst__.setData(JSON.parse(JSON.stringify(c)), e),
                __appServiceSDK__.traceEndEvent()
            } catch(e) {
                v(e)
            }
        }
    },
    {
        key: "pageScrollTo",
        value: function(e) {
            __appServiceSDK__.publishPageScrollTo(e, [this.__wxWebviewId__])
        }
    }]),
    h
} ()

仍然没有发现 isMainTabBarPage关键字。

以上,第五步虽然没找到我们想找到的逻辑,但是发现其实 jt 函数其实和其他路由函数执行的是同一段逻辑,不影响我们对 wx.reLaunch执行过程的分析。

结论

最终,结合源码,我们得出wx.reLaunch的最终执行过程如下图所示:

关于 wx.reLaunch的执行过程,额外提出几个点需要注意:

  1. wx.reLaunch真正的逻辑是清空路由,再打开新页面,并不是传统意义上的“重启”;
  2. wx.reLaunch只会影响小程序各生命周期(回调)的执行,全局 js 代码在小程序加载时执行,分包中的全局 js 代码在分包加载时执行;
  3. wx.reLaunch的目标页在分包内,且分包未加载过时,会先加载分包代码,再执行后续逻辑(unUnload + openNewPage);
  4. wx.reLaunch在非 iOS 设备中,如果小程序不在前台时,执行会报错,导致无法跳转。

除此之外,再额外说几个在 debug 过程中的总结的小 tips:

1、如果在代码里不太好看一些变量或表达式的值,可以复制下来贴在 watch 里。

2、发现一些 function 执行了,但是不知道函数定义的位置位置,可以先将其放进 watch 里,等其有值的时候点开会有一个路径点击开就到了函数定义的位置了。

3、不要无脑 debug,带着疑问,先在脑子里假设出你推测或者认为的一些结论,用 debug 去验证,否则中间很难发现关键信息。其实本文的成因并非是我真的想去深入了解下 wx.reLaunch的执行过程,而是因为对他的理解有偏差,在排查线上 bug 时产生了一些自己无法理解的现象。

最后一次编辑于  2020-06-12  
点赞 12
收藏
评论

2 个评论

  • lucky 🔆
    lucky 🔆
    2020-06-12

    嗯,很棒

    2020-06-12
    赞同 1
    回复
  • 僭越
    僭越
    2020-06-12

    好细致。


    2020-06-12
    赞同
    回复
登录 后发表内容