HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【报Bug】uni.getSystemInfoSync().safeAreaInsets.bottom为 -48 的错误

bug反馈

【报Bug】uni.getSystemInfoSync().safeAreaInsets.bottom为 -48 的错误 Hbuilder v3.3.11

【报Bug】uni.getSystemInfoSync().safeAreaInsets.bottom为 -48 的错误 Hbuilder v3.3.11

android端实体返回键阻止返回上一页且实现点击两次退出

nativeUI

在页面中加入一下代码:

data() {  
            return {  
                backButtonPress: 0  
            }  
        }  

......  

onBackPress() {  
            this.backButtonPress ++  
            if (this.backButtonPress > 1) {  
                plus.runtime.quit()  
            } else {  
                plus.nativeUI.toast(this.$t('app.quit'))  
            }  
            setTimeout(() => {  
                this.backButtonPress = 0;  
            }, 2000)  
            return true  
        }

将backButtonPress设置为0的延时处理,设置为2000毫秒,为了和plus.nativeUI.toast默认的显示时长保持一致:

https://www.html5plus.org/doc/zh_cn/nativeui.html#plus.nativeUI.toast
https://www.html5plus.org/doc/zh_cn/nativeui.html#plus.nativeUI.ToastStyles

duration: (String 类型 )提示消息框显示的时间
可选值为"long"、"short",值为"long"时显示时间约为3.5s,值为"short"时显示时间约为2s,未设置时默认值为"short"。

继续阅读 »

在页面中加入一下代码:

data() {  
            return {  
                backButtonPress: 0  
            }  
        }  

......  

onBackPress() {  
            this.backButtonPress ++  
            if (this.backButtonPress > 1) {  
                plus.runtime.quit()  
            } else {  
                plus.nativeUI.toast(this.$t('app.quit'))  
            }  
            setTimeout(() => {  
                this.backButtonPress = 0;  
            }, 2000)  
            return true  
        }

将backButtonPress设置为0的延时处理,设置为2000毫秒,为了和plus.nativeUI.toast默认的显示时长保持一致:

https://www.html5plus.org/doc/zh_cn/nativeui.html#plus.nativeUI.toast
https://www.html5plus.org/doc/zh_cn/nativeui.html#plus.nativeUI.ToastStyles

duration: (String 类型 )提示消息框显示的时间
可选值为"long"、"short",值为"long"时显示时间约为3.5s,值为"short"时显示时间约为2s,未设置时默认值为"short"。

收起阅读 »

uni-app 实现Map地图聚合功能,可自定义聚合图标样式,支持app的nvue/微信小程序的vue/H5,可以设置或定 死地图的俯视角度/倾斜角度

uni-app 实现Map地图聚合功能,可自定义聚合图标样式,支持app的nvue/微信小程序的vue/H5,可以设置或定 死地图的俯视角度/倾斜角度

由于uni-app的api兼容性,造成个平台的聚合样式可能不同

有偿提供源码

QQ:543610866

继续阅读 »

uni-app 实现Map地图聚合功能,可自定义聚合图标样式,支持app的nvue/微信小程序的vue/H5,可以设置或定 死地图的俯视角度/倾斜角度

由于uni-app的api兼容性,造成个平台的聚合样式可能不同

有偿提供源码

QQ:543610866

收起阅读 »

界面建议

增加一个菜单或选项卡组或复选按钮组,可以选择在IDE中显示的标签。这样可以减少非必要标签的拥挤,减少开发者的视觉干扰,因为同名不同项目按“页面名-项目名”取名的标签也不需要这样的格式了,进一步节省了视觉空间,除非同名页面的两个项目都是同时显示的。

增加一个菜单或选项卡组或复选按钮组,可以选择在IDE中显示的标签。这样可以减少非必要标签的拥挤,减少开发者的视觉干扰,因为同名不同项目按“页面名-项目名”取名的标签也不需要这样的格式了,进一步节省了视觉空间,除非同名页面的两个项目都是同时显示的。

关于HBuilderX logo的建议

优化建议

HBuilderX 在mac上logo 过于"粗旷", 显得格格不入,希望mac版本匹配mac os的视觉风格。

HBuilderX 在mac上logo 过于"粗旷", 显得格格不入,希望mac版本匹配mac os的视觉风格。

文件选择器、快速查询文件、自定义路径、完全自定义UI界面、可多选、自定义类型(android)

文件选择器、快速查询文件、自定义路径、完全自定义UI界面、可多选、自定义类型(android):https://ext.dcloud.net.cn/plugin?id=7608

继续阅读 »

文件选择器、快速查询文件、自定义路径、完全自定义UI界面、可多选、自定义类型(android):https://ext.dcloud.net.cn/plugin?id=7608

收起阅读 »

微信小程序源码+开源H5小游戏代码全套

微信小程序

  微信小程序和小游戏源代码创建思路:
  1.6万套微信小程序和小游戏源代码:casgams.top/gm
  ●创建一个数字猜测游戏,在其中用户选择一个范围;
  ●假设用户选择了一个范围,例如,从a到B,其中a和B属于Integer;
  ●系统将选择某个随机整数,用户必须在猜测的最小次数中猜测这个整数。
  源码分析:
  解释1:如果用户输入范围是1到100。编译器随机选择42作为整数。现在猜谜游戏开始了,所以用户输入50作为他/她的第一猜。编译器显示“Try Again!”你猜得太高了。”这意味着随机数(即42)不会落在50到100之间。这就是猜范围的一半的重要性。再一次,用户猜50的一半(你能告诉我为什么吗?)50的一半是25。用户输入25作为他/她的第二个猜测。这一次,编译器将显示“Try Again!”你猜的太小了。”这意味着小于25的整数(从1到25)是无法猜测的。现在用户猜测的范围更短了,即从25到50。智能!用户猜了这个范围的一半,所以,用户第三次猜37。这一次,编译器再次显示输出:“重试!”你猜的太小了。”对于用户来说,每猜一次,猜测范围就会变小。用户的猜测范围是37到50,用户的第四次猜测是43。这一次,编译器将显示“Try Again!”你猜得太高了。”因此,用户的猜测范围将从37到43,同样,用户猜测的是这个范围的一半,即40作为他/她的第五次猜测。这一次,编译器显示输出:“Try Again!”你猜的太小了。”让猜测更小,从41到43。现在用户第六次猜41。这是错误的,显示输出“Try Again!”你猜的太小了。”最后,用户第七次猜对了42。
  总猜测次数= 7
  解释2:如果用户输入范围是1到50。编译器随机选择42作为整数。现在猜谜游戏开始了。50的一半是25。用户输入25作为他/她的第一个猜测。这一次,编译器将显示“Try Again!”你猜的太小了。”这意味着小于25的整数(从1到25)是无法猜测的。现在用户猜测的范围更短了,即从25到50。智能!用户猜了这个范围的一半,所以,用户猜了37作为他/她的第二次猜测。这一次,编译器再次显示输出:“重试!”你猜的太小了。”对于用户来说,每猜一次,猜测范围就会变小。现在,用户的猜测范围是37到50,用户第三次猜43。这一次,编译器将显示“Try Again!”你猜得太高了。”因此,用户的猜测范围将从37到43,同样,用户猜测的是这个范围的一半,即40作为他/她的第四次猜测。这一次,编译器显示输出:“Try Again!”你猜的太小了。”让猜测更小,从41到43。现在用户第五次猜41。这是错误的,显示输出“Try Again!”你猜的太小了。”最后,用户第六次猜对了42。
  总猜测次数= 6
  所以,猜测的最小次数取决于范围。编译器必须计算出猜测的最小次数,这取决于范围本身。对于这个,我们有一个公式:
  Minimum number of guessing = log2(Upper bound – lower bound + 1)
  代码算法步骤如下:
  ●用户输入范围的上界和下界;
  ●编译器在范围之间生成一个随机整数,并将其存储在一个变量中,以备将来引用;
  ●为了进行重复猜测,将初始化一个while循环;
  ●如果用户猜测的数字大于随机选择的数字,用户将得到一个输出“重试!”你猜得太高了。”;
  ●如果用户猜测的数字小于随机选择的数字,用户将得到一个输出“Try Again!”你猜的太小了。”;
  ●如果用户猜了最少的次数,用户就会得到一个“恭喜!”输出;
  ●否则,如果用户没有在最小猜测次数中猜出整数,他/她将得到“Better Luck Next Time!””输出。
  源码实现如下:
  import random
  import math
  # Taking Inputs
  lower = int(input("Enter Lower bound:- "))
  # Taking Inputs
  upper = int(input("Enter Upper bound:- "))
  # generating random number between
  # the lower and upper
  x = random.randint(lower, upper)
  print("\n\tYou've only ",
  round(math.log(upper - lower + 1, 2)),
  " chances to guess the integer!\n")
  # Initializing the number of guesses.
  count = 0
  # for calculation of minimum number of
  # guesses depends upon range
  while count < math.log(upper - lower + 1, 2):
  count += 1
  # taking guessing number as input
  guess = int(input("Guess a number:- "))
  # Condition testing
  if x == guess:
  print("Congratulations you did it in ",
  count, " try")
  # Once guessed, loop will break
  break
  elif x > guess:
  print("You guessed too small!")
  elif x < guess:
  print("You Guessed too high!")
  # If Guessing is more than required guesses,
  # shows this output.
  if count >= math.log(upper - lower + 1, 2):
  print("\nThe number is %d" % x)
  print("\tBetter Luck Next time!")
  # Better to use This source Code on pycharm!
  下面是上面程序的输出结果:

