ynchuan / blog Goto Github PK
View Code? Open in Web Editor NEWhttps://github.com/ynchuan/blog/issues
Home Page: http://ynchuan.github.io/blog
https://github.com/ynchuan/blog/issues
Home Page: http://ynchuan.github.io/blog
冒泡排序
快速排序
深度复制对象
继承
链表
promise实现
generator
Sizzle
virtual DOM
vNode diff
vNode patch
b-tree
abstract syntax tree (AST)
xss、csrf、arp、xff、中间人攻击、运营商劫持、防暴刷
AMD CMD UMD (module/define实现)
页面开发中最主要的是进行页面布局,布局分为区块上下和左右布局;其中因为行级标签的存在,所以上下布局十分简单;复杂之处在于实现区块左右放置的方式很多,所以本文主要介绍我们常见的左右布局方式。
页面布局实现包含html开发和css编写,二者相辅相成,html是css开发先导,html合理性决定css布局复杂度。
table标签能够实现左右布局,table布局也是页面布局中使用的最早的布局方式,但是由于存在性能和灵活性问题,已被逐渐放弃,转而在采用div布局。
行级元素(display:inline)
和块级元素(display:block)
之间的属性,hack
处理。具有特性:
清除浮动
问题;弹性盒子主要用于移动前端开发,属性不支持ie6、7、8,支持chrome,firefox,不适合面向互联网用户pc网页布局。
float能够使得元素向左或者向右靠边布局,在同级元素中设置正常流区域与浮动块并列,浮动块会在正常流同级区域的边界处,并影响该正常流水平方向布局,在正常流区域块的盒子设置margin等于浮动块的宽度可以清除影响。
float对元素水平方向的影响,除采用margin进行影响的清除,依据块级格式上下文原理,还可以在受影响的元素上添加overflow:hidden
来清除浮动对该区域块带来的影响。
绝对定位,通过对支点元素进行相对偏移实现左右布局
git rebase refer
,将(当前分支)与(refer所在版本线)的历史共同版本起始的后续版本的所有提交以refer版本为基础,每个版本重新提交,复用提交日志git rebase -i refer
,交互式版本重载,后续配合指令git rebase --continue|abort|skip
git cherry-pick -x commit_id
// 增加 -x 参数,表示保留原提交的作者信息进行提交。
在 Git 1.7.2 版本开始,新增了支持批量 cherry-pick ,就是可以一次将一个连续的时间序列内的 commit ,设定一个开始和结束的 commit ,进行 cherry-pick 操作。
git cherry_pick start-commit-id…end-commit-id
https://learngitbranching.js.org/
// setImmediate(function(){
// console.log(1);
// },0);
setTimeout(function() {
console.log(12);
}, 0);
setTimeout(function() {
console.log(2);
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log(9); // macro中添加micro任务
});
}, 0);
setTimeout(function() {
console.log(11);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function() {
console.log(5);
});
console.log(6);
// process.nextTick(function() {
// console.log(7);
// });
console.log(8);
// 3 4 6 8 5 12 2 9 11
var obj = {
a: 'obja',
b: 'objb',
c: 'objc',
d: 'objd',
};
var arr = ['arr1', 'arr2', 'arr3', 'arr4'];
for (var key in obj) {
console.log(key);
console.log("====" + obj[key]);
}
for (var key in arr) {
console.log(key);
console.log("====" + arr[key]);
}
for (var key of arr) {
console.log(key);
}
for (var key of obj) { //报错,没有继承iterator
console.log(key);
}
let conn = new RTCPeerConnection({
iceServers: []
})
let noop = function(){}
conn.onicecandidate = function(ice){
if (ice.candidate){
//使用正则获取ip
let ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
let ip_addr = ip_regex.exec(ice.candidate.candidate)[1];
console.log(ip_addr)
conn.onicecandidate = noop
}
}
conn.createDataChannel('dog')
//创建一个SDP协议请求
conn.createOffer(conn.setLocalDescription.bind(conn),noop)
var url = 'http://pc.videoclick.baidu.com/p.gif?pid=104&u=http%253A%252F%252Fv.baidu.com%252F&tn=indsa&tpl=indsa&refer=&city=%E5%8C%97%E4%BA%AC&sa=c&flow=99090';
var req = new XMLHttpRequest();
req.open('GET', url, false);
// req.setRequestHeader("User-Agent", "UA_test_string");
req.send();
console.log(req.responseText);
var ua = navigator.userAgent;
Object.defineProperty(navigator, 'userAgent', {
get: function () {
return ua + 'HREL'
}
})
利用事件冒泡实现类似$('dom').on('click','.name,.age',function(e){})的调用函数;
var joseph = function (k, m, N) {
var idx;
var data = []
for (var i = 0; i < m; i++) {
data.push(i);
}
console.log(data);
while (m > 1) {
idx = k + N - 1;
if (idx > m) {
idx = idx % m;
}
k = idx;
if (k === m) {
k = 1;
}
--m;
var tmp = data.splice(idx - 1, 1);
console.log(tmp);
}
console.log(data);
};
joseph(2, 10, 3);
img[src='http://g.hiphotos.baidu.com/video/pic/item/77c6a7efce1b9d16f160d9faffdeb48f8d546481.jpg'] display:none 会load图片
buffer<--TypedArray(Uint16Array,Int32Array..)<--ArrayBuffer
node中buffer是基于TypedArray封装,TypedArray又是基于ArrayBuffer封装,ArrayBuffer代码存储空间,以字节为单位,TypedArray代表一种存储方式,例如Uint16Array,标识使用两个字节(即两个ArrayBuffer)存储一个符号;Int32Array,4个ArrayBuffer存储一个字符
遇到了一个关于跨域的问题,简单记录下。
当在A域通过jsonp访问B域下的接口(js),则之前B域下的cookie是可以与接口同步到后台
当在A域通过jsonp访问B域下的接口(js),则之前B域下的cookie是不能与接口同步到后台,应该是webview的安全策略不同于主流浏览器
深度merge
function merge(source, target) {
if (typeof source === 'object' && typeof target === 'object') {
for (var key in target) {
if (target.hasOwnProperty(key)) {
source[key] = merge(source[key], target[key]);
}
}
} else {
source = target;
}
return source;
}
function Set () {
this.set = Object.create(null);
}
Set.prototype.has = function has (key) {
return this.set[key] === true
};
Set.prototype.add = function add (key) {
this.set[key] = true;
};
Set.prototype.clear = function clear () {
this.set = Object.create(null);
};
Math.pow(2,32)
,2^32Math.sqrt(27,3)
27开3次方0b1111>>1 === 0b0111有符号右移2位,0b1111<<1 === 0b11110,-9>>>2无符号右移2位
0b11111111 ~ 0b01111111 [-127,127]
Math.pow(2,1023)+Math.pow(2,1022)+...===Number.MAX_VALUE;
JS中整数最大使用1KB大小存储,再大会越界;C使用4B存储INT大小,最大到Math.pow(2,31)+Math.pow(2,30)+...
0b00000000 与 0b10000000
都标识0,正数和负数都是以补码形式存储页面开发中最主要的是进行页面布局,布局分为区块上下和左右布局;其中因为行级标签的存在,所以上下布局十分简单;复杂之处在于实现区块左右放置的方式很多,所以本文主要介绍我们常见的左右布局方式。
页面布局实现包含html开发和css编写,二者相辅相成,html是css开发先导,html合理性决定css布局复杂度。
table标签能够实现左右布局,table布局也是页面布局中使用的最早的布局方式,但是由于存在性能和灵活性问题,已被逐渐放弃,转而在采用div布局。
行级元素(display:inline)
和块级元素(display:block)
之间的属性,hack
处理。具有特性:
清除浮动
问题;弹性盒子主要用于移动前端开发,属性不支持ie6、7、8,支持chrome,firefox,不适合面向互联网用户pc网页布局。
float能够使得元素向左或者向右靠边布局,在同级元素中设置正常流区域与浮动块并列,浮动块会在正常流同级区域的边界处,并影响该正常流水平方向布局,在正常流区域块的盒子设置margin等于浮动块的宽度可以清除影响。
float对元素水平方向的影响,除采用margin进行影响的清除,依据块级格式上下文原理,还可以在受影响的元素上添加overflow:hidden
来清除浮动对该区域块带来的影响。
绝对定位,通过对支点元素进行相对偏移实现左右布局
https请求交换流程
A
B+C
(A+B->D) + C = E
E+F->D
证书生成过程
# 生成服务器端私钥
openssl genrsa -out server.key 1024
# 生成服务器端公钥
openssl rsa -in server.key -pubout -out server.pem
# 生成客户端私钥
openssl genrsa -out client.key 1024
# 生成客户端公钥
openssl rsa -in client.key -pubout -out client.pem
# 生成 CA 私钥
openssl genrsa -out ca.key 1024
# 生成 CA 公钥
openssl rsa -in ca.key -pubout -out ca.pem
# X.509 Certificate Signing Request (CSR) Management.
openssl req -new -key ca.key -out ca.csr
# X.509 Certificate Data Management.
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
openssl req -new -key server.key -out server.csr
# 向自己的 CA 机构申请证书,签名过程需要 CA 的证书和私钥参与,最终颁发一个带有 CA 签名的证书
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
# client 端
openssl req -new -key client.key -out client.csr
# client 端到 CA 签名
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt
const Readable = require('stream').Readable;
Readable.prototype._pipe_ = function (output) {
this.on('data', chunk => {
let rst = output.write(chunk);
if (!rst) {
this.pause();
output.once('drain', () => {
this.resume();
});
}
console.log(chunk.length);
}).on('end', () => {
output.end();
});
}
const fs = require('fs');
const input = fs.createReadStream('./data/amuse.json');
const output = fs.createWriteStream('./data/amuse3.json');
input._pipe_(output);
const Readable = require('stream').Readable;
const Duplex = require('stream').Duplex;
Readable.prototype._pipe_ = function (output) {
this.on('data', chunk => {
let rst = output.write(chunk);
if (!rst) {
this.pause();
output.once('drain', () => {
this.resume();
});
}
console.log(chunk.length);
}).on('end', () => {
output.end();
});
return output;
}
class MDuplux extends Duplex {
constructor(opt) {
super(opt)
}
_read() {
}
_write(chunk, enc, cbk) {
this.push(chunk);
cbk(); // **会完成写缓存写出并释放,同时触发上层写的drain**
}
}
const fs = require('fs');
const input = fs.createReadStream('./data/amuse.json');
const output = fs.createWriteStream('./data/amuse3.json');
const mDuplux = new MDuplux({
readableHighWaterMark: 32,
writableHighWaterMark: 32
});
input._pipe_(mDuplux)._pipe_(output);
class Node {
constructor(data) {
this.data = data;
this.prev = null;
this.next = null;
}
}
class LinkedList {
constructor(arr) {
this.size = 0;
this.head = new Node('head');
this.end = this.head;
if (arr) {
this.arrayToLinked(arr);
}
}
add(o, r) {
let curr = new Node(o);
if (r) {
let rNode = this.get(r);
curr.prev = rNode;
curr.next = rNode.next;
if (rNode.next) {
rNode.next.prev = curr;
}
rNode.next = curr;
}
else {
let end = this.end;
curr.prev = end;
end.next = curr;
this.end = curr;
}
this.size++;
return curr;
}
remove(o) {
let t = this.get(o);
if (t === this.end) {
this.end = t.prev;
t.prev.next = null;
}
else if (t !== this.head) {
t.next.prev = t.prev;
t.prev.next = t.next;
}
this.size--;
return !!t;
}
update(o, u) {
let t = this.get(o);
if (t) {
t.data = u;
}
return !!t;
}
get(o) {
let curr = this.head;
while (curr.data !== o) {
curr = curr.next;
}
let ret = '';
if (curr !== this.head) {
ret = curr;
}
return ret;
}
getAll() {
let curr = this.head.next;
this.log('-----GET-ALL-START-----');
while (curr) {
this.log(curr.data);
curr = curr.next;
}
this.log('-----GET-ALL--END-----');
}
getSize() {
this.log(this.size);
return this.size;
}
toArray() {
let curr = this.head;
let ret = [];
while (curr.next) {
ret.push(curr.next);
curr = curr.next;
}
this.log(ret);
return ret;
}
toLinked(arr) {
if (arr) {
arr.forEach(item => {
this.add(item);
});
}
}
getEndNode() {
let curr = this.head;
while (curr.next) {
curr = curr.next;
}
this.log(curr);
return curr;
}
getHeadNode() {
let curr = this.end;
while (curr.prev) {
curr = curr.prev;
}
this.log(curr);
return curr;
}
log(msg) {
console.log(msg);
}
}
var ll = new LinkedList();
ll.add('zhao');
ll.add('qian');
ll.add('li');
ll.getAll();
ll.add('sun', 'qian');
ll.getAll();
ll.update('zhao', 'wang');
ll.getAll();
ll.remove('wang');
ll.getAll();
ll.toArray();
ll.toLinked(['zhou', 'wu', 'zheng']);
ll.getAll();
ll.getHeadNode();
ll.getEndNode();
multipart/form-data; boundary=---------------------------66841812532223
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZOVBvOdURQKFDgjK
{"action":"get","format":"json","conds":{"status":-1,"ps":-1,"conf":1}}
在 Ctrl+P 窗口下还可以:
直接输入文件名,跳转到文件
? 列出当前可执行的动作
! 显示 Errors或 Warnings,也可以 Ctrl+Shift+M
: 跳转到行数,也可以 Ctrl+G 直接进入
@ 跳转到 symbol(搜索变量或者函数),也可以 Ctrl+Shift+O 直接进入
@ 根据分类跳转 symbol,查找属性或函数,也可以 Ctrl+Shift+O 后输入:进入
# 根据名字查找 symbol,也可以 Ctrl+T
var t = 30;
var ret = 1;
var start1 = Date.now();
var count1 = 0;
while (count1 < 200000000) {
count1++;
if (t > 30) {
ret = 0;
} else {
ret = 1;
}
}
var end1 = Date.now();
console.log('1:' + (end1 - start1));
var start2 = Date.now();
var count2 = 0;
while (count2 < 200000000) {
count2++;
ret = t > 30 ? 0 : 1;
}
var end2 = Date.now();
console.log('2:' + (end2 - start2));
map具有有序性和一一映射的特性,所以map可以转换为数组和对象
weakMap适用场景:对一些存在引用的数据关联其他信息,引用失效后其他信息自动跟着消失,防止内存泄漏
/**
* 由于map具有有序性和一一映射的特性,所以map可以转换为数组和对象
* map<->array 二维数组形式
* map<->object 键值对形式
* map<->json 键值对形式
*/
let mapToObj = (map) => {
let obj = Object.create(null);
for ([key, value] of map.entries()) {
obj[key] = value;
}
return obj;
}
let map = new Map();
map.set('a', 'a').set('c', 1).set({}, {}).set([1, 2], [3, 4]);
console.log("map to object test:");
console.log(mapToObj(map));
let objToMap = (obj) => {
let ret = new Map;
for (let key in obj) {
ret.set(key, obj[key]);
}
return ret;
};
let obj = { 'a': 1, '2': 'b' };
console.log("object to map test:");
console.log(objToMap(obj));
let arrToMap = (arr) => {
return new Map(arr);
};
let arr = [
[1, 'a'],
[2, 'b'],
[4, 'd']
];
console.log("array to Map test:");
console.log(arrToMap(arr));
let mapToArr = (map) => {
return [...map];
};
console.log("map to array test:");
console.log(mapToArr(new Map().set('a', 'a').set('c', 1).set({}, {}).set([1, 2], [3, 4])));
let jsonToMap1 = (json) => {
return objToMap(JSON.parse(json));
};
let jsonToMap2 = (json) => {
return new Map(JSON.parse(json));
};
let json1 = '{"a":"a","b":"b"}';
console.log("json to map test 1:");
console.log(jsonToMap1(json1));
let json2 = '[["a","1"],["b"],[3,"c"]]';
console.log("json to map test 2:");
console.log(jsonToMap2(json2));
let mapToJson = (map) => {
return JSON.stringify(mapToObj(map));
};
console.log("map to array json:");
console.log(mapToJson(new Map().set('a', 'a').set('c', 1).set({}, {}).set([1, 2], [3, 4])));
gulp通过pipe将流传递到下个转换流中处理,转换流处理逻辑
push
到下一个转换流或输出流internalBinding(fs)
从底层获取数据后触发onread
,进而驱动push
产生data
事件,然后推动后续转换流T1的_write
,此时调用内置_transform
,完成转换,最后调用done
,将下一个转换流的push
将数据向后传递,递归回源后buffer为空,底层将继续输送数据,继续循环。var fs = require("fs");
var through = require("through2");
var path = require("path");
var input = path.resolve(__dirname, './data');
var ouput = path.resolve(__dirname, './data.json');
fs.createReadStream(input)
.pipe(through.obj(function (contents, enc, done) {
contents = contents.toString("utf-8");
enc = "utf-8";
done(null, contents, enc);
}))
.pipe(through.obj(function (contents, enc, done) {
done(null, contents.toUpperCase(), enc);
}))
.pipe(through.obj(function (contents, enc, done) {
contents = contents.split("").reverse().join("");
done(null, contents, enc);
}))
.pipe(fs.createWriteStream(ouput));
var arr = [9, 23, 12, 11, 43, 54, 43, 2, 12, 66];
var bubble1 = function (arr, dir) {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = 0; j < arr.length - i - 1; j++) {
// 遍历从前开始,排序数据后置
if (dir === 'asc') {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
else {
if (arr[j] < arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
}
return arr;
};
var bubble2 = function (arr, dir) {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = arr.length - 1; j > i; j--) {
// 遍历从后开始,排序数据前置
if (dir === 'asc') {
if (arr[j - 1] > arr[j]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
}
}
else {
if (arr[j - 1] < arr[j]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
}
}
}
}
return arr;
};
var rst = bubble2(arr, 'asc');
console.log(rst);
var arr = [9, 23, 12, 11, 43, 54, 43, 2, 12, 66];
var quick = function (arr, dir) {
var lf = [];
var rg = [];
var ref = arr[0];
var ret = [];
if (arr.length > 0) {
arr.forEach(function (i) {
if (dir === 'asc') {
if (i < ref) {
lf.push(i);
}
else if (i > ref) {
rg.push(i);
}
}
else {
if (i > ref) {
lf.push(i);
}
else if (i < ref) {
rg.push(i);
}
}
});
lf = quick(lf, dir);
rg = quick(rg, dir);
ret = lf.concat(ref, rg);
}
return ret;
};
var rst = quick(arr, 'desc');
console.log(rst);
密码不要设置的过于简单,MD5一般是不能够被反编译的
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
md5.update('abcdef');
var ret = md5.digest('hex');
console.log(ret);
var crc32 = require('crc').crc32;
crc32('hello').toString(16);
// "3610a686"
var crypto = require('crypto');
var sha1 = crypto.createHash('sha1');
sha1.update('abcdef');
var ret = sha1.digest('hex');
console.log(ret);
let inputString = '我是明文字符串!';
let publicKeyStr = '-----BEGIN PUBLIC KEY-----\n' +
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCe8g647gv...\n' +
'-----END PUBLIC KEY-----';
let privateKeyStr = '-----BEGIN PRIVATE KEY-----\n' +
'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGB...\n' +
'-----END PRIVATE KEY-----';
const NodeRSA = require('node-rsa');
let key = new NodeRSA(publicKeyStr);
key.setOptions({
encryptionScheme: 'pkcs1'
});
let encryptStr = key.encrypt(inputString, 'base64');
key.importKey(privateKeyStr, 'pkcs8-private');
let decryptStr = key.decrypt(encryptStr, 'utf8');
console.log(encryptStr);
console.log(decryptStr);
const crypto = require('crypto');
let _encryptStr = crypto.publicEncrypt({
key: publicKeyStr,
padding: crypto.constants.RSA_PKCS1_PADDING
}, Buffer.from(inputString, 'utf8')).toString('base64');
let _decryptStr = crypto.privateDecrypt({
key: privateKeyStr,
padding: crypto.constants.RSA_PKCS1_PADDING
}, _encryptStr).toString('utf-8');
console.log(_encryptStr);
console.log(_decryptStr);
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new(P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
}
catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
}
catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : new P(function (resolve) {
resolve(result.value);
}).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = {
label: 0,
sent: function () {
if (t[0] & 1) throw t[1];
return t[1];
},
trys: [],
ops: []
},
f, y, t;
return {
next: verb(0),
"throw": verb(1),
"return": verb(2)
};
function verb(n) {
return function (v) {
return step([n, v]);
};
}
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return {
value: op[1],
done: false
};
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2]) _.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
}
catch (e) {
op = [6, e];
y = 0;
}
finally {
f = t = 0;
}
if (op[0] & 5) throw op[1];
return {
value: op[0] ? op[1] : void 0,
done: true
};
}
};
request.on('data', function (data, response) {
return __awaiter(this, void 0, void 0, function () {
var cookie, user_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cookie = response.headers['set-cookie'];
if (cookie) {
res.setHeader('set-cookie', cookie);
}
if (!(data.errno === 0)) return [3 /*break*/ , 2];
return [4 /*yield*/ , util_1.default.checkUserStatus(req, res, param.username)];
case 1:
user_1 = _a.sent();
data.userInfo = user_1;
if (user_1.status !== 1) {
data.errno = user_1.statusCode;
data.msg = user_1.statusMsg;
}
_a.label = 2;
case 2:
res.json(data);
return [2 /*return*/ ];
}
});
});
});
request.on('data', async function (data, response) {
let cookie = response.headers['set-cookie'];
if (cookie) {
res.setHeader('set-cookie', cookie);
}
if (data.errno === 0) {
let user = await lib.checkUserStatus(req, res, param.username);
data.userInfo = user;
if (user.status !== 1) {
data.errno = user.statusCode;
data.msg = user.statusMsg;
}
}
res.json(data);
});
.auto{
flex: 1 1 auto;
height: 200px
}
.h100{
flex: 1 1 100px;
height: 200px
}
values
insert into table (field1,field2) values (value1,value2)
insert into table (field1,field2) values (value1,value2), (value3,value4)
set
insert into table set field1 = value1, field2 = value2
一次只能添加一条
grep -i 'GET /watch/' ./access.log.201907101* | awk '{match($0, /HTTP\/1.0" (200) - (-|(http(s?):\/\/(\w|[0-9]|\.|:)+\/))/, host);print host[2]}' |sort |uniq -c |sort -n
#!/bin/bash
for m in `cat img`; do
wget -c $m ;
done;
Promise是一个前期配置好执行回调,直到未来某一函数驱动回调执行的函数对象
class MPromise {
constructor(fn) {
this.status = 'pending';
this.defered = []; // 每个promise可以有多个级联promise,一个级联promise可以前置多个回调
let handler = (data) => {
this.data = data;
setImmediate(() => {
this.handler();
});
};
let resolve = data => {
this.status = 'fulfilled';
handler(data);
};
let reject = data => {
this.status = 'rejected';
handler(data);
}
fn(resolve, reject);
}
handler() {
let data = this.data;
let status = this.status;
let defered = this.defered;
defered.forEach(item => {
let next = item.nResolve;
let nReject = item.nReject;
let fn = item.fulfilled;
let finalFns = this.finalFns;
if (status === 'rejected') {
next = nReject;
fn = item.rejected;
}
try {
let rst = void 0;
if (fn) {
if (this.isFinally) {
fn();
rst = data;
}
else {
rst = fn(data);
}
}
if (rst instanceof MPromise) {
rst.then(next);
}
else {
next(rst);
}
}
catch (e) {
console.error(e);
nReject(e);
}
});
}
then(doFulfilled, doRejected) {
let defered = {
fulfilled: doFulfilled,
rejected: doRejected,
};
this.defered.push(defered);
return new MPromise((res, rej) => {
// 推入下一个promise驱动
defered.nResolve = res;
defered.nReject = rej;
});
}
catch (fn) {
return this.then(void 0, fn);
}
finally(fn) {
this.isFinally = 1;
return this.then(fn, fn);
}
static resolve(data) {
return new MPromise((resolve, reject) => {
resolve(data);
});
}
static reject(data) {
return new MPromise((resolve, reject) => {
reject(data);
});
}
}
const Promise = MPromise;
let mpms = new Promise((res, rej) => {
setTimeout(() => {
res(new Date);
})
});
mpms.finally(data => {
console.log('phase 1 finnaly');
console.log(data);
return data;
}).then(data => {
console.log('1');
console.log(data);
}).catch(data => {
console.log('1-1');
console.log(data);
});
mpms.finally(data => {
console.log('phase 2 finnaly');
console.log(data);
return data;
}).then(data => {
console.log('2');
console.log(data);
return '2-1';
}).finally(data => {
console.log('phase 2 finnaly');
console.log(data);
return data;
});
mixins(...mixins) {
function copyProperties(target, source) {
for (let key of Object.keys(source)) {
if (key !== "constructor" &&
key !== "prototype" &&
key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝实例属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
/**
* 函数节流(throttle):某个动作规定的时间内只执行第一次
*/
const throttle = (action, idle) => {
let last = 0;
return (...prop) => {
let curr = +new Date;
if (curr - last > idle) {
action.apply(this, prop);
last = curr;
}
}
};
const throttleAction = throttle((...prop) => {
console.log(prop);
}, 1000);
throttleAction('throttle1');
throttleAction('throttle2');
throttleAction('throttle3'); // 输出 throttle1
setTimeout(() => {
throttleAction('throttle4');
throttleAction('throttle5');
throttleAction('throttle6'); // 输出 throttle4
}, 2000);
/**
* 函数节流(throttlePro):非立即执行,某个动作规定的时间内只执行第一次
*/
const throttlePro = (action, delay) => {
let tId;
return (...args) => {
if (!tId) {
tId = setTimeout(() => {
tId = null;
action.apply(this, args);
}, delay);
}
}
}
const throttleProAction = throttlePro((...prop) => {
console.log(prop);
}, 1000);
throttleProAction('throttlePro1');
throttleProAction('throttlePro2');
throttleProAction('throttlePro3'); // 输出 [throttlePro1]
setTimeout(() => {
throttleProAction('throttlePro4');
throttleProAction('throttlePro5');
throttleProAction('throttlePro6'); // 输出 [throttlePro4]
}, 2000);
/**
* 函数防抖 (debounce):某个动作规定的时间内只执行最后一次
*/
const debounce = (action, idle) => {
let handler;
return (...prop) => {
handler && clearTimeout(handler);
handler = setTimeout(() => {
action.apply(this, prop);
}, idle);
}
};
const debounceAction = debounce((...prop) => {
console.log(prop);
}, 1000);
debounceAction('debounce1');
debounceAction('debounce2');
debounceAction('debounce3'); // 输出 debounce3
setTimeout(() => {
debounceAction('debounce4');
debounceAction('debounce5');
debounceAction('debounce6'); // 输出 debounce6
}, 2000);
var isArray = function (obj) {
var ret = 0;
if (Object.prototype.toString.call(obj) === '[object Array]') {
ret = 1;
}
return ret;
};
var isObject = function (obj) {
var ret = 0;
if (Object.prototype.toString.call(obj) === '[object Object]') {
ret = 1;
}
return ret;
};
var deepCopy = function (obj) {
var ret;
if (isArray(obj)) {
ret = [];
obj.forEach((item) => {
ret.push(deepCopy(item));
});
}
else if (isObject(obj)) {
ret = {};
Object.keys(obj).forEach((k) => {
ret[k] = deepCopy(obj[k]);
});
}
else {
ret = obj;
}
return ret;
};
var deepClone = function (obj) {
var clone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj[key] && typeof obj[key] === "object") {
clone[key] = deepClone(obj[key]);
}
else {
clone[key] = obj[key];
}
}
}
return clone;
};
可以看到高度
记录方式:blh/slh/sh/tb/fs,例如:/80/60/57/42/30
替换元素的宽度是由内部元素的大小控制,不会受display的值影响,如果想干预宽高,需要人工外部设置宽高
替换元素默认display样式:
<!DOCTYPE html>
<html>
<head>
<title>write test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="renderer" content="webkit">
</head>
<body>
video.js:alert(2);
test.js:alert(5);
<script>
var doc = document;
var html0 = '<div class="its"><script>alert(1)<\/script><script src="./video.js"><\/script></div>';
var html1 = '<div class="its"><script src="./video.js"><\/script><script>alert(1)<\/script></div>';
var html2 = '<div class="its"><script>alert(3)<\/script><script src="./video.js"><\/script><script>alert(1)<\/script>';
var html3 = '<div class="its"><script>alert(3)<\/script><script src="./video.js"><\/script><script>alert(1)<\/script><script src="./video.js"><\/script></div>';
var html4 = '<div class="its"><script>alert(3)<\/script><script src="./video.js"><\/script><script>alert(1)<\/script><script src="./test.js"><\/script><script>alert(6)<\/script></div>';
doc.write(html4);
alert(9);
</script>
<div class="sct">js</div>
<p>
doc写操作,
内容A将写到当前脚本的紧贴着的后面,
如果其中有脚本,执行A的内联脚本至外链脚本部分,
然后继续执行当前脚本;
再执行外链脚本,
最后外链脚本及内联脚本同步执行
执行结果:
html0: 1 9 2
html1: 9 2 1
html2: 3 9 2 1
html3: 3 9 2 1 2
html4: 3 9 2 1 5 6
</p>
</body>
</html>
console.log('\u001b[33mFirst some yellow text\u001b[39m');
使用过的Koa2中间件
Koa-div原理
介绍自己写过的中间件
有没有涉及到Cluster
介绍Pm2
Master挂了的话Pm2怎么处理
如何和MySQL进行通信
React声明周期及自己的理解
如何配置React-Router
路由的动态加载模块
服务端渲染SSR
介绍路由的History
介绍Redux数据流的流程
Redux如何实现多个组件之间的通信,多个组件使用相同状态如何进行管理
多个组件之间如何拆分各自的State,每块小的组件有自己的状态,它们之间还有一些公共的状态需要维护,如何思考这块
使用过的Redux中间件
如何解决跨域的问题
常见Http请求头
移动端适配1px的问题
介绍Flex布局
其他CSS方式设置垂直居中
居中为什么要使用Transform(为什么不使用MarginLeft/Top)
使用过Webpack里面哪些Plugin和Loader
Webpack里面的插件是怎么实现的
Dev-Server是怎么跑起来
项目优化
抽取公共文件是怎么配置的
项目中如何处理安全问题
怎么实现this对象的深拷贝
介绍Redux,主要解决什么问题
文件上传如何做断点续传
表单可以跨域吗
Promise、Async有什么区别
搜索请求如何处理(防抖)
搜索请求中文如何请求
介绍观察者模式
介绍中介者模式
观察者和订阅-发布的区别,各自用在哪里
介绍React优化
介绍Http2.0
通过什么做到并发请求
Hhttp1.1时如何复用Tcp连接
介绍Service Worker
介绍CSS3中Position:sticky
Redux请求中间件如何处理并发
介绍Promise,异常捕获
介绍position属性包括CSS3新增
浏览器事件流向
介绍事件代理以及优缺点
React组件中怎么做事件代理
React组件事件代理的原理
介绍This各种情况
前端怎么控制管理路由
使用路由时出现问题如何解决
React怎么做数据的检查和变化
React-Router怎么实现路由切换
React-Router里的标签和标签有什么区别
标签默认事件禁掉之后做了什么才实现了跳转
React层面的性能优化
整个前端性能提升大致分几类
import { Button } from 'antd',打包的时候只打包button,分模块加载,是怎么做到的
使用import时,Webpack对node_modules里的依赖会做什么
JS异步解决方案的发展历程以及优缺点
Http报文的请求会有几个部分
Cookie放哪里,Cookie能做的事情和存在的价值
Cookie和Token都存放在Header里面,为什么只劫持前者
Cookie和Session有哪些方面的区别
React中Dom结构发生变化后内部经历了哪些变化
React挂载的时候有3个组件,TextComponent、ComposeComponent、DomComponent,区别和关系,Dom结构发生变化时怎么区分Data的变化,怎么更新,更新怎么调度,如果更新的时候还有其他任务存在怎么处理
Key主要是解决哪一类的问题,为什么不建议用索引index(重绘)
Redux中异步的请求怎么处理
Redux中间件是什么东西,接受几个参数(两端的柯里化函数)
柯里化函数两端的参数具体是什么东西
中间件是怎么拿到Store和Action,然后怎么处理
State是怎么注入到组件的,从Reducer到组件经历了什么样的过程
Koa中response.send、Response.rounded、Response.json发生了什么事,浏览器为什么能识别到它是一个json结构或是html
Koa-divparser怎么来解析Request
Webpack整个生命周期,Loader和Plugin有什么区别
介绍AST(Abstract Syntax Tree)抽象语法树
安卓Activity之间数据是怎么传递的
安卓4.0到6.0过程中WebView对JS兼容性的变化
WebView和原生是如何通信
跨域怎么解决,有没有使用过Apache等方案
对Async、Await的理解,内部原理
介绍下Promise,内部实现
清除浮动
定位问题(绝对定位、相对定位等)
从输入URL到页面加载全过程
TCP3次握手
TCP属于哪一层(1 物理层 -> 2 数据链路层 -> 3 网络层(IP)-> 4 传输层(TCP) -> 5 应用层(Http))
Redux的设计**
接入Redux的过程
绑定Cconnect的过程
Cconnect原理
Webpack介绍
== 和 ===的区别,什么情况下用相等==
Bind、Call、Apply的区别
动画的了解
介绍下原型链(解决的是继承问题吗)
对跨域的了解
Linux 754 介绍
介绍冒泡排序,选择排序,冒泡排序如何优化
Transform动画和直接使用Left、Top改变位置有什么优缺点
如何判断链表是否有环
介绍二叉搜索树的特点
介绍暂时性死区
ES6中的Map和原生的对象有什么区别
观察者和发布-订阅的区别
React异步渲染的概念,介绍Time Slicing 和 Suspense
16.X声明周期的改变
16.X中Props改变后在哪个生命周期中处理
介绍纯函数
前端性能优化
PureComponent和FunctionComponent区别
介绍JSX
如何做RN在安卓和iOS端的适配
RN为什么能在原生中绘制成原生组件(bundle.js)
介绍虚拟DOM
如何设计一个localStorage,保证数据的实效性
如何设计Promise.all()
介绍高阶组件
sum(2, 3)实现sum(2)(3)的效果
react性能优化
两个对象如何比较
JS的原型
变量作用域链
call、apply、bind的区别
防抖和节流的区别
介绍各种异步方案
React生命周期
介绍Fiber
前端性能优化
介绍DOM树对比
React中的key的作用
如何设计状态树
介绍CSS,Xsrf
Http缓存控制
项目中如何应用数据结构
Native提供了什么能力给RN
如何做工程上的优化
shouldComponentUpdate是为了解决什么问题
如何解决Props层级过深的问题
前端怎么做单元测试
Webpack生命周期
Webpack打包的整个过程
常用的Plugins
Pm2怎么做进程管理,进程挂掉怎么处理
不用Pm2怎么做进程管理
介绍下浏览器跨域
怎么去解决跨域问题
Jsonp方案需要服务端怎么配合
Ajax发生跨域要设置什么(前端)
加上CORS之后从发起到请求正式成功的过程
Xsrf跨域攻击的安全性问题怎么防范
使用Async会注意哪些东西
Async里面有多个await请求,可以怎么优化(请求是否有依赖)
Promise和Async处理失败的时候有什么区别
Redux在状态管理方面解决了React本身不能解决的问题
Redux有没有做过封装
React生命周期,常用的生命周期
对应的生命周期做什么事
遇到性能问题一般在哪个生命周期里解决
怎么做性能优化(异步加载组件)
写React有哪些细节可以优化
React的事件机制(绑定一个事件到一个组件上)
介绍下事件代理,主要解决什么问题
前端开发中用到哪些设计模式
React/Redux中哪些功能用到了哪些设计模式
JS变量类型分为几种,区别是什么
JS里垃圾回收机制是什么,常用的是哪种,怎么处理的
一般怎么组织CSS(Webpack)
小程序里面开页面最多是多少
React子父组件之间如何传值
Emit事件怎么发,需要引入什么
介绍下React高阶组件,和普通组件有什么区别
一个对象数组,每个子对象包含一个ID和Name,React如何渲染出全部的Name
在哪个生命周期里写
其中有几个Name不存在,通过异步接口获取,如何做
渲染的时候Key给什么值,可以使用Index吗?用ID好还是Index好
Webpack如何配Sass,需要配哪些Loader
配CSS需要哪些Loader
如何配置把JS、CSS、Html单独打包成一个文件
Div垂直水平居中(Flex、绝对定位)
两个元素块,一左一右,中间相距10像素
上下固定,中间滚动布局如何实现
[1, 2, 3, 4, 5]变成[1, 2, 3, a, b, 5]
取数组的最大值(ES5、ES6)
apply和call的区别
ES5和ES6有什么区别
some、every、find、filter、map、forEach有什么区别
上述数组随机取数,每次返回的值都不一样
如何找0-5的随机数,95-99呢
页面上有1万个Button如何绑定事件
如何判断是Button
页面上生成一万个Button,并且绑定事件,如何做(JS原生操作DOM)
循环绑定时的Index是多少,为什么,怎么解决
页面上有一个input,还有一个p标签,改变input后p标签就跟着变化,如何处理
监听input的哪个事件,在什么时候触发
对React看法,有没有遇到一些坑
对闭包的看法,为什么要用闭包
手写数组去重函数
手写数组扁平化函数
介绍下Promise的用途和性质
Promise和Callback有什么区别
React生命周期
两道手写算法题
喜马拉雅
ES6新的特性
介绍Promise
Promise有几个状态
说一下闭包
React的生命周期
ComponentWillReceiveProps的触发条件是什么
React16.3对生命周期的改变
介绍下React的Filber架构
画Filber渲染树
介绍React高阶组件
父子组件之间如何通信
Redux怎么实现属性传递,介绍下原理
React-Router版本号
网站SEO怎么处理
介绍下HTTP状态码
403、301、302是什么
缓存相关的HTTP请求头
介绍HTTPS
HTTPS怎么建立安全通道
前端性能优化(JS原生和React)
用户体验做过什么优化
对PWA有什么了解
对安全有什么了解
介绍下数字签名的原理
前后端通信使用什么方案
RESTful常用的Method
介绍下跨域
Access-Control-Allow-Origin在服务端哪里配置
csrf跨站攻击怎么解决
前端和后端怎么联调
LocalStorage和Cookie有什么区别
CSS选择器有哪些
盒子模型,以及标准情况和IE下的区别
如何实现高度自适应
Prototype和Proto区别
_construct是什么
new是怎么实现的
promise的精髓,以及优缺点
如何实现H5手机端的适配
Rrem、Flex的区别(Root em)
em和px的区别
React声明周期
如何去除url中的#号
Redux状态管理器和变量挂载到Window中有什么区别
Webpack和Gulp的优缺点
如何实现异步加载
如何实现分模块打包(多入口)
前端性能优化(1JS、CSS;2图片;3缓存预加载;4SSR;5多域名加载;6负载均衡)
并发请求资源数上限(6个)
base64为什么能提升性能,缺点
介绍Webp这个图片文件格式
介绍Koa2
Promise如何实现的
异步请求,低版本Fetch如何低版本适配
Ajax如何处理跨域
CORS如何设置
Jsonp为什么不支持Post方法
介绍同源策略
React使用过的一些组件
介绍Immuable
介绍下Redux整个流程原理
介绍原型链
如何继承
介绍JS数据类型,基本数据类型和引用数据类型的区别
Array是Object类型吗
数据类型分别存在哪里
vara={name:"前端开发"};varb=a;a=null那么b输出什么
vara={b:1}存放在哪里
vara={b:{c:1}}存放在哪里
栈和堆的区别
垃圾回收时栈和堆的区别
数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少
栈和堆具体怎么存储
介绍闭包以及闭包为什么没清除
闭包的使用场景
JS怎么实现异步
异步整个执行周期
Promise的三种状态
Async/Await怎么实现
Promise和setTimeout执行先后的区别
JS为什么要区分微任务和宏任务
Promise构造函数是同步还是异步执行,then呢
发布-订阅和观察者模式的区别
JS执行过程中分为哪些阶段
词法作用域和this的区别
平常是怎么做继承
深拷贝和浅拷贝
loadsh深拷贝实现原理
ES6中let块作用域是怎么实现的
React中setState后发生了什么
setState为什么默认是异步
setState什么时候是同步的
为什么3大框架出现以后就出现很多Native(RN)框架(虚拟DOM)
虚拟DOM主要做了什么
虚拟DOM本身是什么(JS对象)
304是什么
打包时Hash码是怎么生成的
随机值存在一样的情况,如何避免
使用Webpack构建时有无做一些自定义操作
Webpack做了什么
a,b两个按钮,点击aba,返回顺序可能是baa,如何保证是aba(Promise.then)
Node接口转发有无做什么优化
Node起服务如何保证稳定性,平缓降级,重启等
RN有没有做热加载
RN遇到的兼容性问题
RN如何实现一个原生的组件
RN混原生和原生混RN有什么不同
什么是单页项目
遇到的复杂业务场景
Promise.all实现原理
介绍Promise的特性,优缺点
介绍Redux
RN的原理,为什么可以同时在安卓和IOS端运行
RN如何调用原生的一些功能
介绍RN的缺点
介绍排序算法和快排原理
堆和栈的区别
介绍闭包
闭包的核心是什么
网络的五层模型
HTTP和HTTPS的区别
HTTPS的加密过程
介绍SSL和TLS
介绍DNS解析
JS的继承方法
介绍垃圾回收
Cookie的引用为了解决什么问题
Cookie和localStorage的区别
如何解决跨域问题
前端性能优化
使用Canvas绘图时如何组织成通用组件
formData和原生的Ajax有什么区别
介绍下表单提交,和FormData有什么关系
介绍Redux接入流程
Rudux和全局管理有什么区别(数据可控、数据响应)
RN和原生通信
介绍MVP怎么组织
介绍异步方案
Promise如何实现Then处理
Koa2中间件原理
常用的中间件
服务端怎么做统一的状态处理
如何对相对路径引用进行优化
Node文件查找优先级
Npm2和Npm3+有什么区别
Knex连接数据库响应回调
介绍异步方案
如何处理异常捕获
项目如何管理模块
前端性能优化
JS继承方案
如何判断一个变量是不是数组
变量a和b,如何交换
事件委托
多个
类数组和数组的区别
dom的类数组如何转成数组
介绍单页面应用和多页面应用
Redux状态树的管理
介绍Localstorage的API
Html语义化的理解
和的区别
对闭包的理解
工程中闭包使用场景
介绍this和原型
使用原型最大的好处
React设计思路
为什么虚拟DOM比真实DOM性能好
React常见的通信方式
Redux整体的工作流程
Redux和全局对象之间的区别
Redux数据回溯设计思路
单例、工厂、观察者项目中实际场景
项目中树的使用场景以及了解
工作收获
React生命周期
React性能优化
添加原生事件不移除为什么会内存泄露
还有哪些地方会内存泄露
setInterval需要注意的点
定时器为什么是不精确的
setTimeout(1)和setTimeout(2)之间的区别
介绍宏任务和微任务
Promise里面和then里面执行有什么区别
介绍pureComponet
介绍Function Component
React数据流
props和state的区别
介绍React context
介绍class和ES5的类以及区别
介绍箭头函数和普通函数的区别
介绍defineProperty方法,什么时候需要用到
for..in 和 object.keys的区别
介绍闭包,使用场景
使用闭包特权函数的使用场景
Get和Post有什么区别
React15/16.x的区别
重新渲染Render会做些什么
哪些方法会触发React重新渲染
state和props触发更新的生命周期分别有什么区别
setState是同步还是异步
对无状态组件的理解
介绍Redux工作流程
介绍ES6的功能
let、const以及var的区别
浅拷贝和深拷贝的区别
介绍箭头函数的this
介绍Promise和then
介绍快速排序
算法:前K个最大的元素
对React看法,它的优缺点
使用过程中遇到的问题,如何解决的
React的理念是什么(拿函数式编程来做页面渲染)
JS是什么范式语言(面向对象还是函数式编程)
Koa原理,为什么要用Koa(Express和Koa对比)
使用的Koa中间件
ES6使用的语法
Promise 和 async/await 和 Callback的区别
Promise有没有解决异步的问题(Promise链是真正强大的地方)
Promise和setTimeout的区别(Event Loop)
进程和线程的区别(一个Node实例就是一个进程,Node是单线程,通过事件循环来实现异步)
介绍下DFS深度优先
介绍下观察者模式
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
#div1{width: 100%;min-height: 50px;background-color: yellow;}
#sp1{width: 20%;height: 100%;display: inline-block;background-color: blue;}
#sp2{width: 50%;height: 100%;display: inline-block;background-color: red;}
</style>
</head>
<body>
<div id="div1">
<span id="sp1">aaa</span>
<span id="sp2">bbb</span>
</div>
</body>
</html>
我的理解:
var http = require('http');
var server = http.createServer();
var log = function (str) {
console.log('##' + Date.now() + '##');
console.log(str);
}
server.on('request', function (req, res) {
// req读入流 res写出流
req.on('data', function (chunk) {
log('server-request-data-' + chunk.toString());
}).on('end', function () {
log('server-request-end');
res.writeContinue();
res.end();
});
}).on('connection', function (socket) {
// socket 双工流
socket.on('data', function (chunk) {
log('server-connection-data-' + chunk.toString());
});
socket.on('end', function () {
log('server-connection-end');
});
// socket.write('server socket');
// socket.end('server socket end');
}).listen(9999, function () {
var opt = {
protocol: 'http:',
hostname: '127.0.0.1',
port: 9999,
method: 'POST',
};
var client = http.request(opt);
client.on('response', function (res) {
// res 可读流
res.on('data', function (chunk) {
log('client-response-data-' + chunk.toString());
});
res.on('end', function () {
log('client-response-end');
});
}).on('socket', function (socket) {
// socket duplux流,可读可写
socket.on('data', function (chunk) {
log('client-socket-data-' + chunk.toString());
});
socket.on('end', function () {
log('client-socket-end');
});
// socket.write('client socket');
// socket.end('client socket end');
});
client.write('client write');
client.end('client write end');
});
##1551610895924##
server-connection-data-POST / HTTP/1.1
Host: 127.0.0.1:9999
Connection: close
Transfer-Encoding: chunked
c
client write
10
client write end
0
##1551610895925##
server-request-data-client write
##1551610895926##
server-request-data-client write end
##1551610895926##
server-request-end
##1551610895931##
client-socket-data-HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Sun, 03 Mar 2019 11:01:35 GMT
Connection: close
Content-Length: 0
##1551610895932##
client-response-end
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket'
}
headers: {
'Expect': '100-continue'
}
接入小程序SDK后,前端swan.js是集成在SDK中的,SDK接入的APP(以下简称“宿主”)默认无需关心SDK上swan.js的更新/运行,但是宿主需要扩展swan对于小程序开发者的API/组件时,则需要为js运行时编写特定逻辑。
目前编写扩展,可以给宿主带来三种能力:
swan.tieba.publishThread()
;<bilibili-video></bilibili-video>
;如果宿主需要在swan上扩展API或是扩展组件的话,则需要进行两步的操作:
// 宿主的extension.js逻辑
module.exports = {
name: 'tieba',
// 扩展API
methods: {
publishThread: function (id) {}
},
// 扩展组件
components: {
'video': {}
},
// 扩展私有的统计逻辑
customLog: function () {}
});
三方写的extension.js
中,可以扩展的有:API/组件/统计逻辑。此js会被编译成如下格式:
define('swan-extension', ['swanx', 'swan', 'boxjs'], function (swanx, swan, boxjs) {
module.exports = {
name: 'tieba',
// 扩展API
methods: {
publishThread: function (id) {}
},
// 扩展组件
components: {
'video': {
template: '<div>video</div>',
depandencies: ['swaninterface'],
attached() {
boxjs.xxxx
}
}
},
// 扩展私有的统计逻辑
customLog: function (swanEventFlow) {
swanEventFlow.onMessage('appShow', () => {
// 宿主打印日志的逻辑
});
}
});
});
在extension.js中,因为需要与宿主客户端进行交互,所以会提供一些开发者所没有的特定接口,编写extension.js的开发人员可以使用这些API,编写特定逻辑。其中,extension.js中可以使用的API如下:
API | 描述 |
---|---|
swanx | 为extension.js开发者提供的扩展API集合对象 |
boxjs | 开发者无法访问的私有能力的接口集合 |
swan | 为小程序开发者提供的全局的swan对象(对应的API列表,可以在官网上找到) |
swanx中的方法:
API | 参数 | 描述 |
---|---|---|
swanx.invoke | scheme:String | 开发者可以调用此API,直接调起自己宿主客户端实现的接口。 |
swanx中scheme的格式如下:
** 扩展API方法 **
extension.js
中methods
对象中的所有字段,会自动被merge到相对应的命名空间下,并在全局对象swan
上提供给小程序开发者,如:'swan.tieba.publishThread()' 。
// 宿主的extension.js逻辑
module.exports = {
name: 'tieba',
// 扩展API
methods: {
publishThread: function (id) {
swan.invoke('');
}
}
};
开发者可以在逻辑中直接编写scheme
,与宿主客户端进行交互。
extension.js
中components
对象中的所有字段,将会自动生成对应的组件,并被加上命名空间为前缀,如上面的video
,会被swan.js
扩展为<bilibili-video>
提供给小程序开发者。
在实现组件之前,请extension.js的编写人员,要了解一下组件的基本知识,
即:原生组件是普通的HTML组件在 各阶段生命周期/事件触发 中调用端能力,进行实现的。所以,如果宿主需要做一个普通的HTML组件,则直接编写普通组件逻辑即可(仅编写前端代码)。如果需要编写NA组件,则需要在客户端进行相应实现,并通过前端swanx.invoke()
进行相应通讯。
module.exports = {
name: 'tieba',
// 扩展组件
components: {
'video': {
template: '<div>video</div>',
depandencies: ['swaninterface'],
attached() {
boxjs.xxxx
}
}
}
}
组件的基本描述与生命周期,请参见san框架的官网描述:san框架
在extension.js
中,开发者可以写一个名为customLog
的函数,swan.js
框架会在启动时调用宿主开发的customLog
函数,并给开发者的扩展代码传入swanEventFlow
事件流对象,该事件流对象支持接口如下:
接口名称 | 作用 | 参数 |
---|---|---|
swanEventFlow.onMessage | 监听某一事件 | type:String, handler:Function, options:Object |
swanEventFlow.delHandler | 移除事件流上的某一个事件监听 | type:String, handler:Function |
swanEventFlow.fireMessage | 在当前事件流上派发事件 |
由上我们可以看出,宿主可以使用swanEventFlow
对象,对小程序的各个生命周期关键事件发生节点进行监听。那么开发者可以具体利用的生命周期都有哪些呢?请参见以下列表:
生命周期名称 | 触发时机 | 携带参数 |
---|---|---|
onAppShow | 小程序展示时(包含当前页面第一次展示/小程序切换到前台/覆盖在该页面上的其他页面销毁) | {query:string, path:string} |
onAppHide | 小程序隐藏时(包含home键切换到后台/被其他页面覆盖) | |
onAppLaunch | 小程序首次进入 | {query:string, path:string} |
onPageLoad | 小程序的某个页面加载 | {slaveId:number, query:string, path:string} |
onPageReady | 小程序的某个页面渲染完成 | |
onPageShow | 小程序的某个页面进行展示 | {slaveId:number, query:string, path:string} |
onPageHide | 小程序的某个页面进行隐藏 |
名词 | 解释 |
---|---|
宿主 | 特指承载百度小程序的第三方宿主应用 |
小程序 | 特指百度小程序 |
Swan-Native | 特指百度小程序端上NA SDK。 |
SwanJS | 特指百度小程序前端JS SDK,又称SwanCore,用于和端上通信的桥梁、小程序API、组件实现的基础。 |
Scheme协议 | 指SwanJS和Swan-Native通信的标准协议 |
端能力 | 特指Swan-Native对SwanJS中某一API的实现 |
lib | 说明 |
---|---|
demo | 小程序的demo示例,具有基本的运行小程序的能力。包含接入小程序需要做的初始化工作 & 小程序能力接口的实现(目前是空实现) |
lib-aopannotation-empty | 启动耗时性能监控用的,小程序的core代码里用到了这个库里的Annocation,这里为了编译通过,仅仅带上这个空工程。真正的耗时逻辑是通过gradle插件实现,这里没用到,仅用于手百上,外部不需要处理。 |
lib-ar | 小程序里提供了AR相机的能力,直接集成了AR的jar包,如果不需要可以自行去掉。 |
lib-bdwebview | 小程序使用的WebView组件,目前是通过Sailor SDK直接桥接的系统WebView。(注意:系统WebView在Android5.0一下有问题,小程序需要从Adnroid5.0及以上开始支持)。 |
lib-browser-base | 小程序内部浏览器框架的分类,目前为了和手百保持一致,直接扣取的代码,第三方不用关心,直接集成即可。 |
lib-event-bus | 小程序内部使用到了RxJava和对RxJava封装的RxBus,这个库作为基础提供。 |
lib-multiprocess | 如果使用手百的UBC(天幕)打点系统(下边的lib-ubc lib),才需要这个库,进行跨进程打点用的。 |
lib-ng-aiapps | 小程序的core代码,这个是小程序的主lib,其他lib都是为他服务的。 |
lib-ng-aiapps-download | 小程序sdk从Server拉取SwanJs和小程序包的下载能力,直接接入手百的APS后台,内部直接集成。 |
lib-ng-aiapps-menu | 小程序内部使用的菜单View。 |
lib-ng-aiapps-ubc-empty | 如果不使用百度的UBC打点,需要依赖这个库(不用依赖lib-ubc),所有的打点会空实现。 |
lib-no-proguard | 小程序里有一部分代码不能混淆,是通过实现了这个库里的NoProGuard接口完成的,在打包的时候需要配置混淆规则,详见demo/proguard-rules.pro。 |
lib-process-ipc | 由于小程序是运行在独立进程的,需要多进程间通信,这个lib是用来多进程通信的。 |
lib-runtime | 小程序SDK的运行时环境,需要在app启动的时候调用,详情参考 初始化说明。 |
lib-slide | 小程序内部使用到了侧滑返回页面的能力,这个库提供侧滑功能。 |
lib-ubc | 如果要使用手百的UBC打点,将统计打到天幕平台,需要使用这个lib,这个lib需要依赖lib-multiprocess(lib-ubc 和 lib-ng-aiapps-ubc-empty 二选一,如果需要统计就用lib-ubc,否则就用lib-ng-aiapps-ubc-empty)。 |
lib-united-scheme-core | 小程序内部的通信是使用Scheme协议完成的,这个是小程序内部的通信框架。 |
lib-v4-fragment | 下程序使用到了fragment,但是不同版本的v4包有bug,这里使用了自带的fragment,而不是用系统的v4包提供的。 |
lib-websocket | 小程序里的webSocket能力 |
库 | 说明 |
---|---|
com.baidu.searchbox:http:1.0.9 | 百度封装的http库,在手百的maven仓库。 |
com.squareup.okhttp3:okhttp:3.11.0 | okhttp3.11.0 在手百的maven仓库。 |
com.baidu.searchbox:httpdns:1.0.1 | 百度的HttpDns 在手百的maven仓库。 |
com.airbnb.android:lottie:2.3.1 | lottie2.3.1 |
io.reactivex:rxjava:1.2.3 | rxjava 1.2.3 |
io.reactivex:rxandroid:1.2.0 | rxandroid 1.2.0 |
com.google.code.gson:gson:2.8.0 | gson |
com.android.support:support-v4:26.1.0 com.android.support:appcompat-v7:26.1.0 com.android.support:recyclerview-v7:26.1.0 com.android.support:support-annotations:26.1.0 com.facebook.fresco:fresco:1.2.0 com.baidu.android.common:galaxy-full:2.1.0 com.baidu.pyramid:pyramid-annotation:0.1.2 |
小程序的金字塔注入插件,用于IOC的 在手百的maven仓库。 |
具体配置参考
settings.gradle
调起客户端扫码界面,扫码成功后返回对应的结果。
小程序调用 swan.scanCode
API时,应该跳转到二维码扫描界面,二维码扫描结果应该通过返回给调起的小程序。
IAiAppScanCodeIoc.java
中/**
* 调起二维码扫描界面
*
* @param context 上下文
* @param callback 扫码的结果回调
*/
void scanCode(Context context, IScanResultCallback callback);
IScanResultCallback说明
回调定义:
public interface IScanResultCallback {
/**
* 扫码结果
*
* @param data 结果数据
* @param resultCode 结果码
*/
void onResult(JSONObject data, int resultCode);
}
data返回结构:
{"codeType":"QR_CODE","encoding":"GBK","result":"https://v.yunpub.cn/books/zngm"}
参数名 | 类型 | 说明 |
---|---|---|
codeType | String | 扫码类型(二维码类型:QR_CODE) |
encoding | String | 编码方式(GBK,UTF-8等) |
result | String | 扫码的内容 |
/**
* 通过IOC的方式提前初始化定位服务,减少在使用时候的耗时
*/
void preInitLocation();
/**
* 通过IOC的方式使用app中的定位服务
* @param coorType 经纬度的坐标类型,如文档所示,必须支持wgs84和gcj02两种坐标类型
* @param useCache 是否使用缓存
* @param openGPS 是否使用GPS
* @param listener 定位结果的回调
*/
public void requestLocation(String coorType, boolean useCache, boolean openGPS, LocationListener listener);
/**
* 定位IOC的回调
*/
public interface LocationListener {
/**
* 定位成功回调
* @param result 定位结果,内部包含文档上的返回值
*/
public void onSuccess(LocationResult result);
/**
* 定位失败回调
* @param errCode 错误码,第三方自定义
*/
public void onFailed(int errCode);
}
/**
* 获取地理位置信息
*
* @param context 上下文
* @return 地理位置信息(Observable异步)
*/
@Nullable
Observable<AiAppsLocationInfo> getLocationInfo(Context context);
无,完全有第三方自己实现,没有固定格式和规范
小程序菜单点击分享,调起宿主分享功能,此功能需要宿主实现,调用宿主的分享面板
SwanJS 传递分享参数到 Swan-Native 后,Swan-Native 通过接口调起宿主分享功能
标准协议必要字段:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
title | String | 否 | 分享标题 |
content | String | 否 | 分享内容 |
imageUrl | String | 否 | 分享图标 |
path | String | 否 | 页面 path ,必须是以 / 开头的完整路径 |
需要实现以下接口,完成分享功能
当前接口非终版接口,后面会标准化
/**
* 调起分享
*
* @param title 分享标题
* @param content 分享内容
* @param imageUrl 分享图标
* @param path 页面回流 path
* @param ext 其它信息
* @param listener 分享回调
*/
void share(String title, String content, String imageUrl, String path, String ext, OnShareResultListener listener);
图片功能主要分为图片预览
和图片选择
。
图片预览:查看图片
图片选择:从本地相册选择图片或使用相机拍照。
小程序调用 swan.previewImage
API时,小程序框架传入图片URL信息尝试调起接入方图片浏览页面。
小程序调用 swan.chooseImage
API时,小程序传入选择图片基本参数尝试调起接入方图片选择页面。用户完成图片选择后图片选择页面应该将选择的图片信息返回给调起小程序
IAiAppImageIoc.java
中/**
* 打开图片浏览器
*
* @param context 小程序上下文
* @param pictureParams 图片信息
*/
public void launchPictureBrowser(Context context, JSONObject pictureParams);
pictureParams参数格式
{"url":["https:\/\/image.jpg","https:\/\/preview-image-2.png"],"index":"0"}
pictureParams参数说明
参数名 | 类型 | 说明 |
---|---|---|
url | String[] | 图片url数组 |
index | String | 当前显示图片的链接 |
/**
* 选择图片
*
* @param context 小程序上下文
* @param maxCount 要选择的图片最大张数
* @param callback 结果回调
*/
public void chooseImage(Context context, int maxCount, ChooseImageAction.OnImageChooseResultCallback callback);
OnImageChooseResultCallback说明
/**
* 图片选择的结果回调
*/
public interface OnImageChooseResultCallback {
/**
* 结果回调
* @param success 是否成功
* @param msg 附带信息
* @param paths 图片的路径信息
*/
public void onResult(boolean success, String msg, List<String> paths);
}
参数名 | 类型 | 说明 |
---|---|---|
msg | String | 选择图片页面携带回的信息,主要是图片选择失败时,失败原因等信息 |
paths | List | 用户所选择的本地图片路径 |
地图功能包含(具体参考下文的使用文档):
推荐使用百度地图的 SDK 进行接入,有详细的实现代码,接入成本较低;其他地图SDK需要重新实现下文 接口设计 中的接口。
地图的使用和开发参考小程序文档:
接入地图能力,需要实现的接口,目前有16个,默认是使用百度地图的SDK实现。如果第三方使用百度地图SDK,可以直接使用代码,成本较小。
所有的接口都在 IAiAppMapIoc.java 中。
创建一个地图View。
/**
* 创建地图
* @param context 小程序宿主activity context
* @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 小程序环境,包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean create(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp) {
return false;
}
当地图上的参数发生变化时,如大小,位置,新增绘制等等,只要文档上出现的参数发生变化,就会调用这个接口。
/**
* 更新地图
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
删除地图组件
/**
* 删除地图
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
移动指定的 Marker 标记
/**
* 移动指定的Marker标记,可以设置动画时间
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
定位到当前所在的位置
/**
* 移动到定位位置,showLocation字段必须为true才生效
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean moveToLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
缩放地图视野,以包含指定的所有经纬度
/**
* 缩放视野包含给定的所有点坐标
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean includePoints(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
获取当前地图中的缩放比例
/**
* 获取当前的缩放比例
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getScale(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
获取当前地图view视野矩形的经纬度坐标(左上右下两个点的经纬度)
/**
* 获取当前的视野矩形坐标
* @param context 小程序宿主activity context
* @param @param entity scheme 实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme 结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getRegion(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
获取地图中心的经纬度坐标
/**
* 获取地图中心点坐标
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getCenterLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
当前页面 resume 的时候,回调的接口,通知地图
/**
* 在页面resume的时候通知地图
* @param manager 指定要resume的webView页面,可以通过这个获取到应该要通知的地图view
*/
public void resume(AiAppsSlaveManager manager);
当前 webView 页面 pause 的时候,通知地图
/**
* 在页面pause的时候通知地图
* @param manager 定要pause的webView页面,可以通过这个获取到应该要通知的地图view
*/
public void pause(AiAppsSlaveManager manager);
当前 webView 页面销毁的时候,通知地图
/**
* 在页面销毁的时候通知地图
* @param manager 通知指定slave上的地图,又manager可以找到指定的地图view
*/
public void release(AiAppsSlaveManager manager);
在新的页面打开指定的经纬度在地图上显示
/**
* 打开指定位置
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean openLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
打开新的页面,选择一个地理位置,返回选择位置的经纬度
/**
* 打开选择位置的页面
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false */
public boolean chooseLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
打开步行导航,步行导航只需要终点坐标即可,其实坐标就是当前位置
/**
* 打开步行导航
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean openWalkNavigation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
判断当前最上层的 Fragemnt 是不是正在导航
/**
* 顶层目前是不是步行导航的fragment
* 因为百度地图的步行导航目前只是支持actiivty,退出的时候会把真个actiivty finish,导致小程序本身也被finish了,
* 所以需要在导航退出的时候特殊处理
* @return 如果最上层是导航的fragment返回true
*/
public boolean isTopWalkNavFragment();
以创建地图为例,实现参考
public boolean create(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp) {
// 1.解析参数
MapModel model = getModel(entity);
if (model == null) {
AiAppsLog.e("map", "parse error, model is null");
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_PARAMS_PARSE_FAIL);
return false;
}
// 2.如果slaveId为空,先使用当前的webview序列号; 需要前端支持传递slaveId
if (TextUtils.isEmpty(model.slaveId)) {
String webViewId = AiAppsUtils.getTopWebViewId();
if (!TextUtils.isEmpty(webViewId)) {
model.slaveId = webViewId;
}
AiAppsLog.w("map", "webView id is empty, use current webView");
}
// 3.从webView的id获取对应的webView
WebView slaveWv = getBdWebView(entity, model.slaveId);
if (slaveWv == null) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_PARAMS_PARSE_FAIL);
AiAppsLog.e("map", "can not find weiView by id " + model.slaveId);
return false;
}
// 5.根据执行情况返回
JSONObject params = new JSONObject();
if (!doAction(context, model, slaveWv, aiApp, params, entity, handler)) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_EXECUTE_FAIL);
AiAppsLog.e("map", "doAction fail ");
return false;
}
callBackWithParams(entity, handler, params.length() <= 0 ? null : params);
return true;
}
// 真正执行具体逻辑的操作
protected boolean doAction(Context context, MapModel model, WebView webView, AiApp aiApp, JSONObject params,
UnitedSchemeEntity entity, CallbackHandler handler) {
AiAppsLog.i("map", "MapCreateAction start");
// 1.必要性校验
if (webView == null || mapModel == null || !mapModel.isValid()) {
AiAppsLog.e(MapUtils.MODEL_TAG, "model data is invalid");
return false;
}
// 2.获取webView对应的manager
AiAppsWebViewManager manager = AiAppsController.getInstance().getWebViewManager(mapModel.slaveId);
if (manager == null || !(manager instanceof AiAppsSlaveManager)) {
return false;
}
// 3.将组件扔给当前salve中的helper管理
final MapViewHelper helper = getMapViewHelper((AiAppsSlaveManager) manager);
MapViewItem item = helper.find(mapModel.id);
// 已经存在,直接返回
if (item != null) {
return false;
}
// 4.新创建一个地图组件
item = MapViewItem.makeOne(context, mapModel);
if (item == null) {
return false;
}
// 5.嵌套CoverView,并附着在ViewTree上
AiAppsNAViewContainer container = new AiAppsNAViewContainer(webView.getContext());
boolean insert = container.insertView(item.mapView, mapModel);
if (!insert) {
return false;
}
// 6.保存组件
if (!helper.insert(item)) {
return false;
}
// 7.根据传入的属性初始化
MapViewInitHelper.initMapView(context, item, mapModel, helper);
return true;
}
/**
* 从发送过来的信息里组装需要的model
*
* @param entity scheme发送过来的信息载体
*
* @return MapModel对象,代表这个组件的所有属性
*/
protected MapModel getModel(UnitedSchemeEntity entity) {
if (entity == null) {
return null;
}
MapModel model = null;
Map<String, String> params = entity.getParams();
if (params == null || params.isEmpty()) {
AiAppsLog.e("map", "entity get Params is empty");
return null;
}
String jsonModel = params.get("data");
if(jsonModel == null){
AiAppsLog.e("map", "params string is empty");
return null;
}
try {
JSONObject json = new JSONObject(jsonModel);
model = new MapModel();
model.parseFromJson(json);
} catch (JSONException e) {
e.printStackTrace();
AiAppsLog.e("map", "params json parse error");
}
return model;
}
/**
* 统一包装返回msg
*
* @param resultCode 返回码
*
* @return msg
*/
protected JSONObject wrapResult(int resultCode) {
return UnitedSchemeUtility.wrapCallbackParams(resultCode);
}
/**
* Scheme的结果回调
*
* @param entity scheme实体
* @param handler 回调handler
* @param json 参数数据
*/
protected void callBackWithParams(UnitedSchemeEntity entity, CallbackHandler handler, JSONObject json) {
entity.result = UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
/**
* 获取指定的WebVIew
* @param entity 参数实体
* @param slaveId 当前webView的slaveId
* @return 返回找到的webView,找不到返回null
*/
protected WebView getBdWebView(UnitedSchemeEntity entity, String slaveId) {
WebView ngWebView = AiAppsUtils.getBdWebViewBySlaveId(slaveId);
if (ngWebView == null) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_EXECUTE_FAIL);
return null;
}
return ngWebView;
}
参考代码只提供大概框架,具体细节未提供。
地址、发票功能指 swan.chooseAddress
和 swan.chooseInvoiceTitle
能力以及和宿主配套的地址、发票管理中心,管理中心一般包含添加、删除、编辑等能力
使用示例:外卖小程序,填写配送地址时,可以调用 swan.chooseAddress
能力直接选择宿主管理的地址,获取信息后直接填写相关信息
小程序调用 swan.chooseInvoiceTitle
API时,会跳转到宿主发票管理中心,选择对应发票后返回发票信息到调起的小程序。
小程序调用 swan.chooseInvoiceTitle -> SwanJS -> Scheme -> 端能力 -> 管理中心 -> 选择后携带信息 -> 端能力 -> Scheme -> SwanJS -> 小程序
当前非最终版接口
/**
* 选择发票信息
*
* @return 发票,里面包含详细字段定义
*/
Invoice getInvoiceTitle();
Invoice对象说明:
参数名 | 类型 | 说明 |
---|---|---|
type | String | 抬头类型(0:单位,1:个人) |
title | String | 抬头名称 |
taxNumber | String | 抬头税号 |
companyAddress | String | 单位地址 |
telephone | String | 手机号码 |
bankName | String | 银行名称 |
bankAccount | String | 银行账号 |
当前非最终版接口
/**
* 获取地址
*
* @return 地址信息
*/
Address getAddress();
Address对象说明:
参数名 | 类型 | 说明 |
---|---|---|
userName | String | 收货人姓名 |
postalCode | String | 邮编 |
provinceName | String | 国标收货地址第一级地址 |
cityName | String | 国标收货地址第二级地址 |
countyName | String | 国标收货地址第三级地址 |
detailInfo | String | 详细收货地址信息 |
telNumber | String | 收货人手机号码 |
夜间模式指小程序夜间模式功能,主要需要实现和宿主夜间模式的状态联动,同时需要宿主维护夜间模式状态
小程序夜间模式使用蒙层压黑的方式实现
小程序运行与独立的进程,跨进程状态实现通过小程序内置的CS通信机制(Messenger实现),涉及AiAppsMessengerService
和AiAppsMessengerClient
主进程 -> 小程序进程
在宿主夜间模式发生变化时,调用:
AiAppsMessengerService service = AiAppsMessengerService.getServiceObject();
if (service != null) {
service.sendMessageToAllClients(AiAppsMessengerService.MSG_TYPE_SC_NIGHT_MODE_CHANGED);
}
同时需要实现以下接口:
当前接口和百度App夜间模式实现原理有关,后续应该会改造成设置状态、获取状态两个接口
设置夜间模式开关状态,当前功能和 forceSyncNightModeState(boolean enable)
,功能重复,后续会整合
/**
* 设置夜间模式开关状态
*
* @param enable true/false
*/
void setNightModeSwitcherState(boolean enable);
主进程和小程序进程间,夜间模式状态强制同步,通过 Messenger 实现
/**
* 强制同步夜间模式状态
*
* @param enable true/false
*/
void forceSyncNightModeState(boolean enable);
获取夜间模式状态
/**
* 获取夜间模式开关状态
*
* @return boolean
*/
boolean getNightModeSwitcherState();
此接口和百度App夜间模式机制实现有关,后续标准接口时会去除
/**
* 订阅夜间模式事件
*
* @param tag tag
* @param nightModeChangeListener 监听器
*/
void subscribeNightModeChangeEvent(Object tag, AiAppNightModeChangeListener nightModeChangeListener);
此接口和百度App夜间模式机制实现有关,后续标准接口时会去除
/**
* 取消夜间模式状态订阅
*
* @param tag tag
*/
void unsubscribeNightModeChangedEvent(Object tag);
暂时未确定开源方式,后续补充
IAiAppPluginIoc.java
中在打开文档前,先检查打开文档的插件是否可用
/**
* 阅读类插件是否可用
*
* @param mimeType 文件的mimeType
*/
boolean isReadPluginAvailable(Context context, String mimeType);
调起打开文档的插件展示文档
/**
* 处理文件打开
*
* @param activity activity
* @param path 文件对应的uri
* @param mimeType 文件的mimeType
*/
void openDocument(Activity activity, Uri path, String mimeType);
@Override
public void openDocument(final Activity activity, Uri path, String mimeType) {
Intent intent = new Intent(activity, PluginPaperViewerActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(path, mimeType);
intent.putExtra(PluginPaperViewerActivity.ENABLE_FALLBACK_FINISH_KEY, true);
// 不要新增加task
activity.startActivity(intent);
}
IAiAppFeedbackIoc.java
中。调起反馈
/**
* 调启反馈页面
*
* @param params 反馈参数
* @param callback 反馈结果
*/
void feedback(Bundle params, OnFeedbackResultCallback callback);
/**
* 反馈结果回调
*/
interface OnFeedbackResultCallback {
/**
* 反馈的结果
*
* @param data 反馈结果数据
*/
void onResult(String data);
}
小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是swan.createLivePlayerContext
,通过这个接口创建一个LivePlayerContext
对象,后续对直播组件的操作均可通过该对象完成。
对应的组件是<live-player/>
,支持的操作如下
播放:
LivePlayerContext.play()
停止:
LivePlayerContext.stop()
静音:
LivePlayerContext.mute()
暂停:
LivePlayerContext.pause()
恢复:
LivePlayerContext.resume()
进入全屏:
LivePlayerContext.requestFullScreen(Object object)
退出全屏:
LivePlayerContext.exitFullScreen()
开发者通过swan.createLivePlayerContext(string id)
创建直播对象,参数 id 是<live-player/>
标签的 id。创建的同时请求绑定相应的直播组件,随后在客户端创建一个AiAppsLivePlayer
对象。
涉及到的端能力:LivePlayerAction
接口
public interface IAiAppLiveIoc {
/**
* 打开直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 静音直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean mute(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 全屏直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean setFullScreen(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 继续直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean resume(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
小程序提供了背景音频播放能力,通过swan.getBackgroundAudioManager()
创建BackgroundAudioManager
对象,后续对背景音频的操作均是通过此接口执行。
**注:**背景音频是小程序全局唯一的控件,即在任何一个小程序中控制都会影响到其他小程序的使用。
支持以下操作:
播放:
BackgroundAudioManager.play()
暂停。暂停后的音频再播放会从暂停处开始播放:
BackgroundAudioManager.pause()
停止。停止后的音频再播放会从头开始播放:
InnerAudioContext.stop()
跳转到指定位置:
BackgroundAudioManager.seek(number position)
监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.onCanplay(function callback)
监听音频播放事件:
InnerAudioContext.onPlay(function callback)
监听音频暂停事件:
InnerAudioContext.onPause(function callback)
监听音频停止事件:
InnerAudioContext.onStop(function callback)
监听音频自然播放至结束的事件:
InnerAudioContext.onEnded(function callback)
监听音频播放进度更新事件:
InnerAudioContext.onTimeUpdate(function callback)
监听音频播放错误事件:
InnerAudioContext.onError(function callback)
监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.onWaiting(function callback)
监听音频进行跳转操作的事件
InnerAudioContext.onSeeking(function callback)
监听音频完成跳转操作的事件
InnerAudioContext.onSeeked(function callback)
开发者通过调用通过swan.getBackgroundAudioManager()
创建BackgroundAudioManager
对象。通过swan/backgroundAudio/open
端能力在客户端创建音频播放器对象,后续通过update接口更细你参数,对播放器的播放、暂停、定位时间点等常用操作均通过BackgroundAudioManager
完成。
涉及到的端能力:AudioPlayerAction
目前百度智能小程序底层是通过 Android 系统的 MediaPlayer 实现音频播放,第三方可以根据自己的需求自行替换。
接口如下:
public interface IAiAppAudioIoc {
/**
* 打开音频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新音频参数
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 获取参数值
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean getParamSync(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
智能小程序提供了视频播放能力,通过实现 video 抽象接口就可以轻松让您的小程序拥有定制化的视频播放器,让小程序的的产品体验及产品功能更加完善。
开发者通过swan.createVideoContext
来创建一个播放器实例,后续有关播放器的操作均在此对象上完成。
对应的组件是
<video/>
支持的操作如下:
播放视频:
VideoContext.play()
暂停视频:
VideoContext.pause()
停止视频:
VideoContext.stop()
跳转到指定位置:
VideoContext.seek(number position)
发送弹幕:
VideoContext.sendDanmu(Object data)
进入全屏:
VideoContext.requestFullScreen(Object object)
退出全屏:
VideoContext.exitFullScreen()
小程序调用swan.createVideoContext
时,swan-core 会通知客户端创建 MediaPlayer 对象。通过swan/video/open
接口传入初始化参数,后续可以通过 update 接口更新参数。对播放器的播放、暂停、拖拽等常用操作均通过 videoContext 完成。
涉及到的端能力:VideoOpenAction
以下是需要实现的基础播放器接口
public interface IAiAppVideoIoc {
/**
* 打开视频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放视频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 全屏
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean setFullScreen(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 设置弹幕
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean sendDanmu(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 手机back键被按下
* @return 是否处理back键
*/
public boolean onKeyBack();
}
小程序提供了通用音频播放能力,通过swan.createInnerAudioContext()
创建audioContext
对象,后续对 audio 的操作均是通过此接口执行。
对应的组件是
<audio/>
支持以下操作:
播放:
InnerAudioContext.play()
暂停。暂停后的音频再播放会从暂停处开始播放:
InnerAudioContext.pause()
停止。停止后的音频再播放会从头开始播放:
InnerAudioContext.stop()
跳转到指定位置:
InnerAudioContext.seek(number position)
销毁当前实例:
InnerAudioContext.destroy()
监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.onCanplay(function callback)
取消监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.offCanplay(function callback)
监听音频播放事件:
InnerAudioContext.onPlay(function callback)
取消监听音频播放事件:
InnerAudioContext.offPlay(function callback)
监听音频暂停事件:
InnerAudioContext.onPause(function callback)
取消监听音频暂停事件:
InnerAudioContext.offPause(function callback)
监听音频停止事件:
InnerAudioContext.onStop(function callback)
取消监听音频停止事件:
InnerAudioContext.offStop(function callback)
监听音频自然播放至结束的事件:
InnerAudioContext.onEnded(function callback)
取消监听音频自然播放至结束的事件:
InnerAudioContext.offEnded(function callback)
监听音频播放进度更新事件:
InnerAudioContext.onTimeUpdate(function callback)
取消监听音频播放进度更新事件:
InnerAudioContext.offTimeUpdate(function callback)
监听音频播放错误事件:
InnerAudioContext.onError(function callback)
取消监听音频播放错误事件:
InnerAudioContext.offError(function callback)
监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.onWaiting(function callback)
取消监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.offWaiting(function callback)
监听音频进行跳转操作的事件
InnerAudioContext.onSeeking(function callback)
取消监听音频进行跳转操作的事件
InnerAudioContext.offSeeking(function callback)
监听音频完成跳转操作的事件
InnerAudioContext.onSeeked(function callback)
取消监听音频完成跳转操作的事件
InnerAudioContext.offSeeked(function callback)
开发者通过调用通过swan.createInnerAudioContext()
创建audioContext
对象,并传入 id 绑定相应的audio
标签。通过swan/audio/open
端能力在客户端创建 MediaPlayer 对象,后续通过 update 接口更新参数,对播放器的播放、暂停、定位时间点等常用操作均通过audioContext
完成。
涉及到的端能力:AudioPlayerAction
目前百度智能小程序底层是通过Android系统的MediaPlayer实现音频播放,第三方可以根据自己的需求自行替换。
接口如下:
public interface IAiAppAudioIoc {
/**
* 打开音频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新音频参数
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 继续
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean resume(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
/aiapps/lib-ng-aiapps/src/main/java/com/baidu/searchbox/ng/ai/apps/ioc/interfaces
里),都必须将端上的处理结果返回或通知到JS,否则小程序的开发者无法收到回调,逻辑无法继续。同步处理:在完成相应的逻辑之后,直接调用以下代码,告诉前端JS的处理结果,又叫一级回调
// 处理成功,使用UnitedSchemeStatusCode.ERR_OK
// handler中保存有JS的回调函数,会自动使用webView的方法回调JS函数
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
// 处理失败,使用UnitedSchemeStatusCode.ERR_EXECUTE_FAIL
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_EXECUTE_FAIL));
json 是传给JS的参数,第三方自己根据情况填充,没有可以不用传
异步处理:这个操作需要耗时,所以得处理完毕之后,在主动通知JS。
必须先告诉前端JS,已经收到它的命令,通过:
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
通过这种 成功回调 的方式,告诉JS,端上已经接收到命令,开始执行耗时任务
同样的,如果还没开始异步任务,刚上来就失败了,需要通过告诉前端JS失败了,无法开始异步任务
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_EXECUTE_FAIL));
第二步才开始执行异步任务,此时在接口参数 UnitedSchemeEntity entity 中会有个 cb=xxx 的参数,这个 cb 就是异步回调,又叫二级回调
Map<String, String> params = entity.getParams();
String jsonModel = params.get("data");
JSONObject json = new JSONObject(jsonModel);
callback = json.optString(CB);
这个callback就是JS中的回调函数,专门用于端上处理完异步耗时任务之后,主动通知JS的接收函数。
同步回调(又叫一级回调):
void remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
删除一个 View,是一个同步操作,不耗时,所以直接进行删除操作,操作完毕之后直接通知 JS 结果即可
void remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp){
// 执行删除操作
MapViewManager.get().remove(webView, model);
// 将删除结果返回给前端 json是参数,删除成功UnitedSchemeStatusCode.ERR_OK,失败UnitedSchemeStatusCode.ERR_EXECUTE_FAIL
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
异步回调(又叫二级回调)
void translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp)
动过程需要有动画,移动结束之后要通知 JS,移动结束了,肯定是耗时操作,需要用异步回调。
```
void translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp){
AiAppsWebViewManager manager = AiAppsController.getInstance().getWebViewManager(model.slaveId);
if (manager == null || !(manager instanceof AiAppsSlaveManager)) {
return false;
}
Map<String, String> params = entity.getParams();
String jsonModel = params.get("data");
JSONObject json = new JSONObject(jsonModel);
callback = json.optString(CB);
MapViewHelper helper = MapViewManager.get().getMapViewHelper((AiAppsSlaveManager) manager);
MapViewItem item = helper.find(model.id);
dotTanslateMarker(model, item, handler)
// 校验没问题,先一级回调,告诉前端,本次能力调用成功,接下进行耗时操作
return UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
void dotTanslateMarker(TranslateMarkerModel model, final MapViewItem item, final CallbackHandler handler) {
if (!model.isValid()) {
return false;
}
final LatLng newLatLng = new LatLng(model.coordinate.latitude, model.coordinate.longitude);
final List<MarkerViewItem> markerViewItems = item.getMarkerViewItem(model.markId);
if (markerViewItems != null) {
markerViewItem.translateMarkerWithAnimation(item, newLatLng, model.duration,
new MarkerViewItem.AnimationEndCallback() {
@Override
public void onAnimationEnd() {
if (!TextUtils.isEmpty(mCallback)) {
// 二级回调,通知JS 动画结束
handler.handleSchemeDispatchCallback(mCallback, "");
}
}
});
}
}
```
小程序登录功能(调用宿主登录),和宿主强相关
需要宿主维护自己的账号体系
标准接口,后续提供
在符合法务要求的情况下赋予小程序调用必要功能的权限,比如用户信息、直播、地理位置等
敏感功能权限风控,可以随时由用户或者云端平台撤回授权
用户的敏感信息都从开放平台统一输出
暂无,后续提供
暂无,后续提供
暂无
在小程序正式开源前,会随时更新,不保证时效性,当前为百度内部接入流程
http://icode.baidu.com/repos/baidu/searchbox-android/aiapps/tree/master
将配置好依赖的库,参考根目录的settigns.gradle build.gradle demo/build.gradle
需要使用apply plugin: 'com.baidu.pyramid.di'在application的build.gradle
在 App 的application
中初始化用到了代码
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AppRuntimeInit.onApplicationattachBaseContext(this);
// 如果pyramid插件失效(编译系统不支持transForm时,pyramid会失效),使用手动注入
AiAppImplInject.inject();
// 一下是使用了lib-ubc库的时候需要做的初始化
Initer.onApplicationattachBaseContext(this);
Initer.setIPCReporter(new IPCReporterImpl());
if (AppProcessManager.isServerProcess()) {
UBCIPCManager.addUBCRemoteService();
}
}
@Override
public void onCreate() {
super.onCreate();
// 初始化系统webView
WebViewFactory.initOnAppStart(this, true, false);
NgWebViewInitHelper.getInstance().initBWebkit();
// 初始化Fresco
Fresco.initialize(this);
// 应用启动后,需要在合适时机,执行此方法去下载
DynamicDownloadInit.start();
}
实现com.baidu.searchbox.ng.ai.apps.ioc.interfaces
包小所有的接口(目前需要参考百度App的实现方式),demo 中是默认实现。
和 Server 交互的需要有公参,内部可以参考 URLConfig.java
这个类
每个接入方使用的预置SwanJS都不一样(目前在 /lib-ng-aiapps/src/main/assets/aiapps
文件夹下),需要找 @侯禹 @任仲桦
后端的开放平台,授权相关 需要申请 host_api_key
,找 @刘青
理论上就接入完毕了,如有问题直接找 @康森(Android) @刘青(Server) @侯禹(FE)
#### 功能说明
功能介绍
#### 原理
实现的原理说明
#### 实现
具体实现,包含接口说明等
#### 调试
调试方法,确保功能的正确性
功能说明:调起客户端扫码界面,扫码成功后返回对应的结果。
原理:小程序调用 swan.scanCode API时,应该跳转到二维码扫描界面,二维码扫描结果应该通过返回给调起的小程序。
接口设计:
/**
* 调起二维码扫描界面
*
* @param context 上下文
* @param callback 扫码的结果回调
*/
void scanCode(Context context, IScanResultCallback callback);
回调定义:
public interface IScanResultCallback {
/**
* 扫码结果
*
* @param data 结果数据
* @param resultCode 结果码
*/
void onResult(JSONObject data, int resultCode);
}
data返回结构:
{"codeType":"QR_CODE","encoding":"GBK","result":"https:\/\/v.yunpub.cn\/books\/zngm"}
参数名 | 类型 | 说明 |
---|---|---|
codeType | String | 扫码类型(二维码类型:QR_CODE) |
encoding | String | 编码方式(GBK,UTF-8等) |
result | String | 扫码的内容 |
功能说明:
/**
* 通过IOC的方式提前初始化定位服务,减少在使用时候的耗时
*/
void preInitLocation();
```
* 执行定位操作
/**
* 通过IOC的方式使用app中的定位服务
* @param coorType 经纬度的坐标类型,如文档所示,必须支持wgs84和gcj02两种坐标类型
* @param useCache 是否使用缓存
* @param openGPS 是否使用GPS
* @param listener 定位结果的回调
*/
public void requestLocation(String coorType, boolean useCache, boolean openGPS, LocationListener listener);
/**
* 定位IOC的回调
/
public interface LocationListener {
/*
* 定位成功回调
* @param result 定位结果,内部包含文档上的返回值
/
public void onSuccess(LocationResult result);
/*
* 定位失败回调
* @param errCode 错误码,第三方自定义
*/
public void onFailed(int errCode);
}
```
/**
* 获取地理位置信息
*
* @param context 上下文
* @return 地理位置信息(Observable异步)
*/
@Nullable
Observable<AiAppsLocationInfo> getLocationInfo(Context context);
```
**参考实现**:无,完全有第三方自己实现,没有固定格式和规范。
### 分享
**功能说明**:小程序菜单点击分享,调起宿主分享功能,此功能需要宿主实现,调用宿主的分享面板
**原理**:SwanJS传递分享参数到Swan-Native后,Swan-Native通过接口调起宿主分享功能
**标准协议必要字段**:
|参数名|类型|必填|说明|
|---|---|---|---|
|title| String| 否| 分享标题|
|content |String| 否| 分享内容|
|imageUrl| String| 否| 分享图标|
|path| String| 否| 页面 path ,必须是以 / 开头的完整路径|
#### 实现
需要实现以下接口,完成分享功能。
> 当前接口非终版接口,后面会标准化。
/**
### 图片
**功能说明**:图片功能主要分为图片预览和图片选择。
* 图片预览:查看图片
* 图片选择:从本地相册选择图片或使用相机拍照。
**实现细节**:
* 图片预览:小程序调用 swan.previewImage API时,小程序框架传入图片URL信息尝试调起接入方图片浏览页面。
* 图片选择:小程序调用 swan.chooseImage API时,小程序传入选择图片基本参数尝试调起接入方图片选择页面。用户完成图片选择后图片选择页面应该将选择的图片信息返回给调起小程序
**接入说明**:
* 图片预览接入说明:需要接入方自己实现图片浏览页面。
* 图片选择接入说明:需要接入方自己实现图片选择界面。
**接入方法**:
* 图片选择和图片预览所有接口均位于IAiAppImageIoc.java中。
* 打开图片浏览器。
/**
**pictureParams参数说明**
|参数名|类型|说明|
|---|---|---|
|url| String Array| 图片url数组|
|index| String| 当前显示图片的链接|
* 选择图片
/**
OnImageChooseResultCallback说明
/**
|参数名|类型|说明|
|---|---|---|
|msg| String| 选择图片页面携带回的信息,主要是图片选择失败时,失败原因等信息。|
|paths| String List| 用户所选择的本地图片路径|
### 地图
#### 功能说明
* 地图功能包含(具体参考下文的使用文档):
1. 在指定的页面上创建指定大小的地图View
2. 在地图的View里绘制气泡、折线、Marker、文字、圆形,控件等
3. 响应地图View上各种控件和绘制的交互,如点击事件、经纬度变化事件等等
4. 响应地图View的大小,经纬度,绘制元素的位置和大小更新等
5. 删除地图组件
6. 移动地图上的Marker图标到指定的经纬度
7. 打开指定地图并定位到指定的经纬度
10. 缩放地图视野
11. 获取地图缩放比例
12. 获取地图显示框内的边框四个点经纬度
13. 获取地图当前中心的经纬度
14. 打开一个新页面从中选择一个地点(返回经纬度)
15. 进行步行导航和AR导航
16. 其他细节等
* 推荐使用百度地图的SDK进行接入,有详细的实现代码,接入成本较低;其他地图SDK需要重新实现下文 **接口设计** 中的接口。
* 地图的使用和开发参考小程序文档:
1. [API文档](https://smartprogram.baidu.com/docs/develop/api/location_map/#createMapContext/)
2. [地图组件文档](https://smartprogram.baidu.com/docs/develop/component/map/)
3. 文档中暴露给小程序开发者的字段和参数,最终也会透传到端的SDK的接口里,接入者需要做的就是实现SDK里的接口,将这些参数做处理,以作出响应。
#### 设计原理
* 首先,webView会和一个NA的Root容器(FrameLayout,盖在webView上)平级,一起插入到Android的View树上。
* 然后,所有的组件都会有一个View,这个View会放入到一个子容器中(AiAppsNAViewContainer),然后这个子容器再放到NA的Root容器中进行显示,占用的webView的空间,JS中会预留出来,不会被覆盖。
* 地图组件也是一个View,在收到创建的命令后,会产生一个地图的View,然后根据参数将整个view放到AiAppsNAViewContainer,再进一步放到NA的Root容器中进行显示。
* 在把地图的View添加到Root容器的同时,自己需要管理整个地图的View和参数,然后再地图上进行绘制等。
* 地图的整个数据结构,包括View都需要自己去管理和保存(比如用一个单例),因为所有的接口都是无状态的,每次只能通过前端传过来的id去查找。
#### 接口设计
* 接入地图能力,需要实现的接口,目前有16个,默认是使用百度地图的SDK实现。如果第三方使用百度地图SDK,可以直接使用代码,成本较小。
* 所有的接口都在 **IAiAppMapIoc.java** 中。
* 创建一个地图View。
/**
* 创建地图
* @param context 小程序宿主activity context
* @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler scheme结果回调,不论处理的结果成功还是失败,都必须 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 小程序环境,包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean create(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp) {
return false;
}
```
/**
* 更新地图
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 删除地图
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 移动指定的Marker标记,可以设置动画时间
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 移动到定位位置,showLocation字段必须为true才生效
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean moveToLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 缩放视野包含给定的所有点坐标
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean includePoints(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 获取当前的缩放比例
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getScale(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 获取当前的视野矩形坐标
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getRegion(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 获取地图中心点坐标
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean getCenterLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 在页面resume的时候通知地图
* @param manager 指定要resume的webView页面,可以通过这个获取到应该要通知的地图view
*/
public void resume(AiAppsSlaveManager manager);
/**
* 在页面pause的时候通知地图
* @param manager 定要pause的webView页面,可以通过这个获取到应该要通知的地图view
*/
public void pause(AiAppsSlaveManager manager);
/**
* 在页面销毁的时候通知地图
* @param manager 通知指定slave上的地图,又manager可以找到指定的地图view
*/
public void release(AiAppsSlaveManager manager);
*在新的页面打开指定的经纬度在地图上显示
/**
* 打开指定位置
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean openLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 打开选择位置的页面
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false */
public boolean chooseLocation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 打开步行导航
* @param context 小程序宿主activity context
* @param @param entity scheme实体,包含创建地图的scheme所有信息,从中可以取出所有地图相关的信息(上文文档里出现的所有字段都在)
* @param handler handler scheme结果回调,不论处理的结果成功还是失败,**都必须** 使用这个handler告诉SwanJS处理结果,前端的逻辑全部都严重依赖Android端上的回调。
* @param aiApp 包含了小程序运行时的所有信息
* @return 返回处理结果,处理ok返回true,出现问题返回false
*/
public boolean openWalkNavigation(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 顶层目前是不是步行导航的fragment
* 因为百度地图的步行导航目前只是支持actiivty,退出的时候会把真个actiivty finish,导致小程序本身也被finish了,
* 所以需要在导航退出的时候特殊处理
* @return 如果最上层是导航的fragment返回true
*/
public boolean isTopWalkNavFragment();
public boolean create(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp) {
// 1.解析参数
MapModel model = getModel(entity);
if (model == null) {
AiAppsLog.e("map", "parse error, model is null");
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_PARAMS_PARSE_FAIL);
return false;
}
// 2.如果slaveId为空,先使用当前的webview序列号; 需要前端支持传递slaveId
if (TextUtils.isEmpty(model.slaveId)) {
String webViewId = AiAppsUtils.getTopWebViewId();
if (!TextUtils.isEmpty(webViewId)) {
model.slaveId = webViewId;
}
AiAppsLog.w("map", "webView id is empty, use current webView");
}
// 3.从webView的id获取对应的webView
WebView slaveWv = getBdWebView(entity, model.slaveId);
if (slaveWv == null) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_PARAMS_PARSE_FAIL);
AiAppsLog.e("map", "can not find weiView by id " + model.slaveId);
return false;
}
// 5.根据执行情况返回
JSONObject params = new JSONObject();
if (!doAction(context, model, slaveWv, aiApp, params, entity, handler)) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_EXECUTE_FAIL);
AiAppsLog.e("map", "doAction fail ");
return false;
}
callBackWithParams(entity, handler, params.length() <= 0 ? null : params);
return true;
}
// 真正执行具体逻辑的操作
protected boolean doAction(Context context, MapModel model, WebView webView, AiApp aiApp, JSONObject params,
UnitedSchemeEntity entity, CallbackHandler handler) {
AiAppsLog.i("map", "MapCreateAction start");
// 1.必要性校验
if (webView == null || mapModel == null || !mapModel.isValid()) {
AiAppsLog.e(MapUtils.MODEL_TAG, "model data is invalid");
return false;
}
// 2.获取webView对应的manager
AiAppsWebViewManager manager = AiAppsController.getInstance().getWebViewManager(mapModel.slaveId);
if (manager == null || !(manager instanceof AiAppsSlaveManager)) {
return false;
}
// 3.将组件扔给当前salve中的helper管理
final MapViewHelper helper = getMapViewHelper((AiAppsSlaveManager) manager);
MapViewItem item = helper.find(mapModel.id);
// 已经存在,直接返回
if (item != null) {
return false;
}
// 4.新创建一个地图组件
item = MapViewItem.makeOne(context, mapModel);
if (item == null) {
return false;
}
// 5.嵌套CoverView,并附着在ViewTree上
AiAppsNAViewContainer container = new AiAppsNAViewContainer(webView.getContext());
boolean insert = container.insertView(item.mapView, mapModel);
if (!insert) {
return false;
}
// 6.保存组件
if (!helper.insert(item)) {
return false;
}
// 7.根据传入的属性初始化
MapViewInitHelper.initMapView(context, item, mapModel, helper);
return true;
}
/**
* 从发送过来的信息里组装需要的model
*
* @param entity scheme发送过来的信息载体
*
* @return MapModel对象,代表这个组件的所有属性
*/
protected MapModel getModel(UnitedSchemeEntity entity) {
if (entity == null) {
return null;
}
MapModel model = null;
Map<String, String> params = entity.getParams();
if (params == null || params.isEmpty()) {
AiAppsLog.e("map", "entity get Params is empty");
return null;
}
String jsonModel = params.get("data");
if(jsonModel == null){
AiAppsLog.e("map", "params string is empty");
return null;
}
try {
JSONObject json = new JSONObject(jsonModel);
model = new MapModel();
model.parseFromJson(json);
} catch (JSONException e) {
e.printStackTrace();
AiAppsLog.e("map", "params json parse error");
}
return model;
}
/**
* 统一包装返回msg
*
* @param resultCode 返回码
*
* @return msg
*/
protected JSONObject wrapResult(int resultCode) {
return UnitedSchemeUtility.wrapCallbackParams(resultCode);
}
/**
* Scheme的结果回调
*
* @param entity scheme实体
* @param handler 回调handler
* @param json 参数数据
*/
protected void callBackWithParams(UnitedSchemeEntity entity, CallbackHandler handler, JSONObject json) {
entity.result = UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
/**
* 获取指定的WebVIew
* @param entity 参数实体
* @param slaveId 当前webView的slaveId
* @return 返回找到的webView,找不到返回null
*/
protected WebView getBdWebView(UnitedSchemeEntity entity, String slaveId) {
WebView ngWebView = AiAppsUtils.getBdWebViewBySlaveId(slaveId);
if (ngWebView == null) {
entity.result = wrapResult(UnitedSchemeStatusCode.ERR_EXECUTE_FAIL);
return null;
}
return ngWebView;
}
其他说明:参考代码只提供大概框架,具体细节未提供。
地址、发票功能指 swan.chooseAddress
和 swan.chooseInvoiceTitle
能力以及和宿主配套的地址、发票管理中心,管理中心一般包含添加、删除、编辑等能力
使用示例:外卖小程序,填写配送地址时,可以调用 swan.chooseAddress
能力直接选择宿主管理的地址,获取信息后直接填写相关信息
小程序调用 swan.chooseInvoiceTitle
API时,会跳转到宿主发票管理中心,选择对应发票后返回发票信息到调起的小程序。
小程序调用 swan.chooseInvoiceTitle -> SwanJS -> Scheme -> 端能力 -> 管理中心 -> 选择后携带信息 -> 端能力 -> Scheme -> SwanJS -> 小程序
当前非最终版接口
/**
* 选择发票信息
*
* @return 发票,里面包含详细字段定义
*/
Invoice getInvoiceTitle();
Invoice参数说明:
参数名 | 类型 | 说明 |
---|---|---|
type | String | 抬头类型(0:单位,1:个人) |
title | String | 抬头名称 |
taxNumber | String | 抬头税号 |
companyAddress | String | 单位地址 |
telephone | String | 手机号码 |
bankName | String | 银行名称 |
bankAccount | String | 银行账号 |
当前非最终版接口
/**
* 获取地址
*
* @return 地址信息
*/
Address getAddress();
Address返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
userName | String | 收货人姓名 |
postalCode | String | 邮编 |
provinceName | String | 国标收货地址第一级地址 |
cityName | String | 国标收货地址第二级地址 |
countyName | String | 国标收货地址第三级地址 |
detailInfo | String | 详细收货地址信息 |
telNumber | String | 收货人手机号码 |
夜间模式指小程序夜间模式功能,主要需要实现和宿主夜间模式的状态联动,同时需要宿主维护夜间模式状态
小程序夜间模式使用蒙层压黑的方式实现
小程序运行与独立的进程,跨进程状态实现通过小程序内置的CS通信机制(Messenger实现),涉及AiAppsMessengerService
和AiAppsMessengerClient
主进程 -> 小程序进程
在宿主夜间模式发生变化时,调用:
AiAppsMessengerService service = AiAppsMessengerService.getServiceObject();
if (service != null) {
service.sendMessageToAllClients(AiAppsMessengerService.MSG_TYPE_SC_NIGHT_MODE_CHANGED);
}
同时需要实现以下接口:
当前接口和手百夜间模式实现原理有关,后续应该会改造成设置状态、获取状态两个接口
设置夜间模式开关状态,当前功能和 forceSyncNightModeState(boolean enable)
,功能重复,后续会整合
/**
* 设置夜间模式开关状态
*
* @param enable true/false
*/
void setNightModeSwitcherState(boolean enable);
主进程和小程序进程间,夜间模式状态强制同步,通过 Messenger 实现
/**
* 强制同步夜间模式状态
*
* @param enable true/false
*/
void forceSyncNightModeState(boolean enable);
获取夜间模式状态
/**
* 获取夜间模式开关状态
*
* @return boolean
*/
boolean getNightModeSwitcherState();
此接口和手百夜间模式机制实现有关,后续标准接口时会去除
/**
* 订阅夜间模式事件
*
* @param tag tag
* @param nightModeChangeListener 监听器
*/
void subscribeNightModeChangeEvent(Object tag, AiAppNightModeChangeListener nightModeChangeListener);
此接口和手百夜间模式机制实现有关,后续标准接口时会去除
/**
* 取消夜间模式状态订阅
*
* @param tag tag
*/
void unsubscribeNightModeChangedEvent(Object tag);
暂时未确定开源方式,后续补充
在打开文档前,先检查打开文档的插件是否可用
/**
* 阅读类插件是否可用
*
* @param mimeType 文件的mimeType
*/
boolean isReadPluginAvailable(Context context, String mimeType);
调起打开文档的插件展示文档
/**
* 处理文件打开
*
* @param activity activity
* @param path 文件对应的uri
* @param mimeType 文件的mimeType
*/
void openDocument(Activity activity, Uri path, String mimeType);
@Override
public void openDocument(final Activity activity, Uri path, String mimeType) {
Intent intent = new Intent(activity, PluginPaperViewerActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(path, mimeType);
intent.putExtra(PluginPaperViewerActivity.ENABLE_FALLBACK_FINISH_KEY, true);
// 不要新增加task
activity.startActivity(intent);
}
调起反馈
/**
* 调启反馈页面
*
* @param params 反馈参数
* @param callback 反馈结果
*/
void feedback(Bundle params, OnFeedbackResultCallback callback);
/**
* 反馈结果回调
*/
interface OnFeedbackResultCallback {
/**
* 反馈的结果
*
* @param data 反馈结果数据
*/
void onResult(String data);
}
小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是swan.createLivePlayerContext,通过这个接口创建一个LivePlayerContext对象,后续对直播组件的操作均可通过该对象完成。
对应的组件是,支持的操作如下
播放:
LivePlayerContext.play()
停止:
LivePlayerContext.stop()
静音:
LivePlayerContext.mute()
暂停:
LivePlayerContext.pause()
恢复:
LivePlayerContext.resume()
进入全屏:
LivePlayerContext.requestFullScreen(Object object)
退出全屏:
LivePlayerContext.exitFullScreen()
开发者通过swan.createLivePlayerContext(string id)创建直播对象,参数id是标签的id。创建的同时请求绑定相应的直播组件,随后在客户端创建一个AiAppsLivePlayer对象。
涉及到的端能力:LivePlayerAction
接口
public interface IAiAppLiveIoc {
/**
* 打开直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 静音直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean mute(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 全屏直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean setFullScreen(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 继续直播
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean resume(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
小程序提供了背景音频播放能力,通过swan.getBackgroundAudioManager()创建BackgroundAudioManager对象,后续对背景音频的操作均是通过此接口执行。
**注:**背景音频是小程序全局唯一的控件,即在任何一个小程序中控制都会影响到其他小程序的使用。
支持以下操作:
播放:
BackgroundAudioManager.play()
暂停。暂停后的音频再播放会从暂停处开始播放:
BackgroundAudioManager.pause()
停止。停止后的音频再播放会从头开始播放:
InnerAudioContext.stop()
跳转到指定位置:
BackgroundAudioManager.seek(number position)
监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.onCanplay(function callback)
监听音频播放事件:
InnerAudioContext.onPlay(function callback)
监听音频暂停事件:
InnerAudioContext.onPause(function callback)
监听音频停止事件:
InnerAudioContext.onStop(function callback)
监听音频自然播放至结束的事件:
InnerAudioContext.onEnded(function callback)
监听音频播放进度更新事件:
InnerAudioContext.onTimeUpdate(function callback)
监听音频播放错误事件:
InnerAudioContext.onError(function callback)
监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.onWaiting(function callback)
监听音频进行跳转操作的事件
InnerAudioContext.onSeeking(function callback)
监听音频完成跳转操作的事件
InnerAudioContext.onSeeked(function callback)
开发者通过调用通过swan.getBackgroundAudioManager()创建BackgroundAudioManager对象。通过swan/backgroundAudio/open端能力在客户端创建音频播放器对象,后续通过update接口更细你参数,对播放器的播放、暂停、定位时间点等常用操作均通过BackgroundAudioManager完成。
涉及到的端能力:AudioPlayerAction
目前百度智能小程序底层是通过Android系统的MediaPlayer实现音频播放,第三方可以根据自己的需求自行替换。
接口如下:
public interface IAiAppAudioIoc {
/**
* 打开音频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新音频参数
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 获取参数值
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean getParamSync(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
智能小程序提供了视频播放能力,通过实现video抽象接口就可以轻松让您的小程序拥有定制化的视频播放器,让小程序的的产品体验及产品功能更加完善。
开发者通过swan.createVideoContext来创建一个播放器实例,后续有关播放器的操作均在此对象上完成。
对应的组件是
<video/>
支持的操作如下:
播放视频:
VideoContext.play()
暂停视频:
VideoContext.pause()
停止视频:
VideoContext.stop()
跳转到指定位置:
VideoContext.seek(number position)
发送弹幕:
VideoContext.sendDanmu(Object data)
进入全屏:
VideoContext.requestFullScreen(Object object)
退出全屏:
VideoContext.exitFullScreen()
小程序调用swan.createVideoContext时,swan-core会通知客户端创建MediaPlayer对象。通过swan/video/open接口传入初始化参数,后续可以通过update接口更新参数。对播放器的播放、暂停、拖拽等常用操作均通过videoContext完成。
涉及到的端能力:VideoOpenAction
以下是需要实现的基础播放器接口
public interface IAiAppVideoIoc {
/**
* 打开视频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放视频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 全屏
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean setFullScreen(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 设置弹幕
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean sendDanmu(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 手机back键被按下
* @return 是否处理back键
*/
public boolean onKeyBack();
}
小程序提供了通用音频播放能力,通过swan.createInnerAudioContext()创建audioContext对象,后续对audio的操作均是通过此接口执行。
对应的组件是
<audio/>
支持以下操作:
播放:
InnerAudioContext.play()
暂停。暂停后的音频再播放会从暂停处开始播放:
InnerAudioContext.pause()
停止。停止后的音频再播放会从头开始播放:
InnerAudioContext.stop()
跳转到指定位置:
InnerAudioContext.seek(number position)
销毁当前实例:
InnerAudioContext.destroy()
监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.onCanplay(function callback)
取消监听音频进入可以播放状态的事件,但不保证后面可以流畅播放:
InnerAudioContext.offCanplay(function callback)
监听音频播放事件:
InnerAudioContext.onPlay(function callback)
取消监听音频播放事件:
InnerAudioContext.offPlay(function callback)
监听音频暂停事件:
InnerAudioContext.onPause(function callback)
取消监听音频暂停事件:
InnerAudioContext.offPause(function callback)
监听音频停止事件:
InnerAudioContext.onStop(function callback)
取消监听音频停止事件:
InnerAudioContext.offStop(function callback)
监听音频自然播放至结束的事件:
InnerAudioContext.onEnded(function callback)
取消监听音频自然播放至结束的事件:
InnerAudioContext.offEnded(function callback)
监听音频播放进度更新事件:
InnerAudioContext.onTimeUpdate(function callback)
取消监听音频播放进度更新事件:
InnerAudioContext.offTimeUpdate(function callback)
监听音频播放错误事件:
InnerAudioContext.onError(function callback)
取消监听音频播放错误事件:
InnerAudioContext.offError(function callback)
监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.onWaiting(function callback)
取消监听音频加载中事件,当音频因为数据不足,需要停下来加载时会触发
InnerAudioContext.offWaiting(function callback)
监听音频进行跳转操作的事件
InnerAudioContext.onSeeking(function callback)
取消监听音频进行跳转操作的事件
InnerAudioContext.offSeeking(function callback)
监听音频完成跳转操作的事件
InnerAudioContext.onSeeked(function callback)
取消监听音频完成跳转操作的事件
InnerAudioContext.offSeeked(function callback)
开发者通过调用通过swan.createInnerAudioContext()创建audioContext对象,并传入id绑定相应的audio标签。通过swan/audio/open端能力在客户端创建MediaPlayer对象,后续通过update接口更细你参数,对播放器的播放、暂停、定位时间点等常用操作均通过audioContext完成。
涉及到的端能力:AudioPlayerAction
目前百度智能小程序底层是通过Android系统的MediaPlayer实现音频播放,第三方可以根据自己的需求自行替换。
接口如下:
public interface IAiAppAudioIoc {
/**
* 打开音频
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean open(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 更新音频参数
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean update(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 播放
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean play(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 停止
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean stop(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 暂停
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean pause(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 快进
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean seekTo(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
/**
* 继续
* @param context 上下文
* @param entity scheme实体
* @param handler scheme处理器
* @param aiApp 小程序实体
* @return 是否成功
*/
public boolean resume(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
}
同步处理:在完成相应的逻辑之后,直接调用以下代码,告诉前端JS的处理结果,又叫一级回调
// 处理成功,使用UnitedSchemeStatusCode.ERR_OK
// handler中保存有JS的回调函数,会自动使用webView的方法回调JS函数
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
// 处理失败,使用UnitedSchemeStatusCode.ERR_EXECUTE_FAIL
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_EXECUTE_FAIL));
json 是传给JS的参数,第三方自己根据情况填充,没有可以不用传
异步处理:这个操作需要耗时,所以得处理完毕之后,在主动通知JS。
必须先告诉前端JS,已经收到它的命令,通过:
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
通过这种 成功回调 的方式,告诉JS,端上已经接收到命令,开始执行耗时任务
同样的,如果还没开始异步任务,刚上来就失败了,需要通过告诉前端JS失败了,无法开始异步任务
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_EXECUTE_FAIL));
第二步才开始执行异步任务,此时在接口参数UnitedSchemeEntity entity中会有个cb=xxx的参数,这个cb就是异步回调,又叫二级回调
Map<String, String> params = entity.getParams();
String jsonModel = params.get("data");
JSONObject json = new JSONObject(jsonModel);
callback = json.optString(CB);
这个callback就是JS中的回调函数,专门用于端上处理完异步耗时任务之后,主动通知JS的接收函数。
同步回调(又叫一级回调):
void remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp);
删除一个View,是一个同步操作,不耗时,所以直接进行删除操作,操作完毕之后直接通知JS结果即可
void remove(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp){
// 执行删除操作
MapViewManager.get().remove(webView, model);
// 将删除结果返回给前端 json是参数,删除成功UnitedSchemeStatusCode.ERR_OK,失败UnitedSchemeStatusCode.ERR_EXECUTE_FAIL
UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
异步回调(又叫二级回调)
void translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp)
动过程需要有动画,移动结束之后要通知JS,移动结束了,肯定是耗时操作,需要用异步回调。
```
void translateMarker(Context context, UnitedSchemeEntity entity, CallbackHandler handler, AiApp aiApp){
AiAppsWebViewManager manager = AiAppsController.getInstance().getWebViewManager(model.slaveId);
if (manager == null || !(manager instanceof AiAppsSlaveManager)) {
return false;
}
Map<String, String> params = entity.getParams();
String jsonModel = params.get("data");
JSONObject json = new JSONObject(jsonModel);
callback = json.optString(CB);
MapViewHelper helper = MapViewManager.get().getMapViewHelper((AiAppsSlaveManager) manager);
MapViewItem item = helper.find(model.id);
dotTanslateMarker(model, item, handler)
// 校验没问题,先一级回调,告诉前端,本次能力调用成功,接下进行耗时操作
return UnitedSchemeUtility.callCallback(handler, entity,
UnitedSchemeUtility.wrapCallbackParams(json, UnitedSchemeStatusCode.ERR_OK));
}
void dotTanslateMarker(TranslateMarkerModel model, final MapViewItem item, final CallbackHandler handler) {
if (!model.isValid()) {
return false;
}
final LatLng newLatLng = new LatLng(model.coordinate.latitude, model.coordinate.longitude);
final List<MarkerViewItem> markerViewItems = item.getMarkerViewItem(model.markId);
if (markerViewItems != null) {
markerViewItem.translateMarkerWithAnimation(item, newLatLng, model.duration,
new MarkerViewItem.AnimationEndCallback() {
@Override
public void onAnimationEnd() {
if (!TextUtils.isEmpty(mCallback)) {
// 二级回调,通知JS 动画结束
handler.handleSchemeDispatchCallback(mCallback, "");
}
}
});
}
}
```
小程序登录功能(调用宿主登录),和宿主强相关
需要宿主维护自己的账号体系
标准接口,后续提供
在符合法务要求的情况下赋予小程序调用必要功能的权限,比如用户信息、直播、地理位置等
敏感功能权限风控,可以随时由用户或者云端平台撤回授权
用户的敏感信息都从开放平台统一输出
暂无,后续提供
暂无,后续提供
暂无
在小程序正式开源前,会随时更新,不保证时效性,当前为百度内部接入流程
http://icode.baidu.com/repos/baidu/searchbox-android/aiapps/tree/master
将配置好依赖的库,参考根目录的settigns.gradle build.gradle demo/build.gradle
需要使用apply plugin: 'com.baidu.pyramid.di'在application的build.gradle
在app的application中初始化用到了代码
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AppRuntimeInit.onApplicationattachBaseContext(this);
// 如果pyramid插件失效(编译系统不支持transForm时,pyramid会失效),使用手动注入
AiAppImplInject.inject();
// 一下是使用了lib-ubc库的时候需要做的初始化
Initer.onApplicationattachBaseContext(this);
Initer.setIPCReporter(new IPCReporterImpl());
if (AppProcessManager.isServerProcess()) {
UBCIPCManager.addUBCRemoteService();
}
}
@Override
public void onCreate() {
super.onCreate();
// 初始化系统webView
WebViewFactory.initOnAppStart(this, true, false);
NgWebViewInitHelper.getInstance().initBWebkit();
// 初始化Fresco
Fresco.initialize(this);
// 应用启动后,需要在合适时机,执行此方法去下载
DynamicDownloadInit.start();
}
实现com.baidu.searchbox.ng.ai.apps.ioc.interfaces包小所有的接口(目前需要参考手百的实现方式),demo中是默认实现。
和Server交互的需要有公参,内部可以参考URLConfig.java这个类
每个接入方使用的预置SwanJS都不一样(目前在/lib-ng-aiapps/src/main/assets/aiapps文件夹下),需要找 @侯禹 @任仲桦
后端的开放平台,授权相关 需要申请host_api_key,找 @刘青
理论上就接入完毕了,如有问题直接找 @康森(Android) @刘青(Server) @侯禹(FE)
外部开源库:
库 | 说明 |
---|---|
Lottie.framework | 动画库,animationView组件使用,属于手百小程序特有能力。 |
AFNetworking.framework | 网络库,小程序包、swanCore包,request端能力使用到。 |
SDWebImage.framework | 网络图片库。 |
TurboNet.framework | 网络库。 |
Masonry.framework | 自动布局库。 |
MJRefresh.framework | 下拉刷新库,小程序下拉刷新。 |
MBProgressHUD.framework | loading加载框库。 |
YYCache.framework | 缓存库,小程序runtime webView使用到。 |
FMDatabase.framework | 数据库 |
ZipArchive.framework | 小程序解压使用 |
BaiduB64OC.framework | 加解密库 |
手百内部基础库:
库 | 说明 |
---|---|
BBAFoundation.framework | 基础库 |
BBAUIKit.framework | 基础视图库 |
BBASchemeDispatcher.framework | 路由分发库 |
Pyramid.framework | 组件解耦合库,金字塔模型 |
BBAAPIRequest.framework | 手百网络库 |
BBANetwork.framework | 手百网络库 |
SmartHttpDns.framework | DNS网络库 |
BoxKit.framework | 手百公参数库 |
BBASettings.framework | Storage.framework |
UBC.framework | 日志统计库 |
ApsManager.framework | 下载小程序、swanCore包 |
BBAUpdate.framework/RIButtonItem.framework/BBACommonCrypto.framework | 加密库 |
// 调起协议插件注册
[BBASchemeDispatcher setRegisterPlistName:@"SDPluginNameInfo"];
// 小程序引擎初始化
BBPContext *context = [[[BBPContext alloc] init] autorelease];
context.application = application;
context.launchOptions = launchOptions;
context.homeLaunchedDelayTime = 0.1f;
[PyramidInstance setContext:context];
// appID为宿主app在iTunes上的appID
// scheme是宿主app自定义的,小程序调起协议头。如:tiebaclient
[BBAMNPManager registerDispatcherAppID:@"appID" version:@"33" schemes:@[@"scheme"]];
// .m文件中添加该宏命令
ModuleDefine(xxxxModule类名)
- (void)moduleRegister:(BBPContext *)context {
/* 注册功能及服务,以BBAMNPPlatformProtocol举例
BBAMNPPlatformProtocol是要注册的协议,所有协议由小程序提供,可见小程序代码中的adapter文件夹。
XXXXXPlatformImplement是宿主app对BBAMNPPlatformProtocol的实现类,其他protocol同。
(小程序的功能有部分需要依赖宿主app去实现才能正常工作,具体实现列表可见其他文档,宿主app只需要创建对应的implement文件,并实现对应的协议即可)
BBAMNP_PLATFORM_PYRAMID_IDENTIFITER是BBAMNPPlatformProtocol提供的标识常量,直接使用即可。
*/
[Pyramid registerExternService:NSProtocolFromString(@"BBAMNPPlatformProtocol") implClass:XXXXXPlatformImplement.class identifier:BBAMNP_PLATFORM_PYRAMID_IDENTIFITER];
}
小程序菜单点击分享,调起宿主分享功能,此功能需要宿主实现,调用宿主的分享面板
BBAMNPPlatformProtocol
遵循BBAMNPPlatformProtocol协议,并实现协议中的分享接口
当前接口非终版接口,后面会标准化
/**
* @param command 承载分享内容的对象
*/
- (void)callshare:(BBASchemeDispatcher *)command;
- (void)callshare:(BBASchemeDispatcher *)command {
NSDictionary *options = command.optionsDict;
NSString *title = options[@"title"]; // 分享标题
NSString *content = options[@"content"]; // 分享内容
NSString *imageUrl = options[@"imageUrl"]; // 分享图标
NSString *path = options[@"path"]; // 页面path
// 根据获得的分享内容定制分享逻辑
// Your code
}
遵循BBAMNPPhotoProtocol协议,并实现协议中的图片选择接口。
/**
* Present photo picker controller to get images.
* @discussion Not recommend to use `limitSize` parameter, pass `nil` to use internal default value will be better.
* Even if you have passed a limit value, it maybe return a image that size does not match what you want.
* See also PHImageManager `requestImageForAsset:targetSize:contentMode:options:resultHandler:`.
*
* @param maxNumber The limit count of images.
* @param originalImageBlock Get original image call back.
* @param webImagesBlock Get compressed image call back.
* @param cancelBlock Cancel action call back.
* @param limit The limit image side.
* @return Photo picker controller.
*/
+ (UIViewController *)createPhotoPickerViewController:(NSInteger)maxNumber
originalImages:(void (^)(NSDictionary *result))originalImageBlock
webImages:(void (^)(NSDictionary *result))webImagesBlock
cancel:(void (^)(NSDictionary *result))cancelBlock
limitSize:(NSNumber *)limit;
遵循BBAMNPPlatformProtocol协议,并实现协议中的图片预览接口
/**
* @param command 承载图片信息的对象
*/
+ (BOOL)previewImage:(BBASchemeDispatcher *)command;
- (void)previewImage:(BBASchemeDispatcher *)command {
NSDictionary *options = command.optionsDict;
NSArray *urls = options[@"urls"]; // 所有预览图片的url
NSString *current = options[@"current"]; // 当前预览图片的url
// 根据获得的图片信息定制预览逻辑
Your code
}
无
/**
绑定上下文信息
*/
@property(nonatomic,strong)id context;
/**
地图ID
*/
@property(nonatomic,copy)NSString *mapID;
/**
比例尺的位置,设定坐标以MapView左上角为原点,向右向下增长
*/
@property (nonatomic) CGPoint mapScaleBarPosition;
/**
代理
*/
@property(nonatomic,weak)id<BBAMapKitControllerDelegate> delegate;
/**
获取mapView
@return 地图View
*/
- (UIView *)mapView;
/**
创建map组件
@param mapAttr map 配置
@return 地图View
*/
- (UIView *)mapViewWithAttribute:(BBAMapKitMap *)mapAttr;
/**
销毁map组件
*/
- (BOOL)destroyMap;
/**
移动地图中心点到location位置 无动画
@param location location
*/
- (void)moveToLocation:(CLLocationCoordinate2D)location;
/**
移动地图中心点到location位置
@param location
@param animated
*/
- (void)moveToLocation:(CLLocationCoordinate2D)location animated:(BOOL)animated;
/**
获取当前地图的视野范围,返回西南角,东北角的经纬度
@return @[西南角,东北角]
*/
- (NSArray<CLLocation*> *)getRegion;
/**
获取当前地图的缩放级别
@return float
*/
- (float)getScale;
/**
获取当前地图中心的经纬度
@return
*/
- (CLLocationCoordinate2D)getCenterLocation;
/**
在地图上显示圆
@param aCircle
*/
- (BOOL)showCircle:(BBAMapKitCircle *)aCircle;
/**
在地图上画线 指定一系列坐标点,从数组第一项连线至最后一项
@param polyline
*/
- (BOOL)showPolyline:(BBAMapKitPolyline *)polyline;
/**
添加标记
@param marker
*/
- (BOOL)showMark:(BBAMapKitMarker *)marker;
/**
在地图上添加控件
@param control
*/
- (void)showControl:(BBAMapKitControl *)control;
/**
显示用户位置
@param show
*/
- (void)showUserLocation:(BOOL)show;
/**
用户所在位置经纬度
@return
*/
- (CLLocationCoordinate2D)userLocation;
/**
更新map配置
@param attr
*/
- (void)updateMapAttr:(BBAMapKitMap *)attr;
/**
设置map参数
@param attr
*/
- (void)setMapAttr:(BBAMapKitMap *)attr;
/**
平移marker
@param markid markid
@param destination destination 要移动到的点的坐标
@param autoRotate 是否自动旋转 暂不支持
@param rotate rotate 旋转角度 暂不支持
@param duration duration 动画时长
@param finish finish 动画完成回调
*/
- (void)translateMarker:(NSString *)markid
destination:(CLLocationCoordinate2D)destination
autoRotate:(BOOL)autoRotate
rotate:(double)rotate
duration:(double)duration
finsh:(void(^)(void))finish;
/**
缩放视野展示所有经纬度
@param points 经纬度列表
@param padding 坐标点形成的矩形边缘到地图边缘的距离
@return
*/
- (BOOL)includePoints:(NSArray<CLLocation *> *)points padding:(UIEdgeInsets)padding;
/**
获取当前用户位置
@return id<BBAMapKitUserLocationProtocol>
*/
- (id<BBAMapKitUserLocationProtocol> )curUserLocation;
/**
*当mapview即将被显式的时候调用,恢复之前存储的mapview状态。
*/
-(void)viewWillAppear;
/**
*当mapview即将被隐藏的时候调用,存储当前mapview的状态。
*/
-(void)viewWillDisappear;
/**
计算2个坐标的直线距离
@param coordinate
@param otherCoordinate
@return CLLocationDistance
*/
- (CLLocationDistance)distanceBetween:(CLLocationCoordinate2D)coordinate
andCoordinate:(CLLocationCoordinate2D)otherCoordinate;
/**
添加默认大头针标注
@param coordinate
@return YES 添加成功
*/
- (BOOL)addPointAnnotation:(CLLocationCoordinate2D)coordinate;
/**
比例尺的宽高
@return 比例尺的宽高
*/
- (CGSize) mapScaleBarSize;
/**
返回地图的logo图片名称
@return 地图的logo图片名称
*/
- (NSString *)logoImageName;
/**
地图默认配置
配置显示比例尺 地图logo位置等
@param mapView
@param zoomLevel 缩放级别
*/
+ (void)configMapView:(UIView*)mapView zoomLevel:(float)zoomLevel;
/**
获取相对路径的全路径
@param controller
@param path
@return
*/
- (NSString *)mapKitController:(id<BBAMNPMapProtocol>)controller
fullPathOfRelativePath:(NSString *)path;
/**
定位授权
@param complete
*/
- (void)requestLocationAuthFinish:(void(^)(BOOL result))complete;
@optional
/**
控件点击回调
*/
- (void)mapKitController:(id<BBAMNPMapProtocol>)controller didClickedControl:(BBAMapKitControl *)control;
//点击标记点时触发
- (void)mapKitController:(id<BBAMNPMapProtocol>)controller didClickedMarker:(BBAMapKitMarker *)marker;
//当点击annotation view弹出的泡泡时,调用此接口
- (void)mapKitController:(id<BBAMNPMapProtocol>)controller didClickedCallOut:(BBAMapKitMarker *)callOut;
//视野发生变化
- (void)regionWillChange:(id<BBAMNPMapProtocol>)controller;
//视野发生变化时触发
- (void)regionDidChange:(id<BBAMNPMapProtocol>)controller;
//渲染完成
- (void)mapViewDidFinishRendering:(id<BBAMNPMapProtocol>)controller;
//地图点击
- (void)mapKitController:(id<BBAMNPMapProtocol>)controller didClickedMap:(CLLocationCoordinate2D)coordinate;
//用户位置改变
- (void)userLocationChanged:(id<BBAMNPMapProtocol>)controller;
//显示路线
-(void)showRouteWithStartCoordinate:(CLLocationCoordinate2D)start
endCoordinate:(CLLocationCoordinate2D)end
mapController:(id<BBAMNPMapProtocol>)mapController;
// 获取当前位置
- (void)getCurrentLocation:(void (^)(CLLocationCoordinate2D startCoordinate))completion;
// 步行导航
- (void)startWalkNavigationFromStartCordinate:(CLLocationCoordinate2D)startCordinate toEndCordinate:(CLLocationCoordinate2D)endCordinate callBack:(void (^)(BBAMNPMapWalkNaviPlanRouteErrorCode errorCode))callBack;
typedef NS_ENUM(NSUInteger, BBAMNPMapWalkNaviPlanRouteErrorCode) {
BBAMNPMapWalkNaviPlanRoutesuccess = 1000,//算路成功
BBAMNPMapWalkNaviPlanRouteError = 1004,//算路失败
BBAMNPMapWalkNaviPlanRouteNavLess = 1005,//距离太近
BBAMNPMapWalkNaviPlanRouteNavMore = 1006 //距离太远
};
@property(nonatomic,weak)id<BBAMapKitSearchControllerDelegate> delegate;
/**
逆地理编码搜索结果
@param coordinate
*/
- (void)reverseGeoSearch:(CLLocationCoordinate2D)coordinate;
/**
poi检索
@param key
@param city
@param pageIndex
@param pageCapacity
*/
- (void)searchPoiWithQuery:(NSString *)key
city:(NSString *)city
pageIndex:(int)pageIndex
pageCapacity:(int)pageCapacity;
/**
* 获取位置信息-异步
* @discussion 根据指定坐标系类型异步获取定位信息
* @param type 坐标系类型
* @param completion 定位结果的callback
*/
+ (void)getLocationWithType:(BBAMNPLocationCoordinateType)type completion:(BBAMNPLocationCompletion)completion;
/**
* 获取位置信息-同步
* @discussion 根据指定坐标系类型同步获取定位信息,
* @param type 坐标系类型
* @param completion 定位结果的callback
* @return 定位结果BBAMNPLocationData,可以为nil
*/
+ (BBAMNPLocationData *)getLocationWithType:(BBAMNPLocationCoordinateType)type;
BBAMNPPaymentProtocol
/**
* 百度钱包
* @discussion 直接调起百度钱包支付,订单信息和支付回调的数据都是透传,小程序框架不做特殊处理
* @param orderInfo 订单信息,由业务小程序生成,小程序框架透传给宿主app,宿主app直接传给百度钱包API即可
* @param success 支付成功回调,payResult需要宿主app直接透传给小程序框架
* @param failure 支付失败回调,payResult需要宿主app直接透传给小程序框架
*/
+ (void)requestBDWalletWithOrderInfo:(NSString *)orderInfo
success:(void(^)(NSString *payResult))successBlock
failure:(void(^)(NSString *payResult))failureBlock;
/**
* 支付宝
* @discussion 直接调起支付宝支付,订单信息和支付回调的数据都是透传,小程序框架不做特殊处理
* @param orderInfo 订单信息,由业务小程序生成,小程序框架透传给宿主app,宿主app直接传给支付宝API即可
* @param completion 支付回调,其中status和payResult需要宿主app直接透传给小程序框架
*/
+ (void)requestAlipayWithOrderInfo:(NSString *)orderInfo
completion:(void(^)(NSInteger status, NSDictionary *payResult))completionBlock;
聚合支付
待方案确定后补充小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是swan.createLivePlayerContext,通过这个接口创建一个LivePlayerContext对象,后续对直播组件的操作均可通过该对象完成。
BBAMNPLiveProtocol
非最终方案
1.使用方需要实现直播能力,且直播对象需要实现BBAMNPMessageItemProtocol
协议
2.遵循BBAMNPMessageIMManagerProtocol协议,实现相关接口(返回直播对象)。
// BBAMNPMessageIMManagerProtocol.h
/**
* @ret id<BBAMNPMessageItemProtocol> 拥有直播能力的实例
*/
+ (id<BBAMNPMessageItemProtocol>)getMessageItemInstance;
// BBAMNPMessageItemProtocol.h
typedef NSArray<NSDictionary *> BBAMNPLiveMessage;
typedef void(^BBAMessageMNPLiveMessageHandler)(BBAMNPLiveMessage *messages);
typedef void(^BBAMessageMNPLiveMessageCompletion)(NSError *error);
typedef void(^BBAMessageMNPLiveMessageSendCompletion)(NSDictionary *message, NSError *error);
typedef void(^BBAMessageMNPLiveMessageFetchCompletion)(BBAMNPLiveMessage *messages, NSError *error);
@protocol BBAMNPMessageItemProtocol <NSObject>
+ (NSNumber *)yuk;
// 直播间注册监听者
- (void)registerListener;
// 移除监听者
- (void)removeListener;
/**
* @brief 加入直播间
*
* @param roomInfo 直播间相关信息
* @param messageHandler 新消息接收回调
* @param completion 失败回调
*/
- (void)joinRoom:(NSString *)mcastID
commentUrl:(NSString *)commentUrl
messageHandler:(BBAMessageMNPLiveMessageHandler)messageHandler
completion:(BBAMessageMNPLiveMessageCompletion)completion;
/**
* @brief 加入直播间
*
* @param msgInfo 需要发送的消息
* @param completion 发送完成回调
*/
- (void)sendMessage:(NSString *)msg
completion:(BBAMessageMNPLiveMessageSendCompletion)completion;
/**
* @brief 加入直播间
*
* @param fetchInfo 拉去消息所需相关信息
* @param completion 拉取完成回调
*/
- (void)fetchMessagesWithAppId:(int64_t)appId
useruk:(int64_t)useruk
contacter:(int64_t)contacter
begin:(int64_t)beginMsgId
end:(int64_t)endMsgId
count:(NSInteger)count
completion:(BBAMessageMNPLiveMessageFetchCompletion)completion;
智能小程序提供了视频播放能力,通过实现video抽象接口就可以轻松让您的小程序拥有定制化的视频播放器,让小程序的的产品体验及产品功能更加完善。
开发者通过swan.createVideoContext来创建一个播放器实例,后续有关播放器的操作均在此对象上完成。
BBAMNPVideoProtocol
遵循BBAMNPVideoProtocol协议,实现相关接口
/**
播放视频
@param superView 父视图
@param frame frame
@param videoInfo 视频信息
@param downloadInfo 下载信息
@param delegate BBAMNPVideoHandleDelegate
@return 是否当前要播放的视频已经播放了
*/
+ (BOOL)playVideoInView:(UIView *)superView
withFrame:(CGRect)frame
videoInfo:(NSDictionary *)videoInfo
downloadInfo:(NSDictionary *)downloadInfo
delegate:(id<BBAMNPVideoHandleDelegate>)delegate;
/// 暂停播放器
+ (void)pause;
/// 停止
+ (void)stopPlayer;
/// 关闭播放器
+ (void)closePlayer;
/// 重新播放视频
+ (void)replay;
/// 跳到指定时间播放
+ (void)seek:(double)seconds;
/// 进入全屏
+ (void)enterFullScreen;
/// 退出全屏
+ (void)exitFullScreen;
/// 播放器代理
+ (id)delegate;
/// 播放器当前播放模式
+ (NSUInteger)currentPlayerMode;
/// 发送弹幕信息
+ (void)sendBarrage:(NSDictionary *)barrage;
/// 更新弹幕信息
+ (void)updateBarrageMeta:(NSDictionary *)barrageMeta;
/// 外部更新弹幕信息
+ (void)updateBarrageList:(NSArray *)barrageList;
/// 更新播放器的数据
+ (void)updateVideoInfo:(NSDictionary *)videoInfo;
/// 播放器的父视图
+ (UIView *)currentPlayerSurperView;
/// 当前播放状态
+ (int)currentPlayerStatus;
/// 是否包含在自定义player在视图中
+ (BOOL)containCustomPlayerInView:(UIView *)view;
/// 是否正在播放
+ (BOOL)currentIsPlaying;
/// 是否全屏模式
+ (BOOL)currentIsFullMode;
/// 更新播放器frame
+ (void)updatePlayerFrame:(CGRect)frame vid:(NSString *)vid;
##4.需要宿主提供的基础信息
为了小程序能区分不同的宿主app,需要宿主提供一系列app基础信息,以维持小程序的正常运行。
需要提供的信息都在BBAMNPPlatformProtocol
中定义了接口,宿主app实现这些接口即可
/**
* 获取小程序的根导航栈
* @discussion 获取小程序的根导航栈
* @return 小程序即将push进入的UINavigationControlle
*/
+ (UINavigationController *)rootNavigationController;
+ (NSString *)userAgentForRequest;
+ (NSString *)userAgentForWebView;
+ (NSString *)userAgentForMNP;
+ (NSString *)composeParameters;
+ (NSString *)hostName;
+ (NSString *)hostVersion;
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
Object.create = Object.create || (p=null) => {
let ret = new(function() {});
Object.setPrototypeOf ? Object.setPrototypeOf(ret, p) : ret.__proto__ = p;
return ret;
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.