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

android端实体返回键阻止返回上一页且实现点击两次退出
在页面中加入一下代码:
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"。

文件选择器、快速查询文件、自定义路径、完全自定义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-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
收起阅读 »
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压入小程序进程
收起阅读 »