继续阅读 »

  微信小程序和小游戏源代码创建思路:
  1.6万套微信小程序和小游戏源代码:casgams.top/gm
  ●创建一个数字猜测游戏,在其中用户选择一个范围;
  ●假设用户选择了一个范围,例如,从a到B,其中a和B属于Integer;
  ●系统将选择某个随机整数,用户必须在猜测的最小次数中猜测这个整数。
  源码分析:
  解释1:如果用户输入范围是1到100。编译器随机选择42作为整数。现在猜谜游戏开始了,所以用户输入50作为他/她的第一猜。编译器显示“Try Again!”你猜得太高了。”这意味着随机数(即42)不会落在50到100之间。这就是猜范围的一半的重要性。再一次,用户猜50的一半(你能告诉我为什么吗?)50的一半是25。用户输入25作为他/她的第二个猜测。这一次,编译器将显示“Try Again!”你猜的太小了。”这意味着小于25的整数(从1到25)是无法猜测的。现在用户猜测的范围更短了,即从25到50。智能!用户猜了这个范围的一半,所以,用户第三次猜37。这一次,编译器再次显示输出:“重试!”你猜的太小了。”对于用户来说,每猜一次,猜测范围就会变小。用户的猜测范围是37到50,用户的第四次猜测是43。这一次,编译器将显示“Try Again!”你猜得太高了。”因此,用户的猜测范围将从37到43,同样,用户猜测的是这个范围的一半,即40作为他/她的第五次猜测。这一次,编译器显示输出:“Try Again!”你猜的太小了。”让猜测更小,从41到43。现在用户第六次猜41。这是错误的,显示输出“Try Again!”你猜的太小了。”最后,用户第七次猜对了42。
  总猜测次数= 7
  解释2:如果用户输入范围是1到50。编译器随机选择42作为整数。现在猜谜游戏开始了。50的一半是25。用户输入25作为他/她的第一个猜测。这一次,编译器将显示“Try Again!”你猜的太小了。”这意味着小于25的整数(从1到25)是无法猜测的。现在用户猜测的范围更短了,即从25到50。智能!用户猜了这个范围的一半,所以,用户猜了37作为他/她的第二次猜测。这一次,编译器再次显示输出:“重试!”你猜的太小了。”对于用户来说,每猜一次,猜测范围就会变小。现在,用户的猜测范围是37到50,用户第三次猜43。这一次,编译器将显示“Try Again!”你猜得太高了。”因此,用户的猜测范围将从37到43,同样,用户猜测的是这个范围的一半,即40作为他/她的第四次猜测。这一次,编译器显示输出:“Try Again!”你猜的太小了。”让猜测更小,从41到43。现在用户第五次猜41。这是错误的,显示输出“Try Again!”你猜的太小了。”最后,用户第六次猜对了42。
  总猜测次数= 6
  所以,猜测的最小次数取决于范围。编译器必须计算出猜测的最小次数,这取决于范围本身。对于这个,我们有一个公式:
  Minimum number of guessing = log2(Upper bound – lower bound + 1)
  代码算法步骤如下:
  ●用户输入范围的上界和下界;
  ●编译器在范围之间生成一个随机整数,并将其存储在一个变量中,以备将来引用;
  ●为了进行重复猜测,将初始化一个while循环;
  ●如果用户猜测的数字大于随机选择的数字,用户将得到一个输出“重试!”你猜得太高了。”;
  ●如果用户猜测的数字小于随机选择的数字,用户将得到一个输出“Try Again!”你猜的太小了。”;
  ●如果用户猜了最少的次数,用户就会得到一个“恭喜!”输出;
  ●否则,如果用户没有在最小猜测次数中猜出整数,他/她将得到“Better Luck Next Time!””输出。
  源码实现如下:
  import random
  import math
  # Taking Inputs
  lower = int(input("Enter Lower bound:- "))
  # Taking Inputs
  upper = int(input("Enter Upper bound:- "))
  # generating random number between
  # the lower and upper
  x = random.randint(lower, upper)
  print("\n\tYou've only ",
  round(math.log(upper - lower + 1, 2)),
  " chances to guess the integer!\n")
  # Initializing the number of guesses.
  count = 0
  # for calculation of minimum number of
  # guesses depends upon range
  while count < math.log(upper - lower + 1, 2):
  count += 1
  # taking guessing number as input
  guess = int(input("Guess a number:- "))
  # Condition testing
  if x == guess:
  print("Congratulations you did it in ",
  count, " try")
  # Once guessed, loop will break
  break
  elif x > guess:
  print("You guessed too small!")
  elif x < guess:
  print("You Guessed too high!")
  # If Guessing is more than required guesses,
  # shows this output.
  if count >= math.log(upper - lower + 1, 2):
  print("\nThe number is %d" % x)
  print("\tBetter Luck Next time!")
  # Better to use This source Code on pycharm!
  下面是上面程序的输出结果:

收起阅读 »

一个完全 composition-api 的音频案例 vue3-audio

vue3

vue3-audio
一个完全由 composition-api 构建的音频播放案例。 具体可见 vue3-audio 完全 composition-api 的音频案例

主要用来演示 tob-use 中的 useAudio 的使用。

import { useAudio } from '@/uni_modules/tob-use'  

// 播放列表  
const playlist = ['src1', 'src2', '...']  

const {   
    prev,  
    next,  
    toggle,  
} = useAudio(playlist)  

toggle() // 播放,开  
toggle() // 暂停,关  
toggle() // 播放,开  
// ... 不断对当前状态取反  

toggle(false) // 暂停  
toggle(true) // 播放  

toggle(true, 'new src') // 播放新的音频  

prev() // 上一首  
next() // 下一首  

prev(2) // 上上首  
next(2) // 下下首
继续阅读 »

vue3-audio
一个完全由 composition-api 构建的音频播放案例。 具体可见 vue3-audio 完全 composition-api 的音频案例

主要用来演示 tob-use 中的 useAudio 的使用。

import { useAudio } from '@/uni_modules/tob-use'  

// 播放列表  
const playlist = ['src1', 'src2', '...']  

const {   
    prev,  
    next,  
    toggle,  
} = useAudio(playlist)  

toggle() // 播放,开  
toggle() // 暂停,关  
toggle() // 播放,开  
// ... 不断对当前状态取反  

toggle(false) // 暂停  
toggle(true) // 播放  

toggle(true, 'new src') // 播放新的音频  

prev() // 上一首  
next() // 下一首  

prev(2) // 上上首  
next(2) // 下下首
收起阅读 »

uniapp自定义组件父子组件props传递对象数据时,当对象中包含函数,子组件无法引用到对象中的函数的解决办法【转载】

uniapp自定义组件父子组件props传递对象数据时,当对象中包含函数,子组件无法引用到对象中的函数的解决办法

出现这种情况 是因为uniapp 在传递数据的时候使用的是JSON.parse(JSON.stringify(obj1))这样传递的 无法传递函数。
具体参考
[https://blog.csdn.net/py_boy/article/details/107089150]
解决办法是重写挂载在Vue原型对象上的patch方法 如下

import {myPatch} from "./extendWeixin"  
// #ifndef H5  
Vue.prototype.__patch__ = myPatch;  
// #endif

extendWeixin.js 只需要把原来的patch方法复制过来稍微修改对象复制部分的代码即可

// replace platform patch function  
const ARRAYTYPE = '[object Array]';  
const OBJECTTYPE = '[object Object]';  

export function myPatch (oldVnode, vnode) {  
  var this$1 = this;  
  if (vnode === null) { //destroy  
    return  
  }  
  if (this.mpType === 'page' || this.mpType === 'component') {  
    var mpInstance = this.$scope;  
    var data = Object.create(null);  
    try {  
      data = cloneWithData(this);  
    } catch (err) {  
      console.error(err);  
    }  
    data.__webviewId__ = mpInstance.data.__webviewId__;  
    var mpData = Object.create(null);  
    Object.keys(data).forEach(function (key) { //仅同步 data 中有的数据  
      mpData[key] = mpInstance.data[key];  
    });  
    var diffData = this.$shouldDiffData === false ? data : diff(data, mpData);  
    if (Object.keys(diffData).length) {  
      if (Object({  
        "NODE_ENV": "development",  
        "VUE_APP_NAME": "ancient-empire-app",  
        "VUE_APP_PLATFORM": "mp-weixin",  
        "BASE_URL": "/"  
      }).VUE_APP_DEBUG) {  
        console.log(  
            '[' + (+new Date) + '][' + (mpInstance.is || mpInstance.route)  
            + '][' + this._uid +  
            ']差量更新',  
            JSON.stringify(diffData));  
      }  
      this.__next_tick_pending = true;  
      mpInstance.setData(diffData, function () {  
        this$1.__next_tick_pending = false;  
        flushCallbacks$1(this$1);  
      });  
    } else {  
      flushCallbacks$1(this);  
    }  
  }  
};  

function cloneWithData(vm) {  
  // 确保当前 vm 所有数据被同步  
  var ret = Object.create(null);  
  var dataKeys = [].concat(  
      Object.keys(vm._data || {}),  
      Object.keys(vm._computedWatchers || {}));  

  dataKeys.reduce(function (ret, key) {  
    ret[key] = vm[key];  
    return ret  
  }, ret);  

  // vue-composition-api  
  var compositionApiState = vm.__composition_api_state__  
      || vm.__secret_vfa_state__;  
  var rawBindings = compositionApiState && compositionApiState.rawBindings;  
  if (rawBindings) {  
    Object.keys(rawBindings).forEach(function (key) {  
      ret[key] = vm[key];  
    });  
  }  

  //TODO 需要把无用数据处理掉,比如 list=>l0 则 list 需要移除,否则多传输一份数据  
  Object.assign(ret, vm.$mp.data || {});  
  if (  
      Array.isArray(vm.$options.behaviors) &&  
      vm.$options.behaviors.indexOf('uni://form-field') !== -1  
  ) { //form-field  
    ret['name'] = vm.name;  
    ret['value'] = vm.value;  
  }  

  return copyProperWithMethod(ret);  
}  

function diff(current, pre) {  
  var result = {};  
  syncKeys(current, pre);  
  _diff(current, pre, '', result);  
  return result  
}  

function syncKeys(current, pre) {  
  if (current === pre) {  
    return  
  }  
  var rootCurrentType = type(current);  
  var rootPreType = type(pre);  
  if (rootCurrentType == OBJECTTYPE && rootPreType == OBJECTTYPE) {  
    if (Object.keys(current).length >= Object.keys(pre).length) {  
      for (var key in pre) {  
        var currentValue = current[key];  
        if (currentValue === undefined) {  
          current[key] = null;  
        } else {  
          syncKeys(currentValue, pre[key]);  
        }  
      }  
    }  
  } else if (rootCurrentType == ARRAYTYPE && rootPreType == ARRAYTYPE) {  
    if (current.length >= pre.length) {  
      pre.forEach(function (item, index) {  
        syncKeys(current[index], item);  
      });  
    }  
  }  
}  

function flushCallbacks$1(vm) {  
  if (vm.__next_tick_callbacks && vm.__next_tick_callbacks.length) {  
    if (process.env.VUE_APP_DEBUG) {  
      var mpInstance = vm.$scope;  
      console.log(  
          '[' + (+new Date) + '][' + (mpInstance.is || mpInstance.route) + ']['  
          + vm._uid +  
          ']:flushCallbacks[' + vm.__next_tick_callbacks.length + ']');  
    }  
    var copies = vm.__next_tick_callbacks.slice(0);  
    vm.__next_tick_callbacks.length = 0;  
    for (var i = 0; i < copies.length; i++) {  
      copies[i]();  
    }  
  }  
}  

function _diff(current, pre, path, result) {  
  if (current === pre) {  
    return  
  }  
  var rootCurrentType = type(current);  
  var rootPreType = type(pre);  
  if (rootCurrentType == OBJECTTYPE) {  
    if (rootPreType != OBJECTTYPE || Object.keys(current).length < Object.keys(  
        pre).length) {  
      setResult(result, path, current);  
    } else {  
      var loop = function (key) {  
        var currentValue = current[key];  
        var preValue = pre[key];  
        var currentType = type(currentValue);  
        var preType = type(preValue);  
        if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {  
          if (currentValue != pre[key]) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          }  
        } else if (currentType == ARRAYTYPE) {  
          if (preType != ARRAYTYPE) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          } else {  
            if (currentValue.length < preValue.length) {  
              setResult(result, (path == '' ? '' : path + ".") + key,  
                  currentValue);  
            } else {  
              currentValue.forEach(function (item, index) {  
                _diff(item, preValue[index],  
                    (path == '' ? '' : path + ".") + key + '[' + index + ']',  
                    result);  
              });  
            }  
          }  
        } else if (currentType == OBJECTTYPE) {  
          if (preType != OBJECTTYPE || Object.keys(currentValue).length  
              < Object.keys(preValue).length) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          } else {  
            for (let subKey in currentValue) {  
              _diff(currentValue[subKey], preValue[subKey],  
                  (path == '' ? '' : path + ".") + key + '.' + subKey, result);  
            }  
          }  
        }  
      };  

      for (var key in current) {  
        loop(key);  
      }  
    }  
  } else if (rootCurrentType == ARRAYTYPE) {  
    if (rootPreType != ARRAYTYPE) {  
      setResult(result, path, current);  
    } else {  
      if (current.length < pre.length) {  
        setResult(result, path, current);  
      } else {  
        current.forEach(function (item, index) {  
          _diff(item, pre[index], path + '[' + index + ']', result);  
        });  
      }  
    }  
  } else {  
    setResult(result, path, current);  
  }  
}  

function setResult(result, k, v) {  
  result[k] = v;  
}  

function type(obj) {  
  return Object.prototype.toString.call(obj)  
}  

function copyProperWithMethod(obj1){  
  let obj2 = JSON.parse(JSON.stringify(obj1));  
  copyMethodWithAddress(obj1, obj2);  
  return obj2;  
}  

function copyMethodWithAddress(from, to) {  
  let properArray = Object.keys(from);  
  for (let proper of properArray) {  
    let p = from[proper];  
    if (p instanceof Function) {  
      to[proper] = p;  
    } else if (p instanceof Array) {  
      if (!to[proper]) {  
        to[proper] = [];  
      }  
      copyMethodWithAddressByArray(p, to[proper]);  
    } else if (p instanceof Object) {  
      if (!to[proper]) {  
        to[proper] = {};  
      }  
      copyMethodWithAddress(p, to[proper])  
    }  
  }  
}  

function copyMethodWithAddressByArray(from, to) {  
  while (to.length < from.length) {  
    to.push(null);  
  }  
  for (let i = 0; i < from.length; i++) {  
    let a = from[i];  
    if (a instanceof Array){  
      copyMethodWithAddressByArray(a, to[i]);  
    } else if (a instanceof Function) {  
      to[i] = a;  
    } else if (a instanceof Object) {  
      copyMethodWithAddress(a, to[i]);  
    }  
  }  
}

重点是 copyMethodWithAddress方法

原文链接:https://blog.csdn.net/qq_40445661/article/details/118539610

继续阅读 »

uniapp自定义组件父子组件props传递对象数据时,当对象中包含函数,子组件无法引用到对象中的函数的解决办法

出现这种情况 是因为uniapp 在传递数据的时候使用的是JSON.parse(JSON.stringify(obj1))这样传递的 无法传递函数。
具体参考
[https://blog.csdn.net/py_boy/article/details/107089150]
解决办法是重写挂载在Vue原型对象上的patch方法 如下

import {myPatch} from "./extendWeixin"  
// #ifndef H5  
Vue.prototype.__patch__ = myPatch;  
// #endif

extendWeixin.js 只需要把原来的patch方法复制过来稍微修改对象复制部分的代码即可

// replace platform patch function  
const ARRAYTYPE = '[object Array]';  
const OBJECTTYPE = '[object Object]';  

export function myPatch (oldVnode, vnode) {  
  var this$1 = this;  
  if (vnode === null) { //destroy  
    return  
  }  
  if (this.mpType === 'page' || this.mpType === 'component') {  
    var mpInstance = this.$scope;  
    var data = Object.create(null);  
    try {  
      data = cloneWithData(this);  
    } catch (err) {  
      console.error(err);  
    }  
    data.__webviewId__ = mpInstance.data.__webviewId__;  
    var mpData = Object.create(null);  
    Object.keys(data).forEach(function (key) { //仅同步 data 中有的数据  
      mpData[key] = mpInstance.data[key];  
    });  
    var diffData = this.$shouldDiffData === false ? data : diff(data, mpData);  
    if (Object.keys(diffData).length) {  
      if (Object({  
        "NODE_ENV": "development",  
        "VUE_APP_NAME": "ancient-empire-app",  
        "VUE_APP_PLATFORM": "mp-weixin",  
        "BASE_URL": "/"  
      }).VUE_APP_DEBUG) {  
        console.log(  
            '[' + (+new Date) + '][' + (mpInstance.is || mpInstance.route)  
            + '][' + this._uid +  
            ']差量更新',  
            JSON.stringify(diffData));  
      }  
      this.__next_tick_pending = true;  
      mpInstance.setData(diffData, function () {  
        this$1.__next_tick_pending = false;  
        flushCallbacks$1(this$1);  
      });  
    } else {  
      flushCallbacks$1(this);  
    }  
  }  
};  

function cloneWithData(vm) {  
  // 确保当前 vm 所有数据被同步  
  var ret = Object.create(null);  
  var dataKeys = [].concat(  
      Object.keys(vm._data || {}),  
      Object.keys(vm._computedWatchers || {}));  

  dataKeys.reduce(function (ret, key) {  
    ret[key] = vm[key];  
    return ret  
  }, ret);  

  // vue-composition-api  
  var compositionApiState = vm.__composition_api_state__  
      || vm.__secret_vfa_state__;  
  var rawBindings = compositionApiState && compositionApiState.rawBindings;  
  if (rawBindings) {  
    Object.keys(rawBindings).forEach(function (key) {  
      ret[key] = vm[key];  
    });  
  }  

  //TODO 需要把无用数据处理掉,比如 list=>l0 则 list 需要移除,否则多传输一份数据  
  Object.assign(ret, vm.$mp.data || {});  
  if (  
      Array.isArray(vm.$options.behaviors) &&  
      vm.$options.behaviors.indexOf('uni://form-field') !== -1  
  ) { //form-field  
    ret['name'] = vm.name;  
    ret['value'] = vm.value;  
  }  

  return copyProperWithMethod(ret);  
}  

function diff(current, pre) {  
  var result = {};  
  syncKeys(current, pre);  
  _diff(current, pre, '', result);  
  return result  
}  

function syncKeys(current, pre) {  
  if (current === pre) {  
    return  
  }  
  var rootCurrentType = type(current);  
  var rootPreType = type(pre);  
  if (rootCurrentType == OBJECTTYPE && rootPreType == OBJECTTYPE) {  
    if (Object.keys(current).length >= Object.keys(pre).length) {  
      for (var key in pre) {  
        var currentValue = current[key];  
        if (currentValue === undefined) {  
          current[key] = null;  
        } else {  
          syncKeys(currentValue, pre[key]);  
        }  
      }  
    }  
  } else if (rootCurrentType == ARRAYTYPE && rootPreType == ARRAYTYPE) {  
    if (current.length >= pre.length) {  
      pre.forEach(function (item, index) {  
        syncKeys(current[index], item);  
      });  
    }  
  }  
}  

function flushCallbacks$1(vm) {  
  if (vm.__next_tick_callbacks && vm.__next_tick_callbacks.length) {  
    if (process.env.VUE_APP_DEBUG) {  
      var mpInstance = vm.$scope;  
      console.log(  
          '[' + (+new Date) + '][' + (mpInstance.is || mpInstance.route) + ']['  
          + vm._uid +  
          ']:flushCallbacks[' + vm.__next_tick_callbacks.length + ']');  
    }  
    var copies = vm.__next_tick_callbacks.slice(0);  
    vm.__next_tick_callbacks.length = 0;  
    for (var i = 0; i < copies.length; i++) {  
      copies[i]();  
    }  
  }  
}  

function _diff(current, pre, path, result) {  
  if (current === pre) {  
    return  
  }  
  var rootCurrentType = type(current);  
  var rootPreType = type(pre);  
  if (rootCurrentType == OBJECTTYPE) {  
    if (rootPreType != OBJECTTYPE || Object.keys(current).length < Object.keys(  
        pre).length) {  
      setResult(result, path, current);  
    } else {  
      var loop = function (key) {  
        var currentValue = current[key];  
        var preValue = pre[key];  
        var currentType = type(currentValue);  
        var preType = type(preValue);  
        if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {  
          if (currentValue != pre[key]) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          }  
        } else if (currentType == ARRAYTYPE) {  
          if (preType != ARRAYTYPE) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          } else {  
            if (currentValue.length < preValue.length) {  
              setResult(result, (path == '' ? '' : path + ".") + key,  
                  currentValue);  
            } else {  
              currentValue.forEach(function (item, index) {  
                _diff(item, preValue[index],  
                    (path == '' ? '' : path + ".") + key + '[' + index + ']',  
                    result);  
              });  
            }  
          }  
        } else if (currentType == OBJECTTYPE) {  
          if (preType != OBJECTTYPE || Object.keys(currentValue).length  
              < Object.keys(preValue).length) {  
            setResult(result, (path == '' ? '' : path + ".") + key,  
                currentValue);  
          } else {  
            for (let subKey in currentValue) {  
              _diff(currentValue[subKey], preValue[subKey],  
                  (path == '' ? '' : path + ".") + key + '.' + subKey, result);  
            }  
          }  
        }  
      };  

      for (var key in current) {  
        loop(key);  
      }  
    }  
  } else if (rootCurrentType == ARRAYTYPE) {  
    if (rootPreType != ARRAYTYPE) {  
      setResult(result, path, current);  
    } else {  
      if (current.length < pre.length) {  
        setResult(result, path, current);  
      } else {  
        current.forEach(function (item, index) {  
          _diff(item, pre[index], path + '[' + index + ']', result);  
        });  
      }  
    }  
  } else {  
    setResult(result, path, current);  
  }  
}  

function setResult(result, k, v) {  
  result[k] = v;  
}  

function type(obj) {  
  return Object.prototype.toString.call(obj)  
}  

function copyProperWithMethod(obj1){  
  let obj2 = JSON.parse(JSON.stringify(obj1));  
  copyMethodWithAddress(obj1, obj2);  
  return obj2;  
}  

function copyMethodWithAddress(from, to) {  
  let properArray = Object.keys(from);  
  for (let proper of properArray) {  
    let p = from[proper];  
    if (p instanceof Function) {  
      to[proper] = p;  
    } else if (p instanceof Array) {  
      if (!to[proper]) {  
        to[proper] = [];  
      }  
      copyMethodWithAddressByArray(p, to[proper]);  
    } else if (p instanceof Object) {  
      if (!to[proper]) {  
        to[proper] = {};  
      }  
      copyMethodWithAddress(p, to[proper])  
    }  
  }  
}  

function copyMethodWithAddressByArray(from, to) {  
  while (to.length < from.length) {  
    to.push(null);  
  }  
  for (let i = 0; i < from.length; i++) {  
    let a = from[i];  
    if (a instanceof Array){  
      copyMethodWithAddressByArray(a, to[i]);  
    } else if (a instanceof Function) {  
      to[i] = a;  
    } else if (a instanceof Object) {  
      copyMethodWithAddress(a, to[i]);  
    }  
  }  
}

重点是 copyMethodWithAddress方法

原文链接:https://blog.csdn.net/qq_40445661/article/details/118539610

收起阅读 »

uni-app 实现验证码输入框,支持多个数字,支持键盘上的短信验证码赋值

uni-app 实现验证码输入框,支持多个数字,支持键盘上的短信验证码赋值

附件为手机录屏演示

有偿提供源码

QQ:543610866

uni-app 实现验证码输入框,支持多个数字,支持键盘上的短信验证码赋值

附件为手机录屏演示

有偿提供源码

QQ:543610866

uniapp Android 自定义启动页相关

启动页

1、.9.png图的制作:

uniapp社区文档和经验贴
https://ask.dcloud.net.cn/article/35527
https://ask.dcloud.net.cn/article/37365

Android官方教程:
https://developer.android.com/studio/write/draw9patch?hl=zh-cn

博客园(这位博主的说明非常详细,照着做,问题不大):
https://www.cnblogs.com/minblog/p/12588561.html

制作.9图时,如果不好标记,把图放大一点(就是Zoom调大一点),边界上的像素位就比较明显

2、uniapp Android 端配置完成后,真机调试是看不到效果的,官方文档有这方面的说明:
https://uniapp.dcloud.io/collocation/manifest?id=splashscreen

继续阅读 »

1、.9.png图的制作:

uniapp社区文档和经验贴
https://ask.dcloud.net.cn/article/35527
https://ask.dcloud.net.cn/article/37365

Android官方教程:
https://developer.android.com/studio/write/draw9patch?hl=zh-cn

博客园(这位博主的说明非常详细,照着做,问题不大):
https://www.cnblogs.com/minblog/p/12588561.html

制作.9图时,如果不好标记,把图放大一点(就是Zoom调大一点),边界上的像素位就比较明显

2、uniapp Android 端配置完成后,真机调试是看不到效果的,官方文档有这方面的说明:
https://uniapp.dcloud.io/collocation/manifest?id=splashscreen

收起阅读 »

android 基于小程序sdk的网络日志抓取实现

android 基于uni小程序sdk的网络日志抓取实现

由于测试需求,需要在android端实现对小程序业务的网络日志捕获,来高效定位前端问题,需求如下:
1.打开小程序,能自动弹出一个debug的小浮窗。

2.点击小浮窗能进入并且看到当次小程序运行过程中的所有网络请求。

--以下uni小程序sdk统称为umpsdk

网络日志捕获

通过翻看umpsdk源码,发现umpsdk是基于weex实现,那么猜测网络层应该在weex里面,所以我们主要分析weex层应该能找到思路。对应源码包应该在uniapp-v8-release.aar。

首先我们从入口处出发,能看到WXSDKInstance这个类,用来做sdk初始化。

public IWXHttpAdapter getWXHttpAdapter() {  
  return WXSDKManager.getInstance().getIWXHttpAdapter();  
}  

public IWXStatisticsListener getWXStatisticsListener() {  
  return this.mStatisticsListener;  
}  

@Nullable  
public IWebSocketAdapter getWXWebSocketAdapter() {  
  return WXSDKManager.getInstance().getIWXWebSocketAdapter();  
}

通过读这块代码发现基础组件都是由WXSDKManager这个类来管理。我们再进入WXSDKManager分析。首先来看构造器

public static WXSDKManager getInstance() {  
  if (sManager == null) {  
    Class var0 = WXSDKManager.class;  
    synchronized(WXSDKManager.class) {  
      if (sManager == null) {  
        sManager = new WXSDKManager();  
      }  
    }  
  }  

  return sManager;  
}

这是一个单例模式。

从语义理解getIWXHttpAdapter这个应该是一个负责网络请求的适配器组件。我们找到对应WXSDKManager中getIWXHttpAdapter的实现。

@NonNull  
public IWXHttpAdapter getIWXHttpAdapter() {  
  if (this.mIWXHttpAdapter == null) {  
    this.mIWXHttpAdapter = new DefaultWXHttpAdapter();  
  }  

  return this.mIWXHttpAdapter;  
}

同样也是一个判空赋值处理,我们再来看DefaultWXHttpAdapter。路径com.taobao.weex.adapter.DefaultWXHttpAdapter

public class DefaultWXHttpAdapter implements IWXHttpAdapter {  
  private static final DefaultWXHttpAdapter.IEventReporterDelegate DEFAULT_DELEGATE = new DefaultWXHttpAdapter.NOPEventReportDelegate();  
  private ExecutorService mExecutorService;  

  public DefaultWXHttpAdapter() {  
  }  

  private void execute(Runnable runnable) {  
    if (this.mExecutorService == null) {  
      this.mExecutorService = Executors.newFixedThreadPool(3);  
    }  

    this.mExecutorService.execute(runnable);  
  }  

  public void sendRequest(final WXRequest request, final OnHttpListener listener) {  
    if (listener != null) {  
      listener.onHttpStart();  
    }  

    this.execute(new Runnable() {  
      public void run() {  
        WXSDKInstance instance = (WXSDKInstance)WXSDKManager.getInstance().getAllInstanceMap().get(request.instanceId);  
        if (null != instance && !instance.isDestroy()) {  
          instance.getApmForInstance().actionNetRequest();  
        }  

        boolean isNetRequestSucceed = true;  
        WXResponse response = new WXResponse();  
        DefaultWXHttpAdapter.IEventReporterDelegate reporter = DefaultWXHttpAdapter.this.getEventReporterDelegate();  

        try {  
          HttpURLConnection connection = DefaultWXHttpAdapter.this.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 = DefaultWXHttpAdapter.this.readInputStreamAsBytes(rawStream, listener);  
          } else {  
            response.errorMsg = DefaultWXHttpAdapter.this.readInputStream(connection.getErrorStream(), listener);  
            isNetRequestSucceed = false;  
          }  

          if (listener != null) {  
            listener.onHttpFinish(response);  
          }  
        } catch (IllegalArgumentException | IOException var10) {  
          Exception e = var10;  
          isNetRequestSucceed = false;  
          var10.printStackTrace();  
          response.statusCode = "-1";  
          response.errorCode = "-1";  
          response.errorMsg = var10.getMessage();  
          if (listener != null) {  
            listener.onHttpFinish(response);  
          }  

          if (var10 instanceof IOException) {  
            try {  
              reporter.httpExchangeFailed((IOException)e);  
            } catch (Throwable var9) {  
              var9.printStackTrace();  
            }  
          }  
        }  

        if (null != instance && !instance.isDestroy()) {  
          instance.getApmForInstance().actionNetResult(isNetRequestSucceed, (String)null);  
        }  

      }  
    });  
  }  

  private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {  
    URL url = new URL(request.url);  
    HttpURLConnection connection = this.createConnection(url);  
    connection.setConnectTimeout(request.timeoutMs);  
    connection.setReadTimeout(request.timeoutMs);  
    connection.setUseCaches(false);  
    connection.setDoInput(true);  
    if (request.paramMap != null) {  
      Set<String> keySets = request.paramMap.keySet();  
      Iterator var6 = keySets.iterator();  

      while(var6.hasNext()) {  
        String key = (String)var6.next();  
        connection.addRequestProperty(key, (String)request.paramMap.get(key));  
      }  
    }  

    if (!"POST".equals(request.method) && !"PUT".equals(request.method) && !"PATCH".equals(request.method)) {  
      if (!TextUtils.isEmpty(request.method)) {  
        connection.setRequestMethod(request.method);  
      } else {  
        connection.setRequestMethod("GET");  
      }  
    } else {  
      connection.setRequestMethod(request.method);  
      if (request.body != null) {  
        if (listener != null) {  
          listener.onHttpUploadProgress(0);  
        }  

        connection.setDoOutput(true);  
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());  
        out.write(request.body.getBytes());  
        out.close();  
        if (listener != null) {  
          listener.onHttpUploadProgress(100);  
        }  
      }  
    }  

    return connection;  
  }  

  private byte[] readInputStreamAsBytes(InputStream inputStream, OnHttpListener listener) throws IOException {  
    if (inputStream == null) {  
      return null;  
    } else {  
      ByteArrayOutputStream buffer = new ByteArrayOutputStream();  
      int readCount = 0;  
      byte[] data = new byte[2048];  

      int nRead;  
      while((nRead = inputStream.read(data, 0, data.length)) != -1) {  
        buffer.write(data, 0, nRead);  
        readCount += nRead;  
        if (listener != null) {  
          listener.onHttpResponseProgress(readCount);  
        }  
      }  

      buffer.flush();  
      return buffer.toByteArray();  
    }  
  }  

  private String readInputStream(InputStream inputStream, OnHttpListener listener) throws IOException {  
    if (inputStream == null) {  
      return null;  
    } else {  
      StringBuilder builder = new StringBuilder();  
      BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(inputStream));  
      char[] data = new char[2048];  

      int len;  
      while((len = localBufferedReader.read(data)) != -1) {  
        builder.append(data, 0, len);  
        if (listener != null) {  
          listener.onHttpResponseProgress(builder.length());  
        }  
      }  

      localBufferedReader.close();  
      return builder.toString();  
    }  
  }  

  protected HttpURLConnection createConnection(URL url) throws IOException {  
    return (HttpURLConnection)url.openConnection();  
  }  

  @NonNull  
  public DefaultWXHttpAdapter.IEventReporterDelegate getEventReporterDelegate() {  
    return DEFAULT_DELEGATE;  
  }  

  private static class NOPEventReportDelegate implements DefaultWXHttpAdapter.IEventReporterDelegate {  
    private NOPEventReportDelegate() {  
    }  

    public void preConnect(HttpURLConnection connection, @Nullable String body) {  
    }  

    public void postConnect() {  
    }  

    public InputStream interpretResponseStream(@Nullable InputStream inputStream) {  
      return inputStream;  
    }  

    public void httpExchangeFailed(IOException e) {  
    }  
  }  

  public interface IEventReporterDelegate {  
    void preConnect(HttpURLConnection var1, @Nullable String var2);  

    void postConnect();  

    InputStream interpretResponseStream(@Nullable InputStream var1);  

    void httpExchangeFailed(IOException var1);  
  }  
}

看到这里我们发现核心是HttpURLConnection来做网络请求的。这里我们发现sendRequest就是请求处理部分。

看到这里现在我们思路已经清晰了,首先做一下几点考虑:

1.从哪里hook

2.怎样hook侵入性最小

最后我们选择自定义适配器的方式,通过hook替换网络适配器来达成需求的后台处理部分。

class XXXWXHttpAdapter(private val function: (UniResponse) -> Unit) : IWXHttpAdapter {  
    private var mExecutorService: ExecutorService? = null  
    private fun execute(runnable: Runnable) {  
        if (mExecutorService == null) {  
            mExecutorService = Executors.newFixedThreadPool(3)  
        }  
        mExecutorService!!.execute(runnable)  
    }  

    override fun sendRequest(request: WXRequest, listener: IWXHttpAdapter.OnHttpListener) {  
        if (listener != null) {  
            listener.onHttpStart()  
        }  
        execute {  
            val instance: WXSDKInstance = WXSDKManager.getInstance().allInstanceMap  
                .get(request.instanceId) as WXSDKInstance  
            if (null != instance && !instance.isDestroy) {  
                instance.apmForInstance.actionNetRequest()  
            }  
            var isNetRequestSucceed = true  
            val response = WXResponse()  
            val reporter: IEventReporterDelegate = eventReporterDelegate  
            var uniResponse: UniResponse? = null  
            try {  
                val connection = openConnection(request, listener)  
                reporter.preConnect(connection, request.body)  
                val headers = connection.headerFields  
                val responseCode = connection.responseCode  
                if (listener != null) {  
                    listener.onHeadersReceived(responseCode, headers)  
                }  
                reporter.postConnect()  
                response.statusCode = responseCode.toString()  
                if (responseCode in 200..299) {  
                    var rawStream = connection.inputStream  
                    rawStream = reporter.interpretResponseStream(rawStream)  
                    response.originalData =  
                        readInputStreamAsBytes(rawStream, listener)  
                } else {  
                    response.errorMsg =  
                        readInputStream(connection.errorStream, listener)  
                    isNetRequestSucceed = false  
                }  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
            } catch (var10: IllegalArgumentException) {  
                val e: Exception = var10  
                isNetRequestSucceed = false  
                var10.printStackTrace()  
                response.statusCode = "-1"  
                response.errorCode = "-1"  
                response.errorMsg = var10.message  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
                if (var10 is IOException) {  
                    try {  
                        reporter.httpExchangeFailed(e as IOException)  
                    } catch (var9: Throwable) {  
                        var9.printStackTrace()  
                    }  
                }  
            } catch (var10: IOException) {  
                val e: Exception = var10  
                isNetRequestSucceed = false  
                var10.printStackTrace()  
                response.statusCode = "-1"  
                response.errorCode = "-1"  
                response.errorMsg = var10.message  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
                if (var10 is IOException) {  
                    try {  
                        reporter.httpExchangeFailed(e as IOException)  
                    } catch (var9: Throwable) {  
                        var9.printStackTrace()  
                    }  
                }  
            }  
            if (uniResponse != null) {  
                uniLog(uniResponse, function)  
            }  
            if (null != instance && !instance.isDestroy) {  
                instance.apmForInstance  
                    .actionNetResult(isNetRequestSucceed, null as String?)  
            }  
        }  
    }  

    @Throws(IOException::class)  
    private fun openConnection(  
        request: WXRequest,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): HttpURLConnection {  
        val url = URL(request.url)  
        val connection = createConnection(url)  
        connection.connectTimeout = request.timeoutMs  
        connection.readTimeout = request.timeoutMs  
        connection.useCaches = false  
        connection.doInput = true  
        if (request.paramMap != null) {  
            val keySets: Set<String> = request.paramMap.keys  
            val var6: Iterator<*> = keySets.iterator()  
            while (var6.hasNext()) {  
                val key = var6.next() as String  
                connection.addRequestProperty(key, request.paramMap.get(key) as String)  
            }  
        }  
        if ("POST" != request.method && "PUT" != request.method && "PATCH" != request.method) {  
            if (!TextUtils.isEmpty(request.method)) {  
                connection.requestMethod = request.method  
            } else {  
                connection.requestMethod = "GET"  
            }  
        } else {  
            connection.requestMethod = request.method  
            if (request.body != null) {  
                listener?.onHttpUploadProgress(0)  
                connection.doOutput = true  
                val out = DataOutputStream(connection.outputStream)  
                out.write(request.body.toByteArray())  
                out.close()  
                listener?.onHttpUploadProgress(100)  
            }  
        }  
        return connection  
    }  

    @Throws(IOException::class)  
    private fun readInputStreamAsBytes(  
        inputStream: InputStream?,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): ByteArray? {  
        return if (inputStream == null) {  
            null  
        } else {  
            val buffer = ByteArrayOutputStream()  
            var readCount = 0  
            val data = ByteArray(2048)  
            var nRead: Int  
            while (inputStream.read(data, 0, data.size).also { nRead = it } != -1) {  
                buffer.write(data, 0, nRead)  
                readCount += nRead  
                listener?.onHttpResponseProgress(readCount)  
            }  
            buffer.flush()  
            buffer.toByteArray()  
        }  
    }  

    @Throws(IOException::class)  
    private fun readInputStream(  
        inputStream: InputStream?,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): String? {  
        return if (inputStream == null) {  
            null  
        } else {  
            val builder = StringBuilder()  
            val localBufferedReader = BufferedReader(InputStreamReader(inputStream))  
            val data = CharArray(2048)  
            var len: Int  
            while (localBufferedReader.read(data).also { len = it } != -1) {  
                builder.append(data, 0, len)  
                listener?.onHttpResponseProgress(builder.length)  
            }  
            localBufferedReader.close()  
            builder.toString()  
        }  
    }  

    @Throws(IOException::class)  
    protected fun createConnection(url: URL): HttpURLConnection {  
        return url.openConnection() as HttpURLConnection  
    }  

    private class NOPEventReportDelegate : IEventReporterDelegate {  
        override fun preConnect(connection: HttpURLConnection?, body: String?) {}  
        override fun postConnect() {}  
        override fun interpretResponseStream(inputStream: InputStream?): InputStream? {  
            return inputStream  
        }  

        override fun httpExchangeFailed(e: IOException?) {}  
    }  

    interface IEventReporterDelegate {  
        fun preConnect(var1: HttpURLConnection?, var2: String?)  
        fun postConnect()  
        fun interpretResponseStream(var1: InputStream?): InputStream?  
        fun httpExchangeFailed(var1: IOException?)  
    }  

    companion object {  
        val eventReporterDelegate: IEventReporterDelegate = NOPEventReportDelegate()  
    }  

    private fun convertUni(wxRequest: WXRequest, wxResponse: WXResponse): UniResponse? =  
        if (wxRequest.url.contains("xxx") && wxRequest.paramMap.containsValue("application/json")) {  
            when {  
                wxRequest.paramMap.containsValue("application/json") -> {  
                    UniResponse(  
                        UniRequest(  
                            wxRequest.paramMap,  
                            wxRequest.url,  
                            wxRequest.method,  
                            JSONObject.parse(wxRequest.body)  
                        ),  
                        wxResponse.statusCode,  
                        wxResponse.data,  
                        System.currentTimeMillis(),  
                        wxResponse.originalData,  
                        wxResponse.errorCode,  
                        wxResponse.errorMsg,  
                        wxResponse.toastMsg,  
                        wxResponse.extendParams  
                    )  
                }  
                wxRequest.paramMap.containsValue("application/x-www-form-urlencoded") -> {  
                    UniResponse(  
                        UniRequest(  
                            wxRequest.paramMap,  
                            wxRequest.url,  
                            wxRequest.method,  
                            wxRequest.body  
                        ),  
                        wxResponse.statusCode,  
                        wxResponse.data,  
                        System.currentTimeMillis(),  
                        wxResponse.originalData,  
                        wxResponse.errorCode,  
                        wxResponse.errorMsg,  
                        wxResponse.toastMsg,  
                        wxResponse.extendParams  
                    )  
                }  
                else -> {  
                    null  
                }  
            }  

        } else {  
            null  
        }  

    private fun uniLog(resp: UniResponse, function: (UniResponse) -> Unit) {  
        function(resp)  
        Log.i("uni-web-req", resp.toJSON())  
        Log.i("uni-web-req-curl", resp.convertorCURL())  
    }  
}  
fun UniResponse.toJSON(): String {  
    val temp = this  

    this.originalData?.apply {  
        temp.data = JSONObject.parse(this)  
    }  
    return JSONObject.toJSONString(temp)  
}  

fun UniResponse.convertorCURL(): String {  
    var curlCmd: String = "curl "  
    if (this.request.body != null) {  
        curlCmd += if (this.request.paramMap?.containsValue("application/json") == true) {  
            "-d '${JSONObject.toJSON(this.request.body)}' "  
        } else {  
            "-d '${this.request.body as String}'"  
        }  
    }  
    //parse Headers  
    this.request.paramMap?.forEach { item ->  
        curlCmd += "-H '${item.key}: ${item.value}' "  
    }  
    curlCmd += "${this.request.method} ${this.request.url}"  
    return curlCmd  
}

如下hook替换适配器

val cls = Class.forName("com.taobao.weex.WXSDKManager")  
val method = cls.getDeclaredMethod("getInstance")  
val manager = method.invoke(null)  
val adapterField = cls.getDeclaredField("mIWXHttpAdapter")  
adapterField.isAccessible = true  
adapterField.set(  
    manager,  
    XXXWXHttpAdapter {  
        JLog.i("onEvent ", " it >>> $it")  
        responseList.add(it)  
    }  
)

自此完成日志捕获,新版本小程序sdk是基于多进程的所以我们把hook移入service,对应三个service0,1,2。分别对应进程unimp0,nuimp1,nuimp2。之后我们需要做的就是在启动小程序时,获取当前运行的进程名再做相对应的service启动即可。

浮窗控制

我们需要考虑一下几点:

1.代码尽可能少的侵入

2.尽可能少的暴露过多到业务层(因为我们需要区分debug以及release)

通过调研发现weex中有关WXModule的部分,其中框架层有对activity 生命周期的hook。经过试验发现,这里有这样一个规律:onCreate不生效,onResume仅在二次之后打开生效,onDestory每次生效。

通过阅读weex源码发现(这里不做深入探讨),WXModule在打开小程序的过程会进行一次初始化(WXModuleManager)。

至此思路已经清晰

1.我们在wxmodule构造方法中去标记小程序在前台的标志

2.在onDestory中标记小程序退出

3.采用aidl封装进程间通讯

4.通过浮窗点击的跳转自定义activity需要采用startActivityFromUniTask压入小程序进程

继续阅读 »

android 基于uni小程序sdk的网络日志抓取实现

由于测试需求,需要在android端实现对小程序业务的网络日志捕获,来高效定位前端问题,需求如下:
1.打开小程序,能自动弹出一个debug的小浮窗。

2.点击小浮窗能进入并且看到当次小程序运行过程中的所有网络请求。

--以下uni小程序sdk统称为umpsdk

网络日志捕获

通过翻看umpsdk源码,发现umpsdk是基于weex实现,那么猜测网络层应该在weex里面,所以我们主要分析weex层应该能找到思路。对应源码包应该在uniapp-v8-release.aar。

首先我们从入口处出发,能看到WXSDKInstance这个类,用来做sdk初始化。

public IWXHttpAdapter getWXHttpAdapter() {  
  return WXSDKManager.getInstance().getIWXHttpAdapter();  
}  

public IWXStatisticsListener getWXStatisticsListener() {  
  return this.mStatisticsListener;  
}  

@Nullable  
public IWebSocketAdapter getWXWebSocketAdapter() {  
  return WXSDKManager.getInstance().getIWXWebSocketAdapter();  
}

通过读这块代码发现基础组件都是由WXSDKManager这个类来管理。我们再进入WXSDKManager分析。首先来看构造器

public static WXSDKManager getInstance() {  
  if (sManager == null) {  
    Class var0 = WXSDKManager.class;  
    synchronized(WXSDKManager.class) {  
      if (sManager == null) {  
        sManager = new WXSDKManager();  
      }  
    }  
  }  

  return sManager;  
}

这是一个单例模式。

从语义理解getIWXHttpAdapter这个应该是一个负责网络请求的适配器组件。我们找到对应WXSDKManager中getIWXHttpAdapter的实现。

@NonNull  
public IWXHttpAdapter getIWXHttpAdapter() {  
  if (this.mIWXHttpAdapter == null) {  
    this.mIWXHttpAdapter = new DefaultWXHttpAdapter();  
  }  

  return this.mIWXHttpAdapter;  
}

同样也是一个判空赋值处理,我们再来看DefaultWXHttpAdapter。路径com.taobao.weex.adapter.DefaultWXHttpAdapter

public class DefaultWXHttpAdapter implements IWXHttpAdapter {  
  private static final DefaultWXHttpAdapter.IEventReporterDelegate DEFAULT_DELEGATE = new DefaultWXHttpAdapter.NOPEventReportDelegate();  
  private ExecutorService mExecutorService;  

  public DefaultWXHttpAdapter() {  
  }  

  private void execute(Runnable runnable) {  
    if (this.mExecutorService == null) {  
      this.mExecutorService = Executors.newFixedThreadPool(3);  
    }  

    this.mExecutorService.execute(runnable);  
  }  

  public void sendRequest(final WXRequest request, final OnHttpListener listener) {  
    if (listener != null) {  
      listener.onHttpStart();  
    }  

    this.execute(new Runnable() {  
      public void run() {  
        WXSDKInstance instance = (WXSDKInstance)WXSDKManager.getInstance().getAllInstanceMap().get(request.instanceId);  
        if (null != instance && !instance.isDestroy()) {  
          instance.getApmForInstance().actionNetRequest();  
        }  

        boolean isNetRequestSucceed = true;  
        WXResponse response = new WXResponse();  
        DefaultWXHttpAdapter.IEventReporterDelegate reporter = DefaultWXHttpAdapter.this.getEventReporterDelegate();  

        try {  
          HttpURLConnection connection = DefaultWXHttpAdapter.this.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 = DefaultWXHttpAdapter.this.readInputStreamAsBytes(rawStream, listener);  
          } else {  
            response.errorMsg = DefaultWXHttpAdapter.this.readInputStream(connection.getErrorStream(), listener);  
            isNetRequestSucceed = false;  
          }  

          if (listener != null) {  
            listener.onHttpFinish(response);  
          }  
        } catch (IllegalArgumentException | IOException var10) {  
          Exception e = var10;  
          isNetRequestSucceed = false;  
          var10.printStackTrace();  
          response.statusCode = "-1";  
          response.errorCode = "-1";  
          response.errorMsg = var10.getMessage();  
          if (listener != null) {  
            listener.onHttpFinish(response);  
          }  

          if (var10 instanceof IOException) {  
            try {  
              reporter.httpExchangeFailed((IOException)e);  
            } catch (Throwable var9) {  
              var9.printStackTrace();  
            }  
          }  
        }  

        if (null != instance && !instance.isDestroy()) {  
          instance.getApmForInstance().actionNetResult(isNetRequestSucceed, (String)null);  
        }  

      }  
    });  
  }  

  private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {  
    URL url = new URL(request.url);  
    HttpURLConnection connection = this.createConnection(url);  
    connection.setConnectTimeout(request.timeoutMs);  
    connection.setReadTimeout(request.timeoutMs);  
    connection.setUseCaches(false);  
    connection.setDoInput(true);  
    if (request.paramMap != null) {  
      Set<String> keySets = request.paramMap.keySet();  
      Iterator var6 = keySets.iterator();  

      while(var6.hasNext()) {  
        String key = (String)var6.next();  
        connection.addRequestProperty(key, (String)request.paramMap.get(key));  
      }  
    }  

    if (!"POST".equals(request.method) && !"PUT".equals(request.method) && !"PATCH".equals(request.method)) {  
      if (!TextUtils.isEmpty(request.method)) {  
        connection.setRequestMethod(request.method);  
      } else {  
        connection.setRequestMethod("GET");  
      }  
    } else {  
      connection.setRequestMethod(request.method);  
      if (request.body != null) {  
        if (listener != null) {  
          listener.onHttpUploadProgress(0);  
        }  

        connection.setDoOutput(true);  
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());  
        out.write(request.body.getBytes());  
        out.close();  
        if (listener != null) {  
          listener.onHttpUploadProgress(100);  
        }  
      }  
    }  

    return connection;  
  }  

  private byte[] readInputStreamAsBytes(InputStream inputStream, OnHttpListener listener) throws IOException {  
    if (inputStream == null) {  
      return null;  
    } else {  
      ByteArrayOutputStream buffer = new ByteArrayOutputStream();  
      int readCount = 0;  
      byte[] data = new byte[2048];  

      int nRead;  
      while((nRead = inputStream.read(data, 0, data.length)) != -1) {  
        buffer.write(data, 0, nRead);  
        readCount += nRead;  
        if (listener != null) {  
          listener.onHttpResponseProgress(readCount);  
        }  
      }  

      buffer.flush();  
      return buffer.toByteArray();  
    }  
  }  

  private String readInputStream(InputStream inputStream, OnHttpListener listener) throws IOException {  
    if (inputStream == null) {  
      return null;  
    } else {  
      StringBuilder builder = new StringBuilder();  
      BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(inputStream));  
      char[] data = new char[2048];  

      int len;  
      while((len = localBufferedReader.read(data)) != -1) {  
        builder.append(data, 0, len);  
        if (listener != null) {  
          listener.onHttpResponseProgress(builder.length());  
        }  
      }  

      localBufferedReader.close();  
      return builder.toString();  
    }  
  }  

  protected HttpURLConnection createConnection(URL url) throws IOException {  
    return (HttpURLConnection)url.openConnection();  
  }  

  @NonNull  
  public DefaultWXHttpAdapter.IEventReporterDelegate getEventReporterDelegate() {  
    return DEFAULT_DELEGATE;  
  }  

  private static class NOPEventReportDelegate implements DefaultWXHttpAdapter.IEventReporterDelegate {  
    private NOPEventReportDelegate() {  
    }  

    public void preConnect(HttpURLConnection connection, @Nullable String body) {  
    }  

    public void postConnect() {  
    }  

    public InputStream interpretResponseStream(@Nullable InputStream inputStream) {  
      return inputStream;  
    }  

    public void httpExchangeFailed(IOException e) {  
    }  
  }  

  public interface IEventReporterDelegate {  
    void preConnect(HttpURLConnection var1, @Nullable String var2);  

    void postConnect();  

    InputStream interpretResponseStream(@Nullable InputStream var1);  

    void httpExchangeFailed(IOException var1);  
  }  
}

看到这里我们发现核心是HttpURLConnection来做网络请求的。这里我们发现sendRequest就是请求处理部分。

看到这里现在我们思路已经清晰了,首先做一下几点考虑:

1.从哪里hook

2.怎样hook侵入性最小

最后我们选择自定义适配器的方式,通过hook替换网络适配器来达成需求的后台处理部分。

class XXXWXHttpAdapter(private val function: (UniResponse) -> Unit) : IWXHttpAdapter {  
    private var mExecutorService: ExecutorService? = null  
    private fun execute(runnable: Runnable) {  
        if (mExecutorService == null) {  
            mExecutorService = Executors.newFixedThreadPool(3)  
        }  
        mExecutorService!!.execute(runnable)  
    }  

    override fun sendRequest(request: WXRequest, listener: IWXHttpAdapter.OnHttpListener) {  
        if (listener != null) {  
            listener.onHttpStart()  
        }  
        execute {  
            val instance: WXSDKInstance = WXSDKManager.getInstance().allInstanceMap  
                .get(request.instanceId) as WXSDKInstance  
            if (null != instance && !instance.isDestroy) {  
                instance.apmForInstance.actionNetRequest()  
            }  
            var isNetRequestSucceed = true  
            val response = WXResponse()  
            val reporter: IEventReporterDelegate = eventReporterDelegate  
            var uniResponse: UniResponse? = null  
            try {  
                val connection = openConnection(request, listener)  
                reporter.preConnect(connection, request.body)  
                val headers = connection.headerFields  
                val responseCode = connection.responseCode  
                if (listener != null) {  
                    listener.onHeadersReceived(responseCode, headers)  
                }  
                reporter.postConnect()  
                response.statusCode = responseCode.toString()  
                if (responseCode in 200..299) {  
                    var rawStream = connection.inputStream  
                    rawStream = reporter.interpretResponseStream(rawStream)  
                    response.originalData =  
                        readInputStreamAsBytes(rawStream, listener)  
                } else {  
                    response.errorMsg =  
                        readInputStream(connection.errorStream, listener)  
                    isNetRequestSucceed = false  
                }  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
            } catch (var10: IllegalArgumentException) {  
                val e: Exception = var10  
                isNetRequestSucceed = false  
                var10.printStackTrace()  
                response.statusCode = "-1"  
                response.errorCode = "-1"  
                response.errorMsg = var10.message  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
                if (var10 is IOException) {  
                    try {  
                        reporter.httpExchangeFailed(e as IOException)  
                    } catch (var9: Throwable) {  
                        var9.printStackTrace()  
                    }  
                }  
            } catch (var10: IOException) {  
                val e: Exception = var10  
                isNetRequestSucceed = false  
                var10.printStackTrace()  
                response.statusCode = "-1"  
                response.errorCode = "-1"  
                response.errorMsg = var10.message  
                uniResponse = convertUni(request, response)  
                if (listener != null) {  
                    listener.onHttpFinish(response)  
                }  
                if (var10 is IOException) {  
                    try {  
                        reporter.httpExchangeFailed(e as IOException)  
                    } catch (var9: Throwable) {  
                        var9.printStackTrace()  
                    }  
                }  
            }  
            if (uniResponse != null) {  
                uniLog(uniResponse, function)  
            }  
            if (null != instance && !instance.isDestroy) {  
                instance.apmForInstance  
                    .actionNetResult(isNetRequestSucceed, null as String?)  
            }  
        }  
    }  

    @Throws(IOException::class)  
    private fun openConnection(  
        request: WXRequest,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): HttpURLConnection {  
        val url = URL(request.url)  
        val connection = createConnection(url)  
        connection.connectTimeout = request.timeoutMs  
        connection.readTimeout = request.timeoutMs  
        connection.useCaches = false  
        connection.doInput = true  
        if (request.paramMap != null) {  
            val keySets: Set<String> = request.paramMap.keys  
            val var6: Iterator<*> = keySets.iterator()  
            while (var6.hasNext()) {  
                val key = var6.next() as String  
                connection.addRequestProperty(key, request.paramMap.get(key) as String)  
            }  
        }  
        if ("POST" != request.method && "PUT" != request.method && "PATCH" != request.method) {  
            if (!TextUtils.isEmpty(request.method)) {  
                connection.requestMethod = request.method  
            } else {  
                connection.requestMethod = "GET"  
            }  
        } else {  
            connection.requestMethod = request.method  
            if (request.body != null) {  
                listener?.onHttpUploadProgress(0)  
                connection.doOutput = true  
                val out = DataOutputStream(connection.outputStream)  
                out.write(request.body.toByteArray())  
                out.close()  
                listener?.onHttpUploadProgress(100)  
            }  
        }  
        return connection  
    }  

    @Throws(IOException::class)  
    private fun readInputStreamAsBytes(  
        inputStream: InputStream?,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): ByteArray? {  
        return if (inputStream == null) {  
            null  
        } else {  
            val buffer = ByteArrayOutputStream()  
            var readCount = 0  
            val data = ByteArray(2048)  
            var nRead: Int  
            while (inputStream.read(data, 0, data.size).also { nRead = it } != -1) {  
                buffer.write(data, 0, nRead)  
                readCount += nRead  
                listener?.onHttpResponseProgress(readCount)  
            }  
            buffer.flush()  
            buffer.toByteArray()  
        }  
    }  

    @Throws(IOException::class)  
    private fun readInputStream(  
        inputStream: InputStream?,  
        listener: IWXHttpAdapter.OnHttpListener?  
    ): String? {  
        return if (inputStream == null) {  
            null  
        } else {  
            val builder = StringBuilder()  
            val localBufferedReader = BufferedReader(InputStreamReader(inputStream))  
            val data = CharArray(2048)  
            var len: Int  
            while (localBufferedReader.read(data).also { len = it } != -1) {  
                builder.append(data, 0, len)  
                listener?.onHttpResponseProgress(builder.length)  
            }  
            localBufferedReader.close()  
            builder.toString()  
        }  
    }  

    @Throws(IOException::class)  
    protected fun createConnection(url: URL): HttpURLConnection {  
        return url.openConnection() as HttpURLConnection  
    }  

    private class NOPEventReportDelegate : IEventReporterDelegate {  
        override fun preConnect(connection: HttpURLConnection?, body: String?) {}  
        override fun postConnect() {}  
        override fun interpretResponseStream(inputStream: InputStream?): InputStream? {  
            return inputStream  
        }  

        override fun httpExchangeFailed(e: IOException?) {}  
    }  

    interface IEventReporterDelegate {  
        fun preConnect(var1: HttpURLConnection?, var2: String?)  
        fun postConnect()  
        fun interpretResponseStream(var1: InputStream?): InputStream?  
        fun httpExchangeFailed(var1: IOException?)  
    }  

    companion object {  
        val eventReporterDelegate: IEventReporterDelegate = NOPEventReportDelegate()  
    }  

    private fun convertUni(wxRequest: WXRequest, wxResponse: WXResponse): UniResponse? =  
        if (wxRequest.url.contains("xxx") && wxRequest.paramMap.containsValue("application/json")) {  
            when {  
                wxRequest.paramMap.containsValue("application/json") -> {  
                    UniResponse(  
                        UniRequest(  
                            wxRequest.paramMap,  
                            wxRequest.url,  
                            wxRequest.method,  
                            JSONObject.parse(wxRequest.body)  
                        ),  
                        wxResponse.statusCode,  
                        wxResponse.data,  
                        System.currentTimeMillis(),  
                        wxResponse.originalData,  
                        wxResponse.errorCode,  
                        wxResponse.errorMsg,  
                        wxResponse.toastMsg,  
                        wxResponse.extendParams  
                    )  
                }  
                wxRequest.paramMap.containsValue("application/x-www-form-urlencoded") -> {  
                    UniResponse(  
                        UniRequest(  
                            wxRequest.paramMap,  
                            wxRequest.url,  
                            wxRequest.method,  
                            wxRequest.body  
                        ),  
                        wxResponse.statusCode,  
                        wxResponse.data,  
                        System.currentTimeMillis(),  
                        wxResponse.originalData,  
                        wxResponse.errorCode,  
                        wxResponse.errorMsg,  
                        wxResponse.toastMsg,  
                        wxResponse.extendParams  
                    )  
                }  
                else -> {  
                    null  
                }  
            }  

        } else {  
            null  
        }  

    private fun uniLog(resp: UniResponse, function: (UniResponse) -> Unit) {  
        function(resp)  
        Log.i("uni-web-req", resp.toJSON())  
        Log.i("uni-web-req-curl", resp.convertorCURL())  
    }  
}  
fun UniResponse.toJSON(): String {  
    val temp = this  

    this.originalData?.apply {  
        temp.data = JSONObject.parse(this)  
    }  
    return JSONObject.toJSONString(temp)  
}  

fun UniResponse.convertorCURL(): String {  
    var curlCmd: String = "curl "  
    if (this.request.body != null) {  
        curlCmd += if (this.request.paramMap?.containsValue("application/json") == true) {  
            "-d '${JSONObject.toJSON(this.request.body)}' "  
        } else {  
            "-d '${this.request.body as String}'"  
        }  
    }  
    //parse Headers  
    this.request.paramMap?.forEach { item ->  
        curlCmd += "-H '${item.key}: ${item.value}' "  
    }  
    curlCmd += "${this.request.method} ${this.request.url}"  
    return curlCmd  
}

如下hook替换适配器

val cls = Class.forName("com.taobao.weex.WXSDKManager")  
val method = cls.getDeclaredMethod("getInstance")  
val manager = method.invoke(null)  
val adapterField = cls.getDeclaredField("mIWXHttpAdapter")  
adapterField.isAccessible = true  
adapterField.set(  
    manager,  
    XXXWXHttpAdapter {  
        JLog.i("onEvent ", " it >>> $it")  
        responseList.add(it)  
    }  
)

自此完成日志捕获,新版本小程序sdk是基于多进程的所以我们把hook移入service,对应三个service0,1,2。分别对应进程unimp0,nuimp1,nuimp2。之后我们需要做的就是在启动小程序时,获取当前运行的进程名再做相对应的service启动即可。

浮窗控制

我们需要考虑一下几点:

1.代码尽可能少的侵入

2.尽可能少的暴露过多到业务层(因为我们需要区分debug以及release)

通过调研发现weex中有关WXModule的部分,其中框架层有对activity 生命周期的hook。经过试验发现,这里有这样一个规律:onCreate不生效,onResume仅在二次之后打开生效,onDestory每次生效。

通过阅读weex源码发现(这里不做深入探讨),WXModule在打开小程序的过程会进行一次初始化(WXModuleManager)。

至此思路已经清晰

1.我们在wxmodule构造方法中去标记小程序在前台的标志

2.在onDestory中标记小程序退出

3.采用aidl封装进程间通讯

4.通过浮窗点击的跳转自定义activity需要采用startActivityFromUniTask压入小程序进程

收起阅读 »