GithubHelp home page GithubHelp logo

blog's People

Contributors

ynchuan avatar

Watchers

 avatar  avatar

blog's Issues

doc.write

<!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>

Object.create

Object.create = Object.create || (p=null) => {
    let ret = new(function() {});
    Object.setPrototypeOf ? Object.setPrototypeOf(ret, p) : ret.__proto__ = p;
    return ret;
}

Content-Type

application/x-www-form-urlencode

  • 表单的提交,并且将提交的数据进行urlencode。默认情况下,所有的表单提交都是通过这种默认的方式实现的。
  • Ajax默认也是通过HTTP application/x-www-form-urlencoded
    image

multipart/form-data

  • 如果要在表单中上传文件,一般会将form的enctype参数设置为multipart/form-data。这种方式只支持POST的请求方式
  • Contype-Type=multipart/form-data情况的时候,都会通过一个特殊的字符串来将原始POST数据进行分割,multipart/form-data; boundary=---------------------------66841812532223
    image

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZOVBvOdURQKFDgjK
image

application/json

  • 请求的Content-Type是Json的数据格式,http body中的内容就是请求的json数据
  • 例如:{"action":"get","format":"json","conds":{"status":-1,"ps":-1,"conf":1}}

line-height

可以看到高度

  • fs (font-size)
  • tb (top-bottom)
  • sh (选中阴影高度)
  • slh (self line-height)
  • blh (box line-height)

记录方式:blh/slh/sh/tb/fs,例如:/80/60/57/42/30

安全

MD5

密码不要设置的过于简单,MD5一般是不能够被反编译的

var crypto = require('crypto');
var md5 = crypto.createHash('md5');
md5.update('abcdef');
var ret = md5.digest('hex');
console.log(ret);

CRC

var crc32 = require('crc').crc32;
crc32('hello').toString(16);
// "3610a686"

SHA1

var crypto = require('crypto');
var sha1 = crypto.createHash('sha1');
sha1.update('abcdef');
var ret = sha1.digest('hex');
console.log(ret);

RSA

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);

备注

  • CRC采用多项式除法,MD5和SHA1使用的是替换、轮转等方法
  • CRC校验位的长度跟其多项式有关系,一般为16位或32位;MD5是16个字节(128位);SHA1是20个字节(160位)。
  • CRC的安全性跟多项式有很大关系,相对于MD5和SHA1要弱很多;MD5的安全性很高,不过大概在04年的时候被山东大学的王小云破解了;SHA1的安全性最高。
  • CRC的计算效率很高;MD5和SHA1比较慢。
  • CRC一般用作通信数据的校验;MD5和SHA1用于安全(Security)领域,比如文件校验、数字签名等

async

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);
});

Josephus

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);

Promise

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;
});

if-else vs 3unary

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));

babel-transform

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)

linux

  • ls -l --time=ctime install.log
  • chown runoob:runoobgroup file1.txt
  • 日志分词: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;

左右样式

页面开发中最主要的是进行页面布局,布局分为区块上下和左右布局;其中因为行级标签的存在,所以上下布局十分简单;复杂之处在于实现区块左右放置的方式很多,所以本文主要介绍我们常见的左右布局方式。

页面布局实现包含html开发和css编写,二者相辅相成,html是css开发先导,html合理性决定css布局复杂度。

1. table

table标签能够实现左右布局,table布局也是页面布局中使用的最早的布局方式,但是由于存在性能和灵活性问题,已被逐渐放弃,转而在采用div布局。

  • table,display:table;块级标签,但是具有包裹性
  • tr,display:table-row,表示一行
  • td,display:table-cell,表示一列

2. inline-block

  • display:inline-block,介于行级元素(display:inline)块级元素(display:block)之间的属性,
  • 可以像行级元素一样水平布局,也可以像块级元素设置宽高属性,所以可以进行左右布局。
  • 因为存在空白点问题,所以一般不用于大区块布局,常用于与行内元素构成的内容同行
  • 不支持ie6、7浏览器,请注意,但是可以使用inline进行hack处理。

3. float

  • 由于具有先左右后上线排布特性,所以成为常见实现左右布局方式
  • 本意为脱离文档流,实现文字环绕效果
  • 属性值包括none/left/right;left属性值使该区域向父级标签区域的左侧边界放置,right属性值使该区域块向父级标签的右侧边界放置

具有特性:

  • 包裹性:可以按照区域块中子元素的实际宽度进行包裹;
  • 破坏性:由于脱离文档流,float区域块不会被父级区域块包裹,造成高度塌陷,引起下文元素布局异常,引入常见清除浮动问题;

4. flexbox

弹性盒子主要用于移动前端开发,属性不支持ie6、7、8,支持chrome,firefox,不适合面向互联网用户pc网页布局。

  • display:flex
  • flex: 0 1 auto
  • 通过容器和项目,完成通用和个性管理

5. float+margin实现左右布局

float能够使得元素向左或者向右靠边布局,在同级元素中设置正常流区域与浮动块并列,浮动块会在正常流同级区域的边界处,并影响该正常流水平方向布局,在正常流区域块的盒子设置margin等于浮动块的宽度可以清除影响。

6. (float+overflow)实现左右布局

float对元素水平方向的影响,除采用margin进行影响的清除,依据块级格式上下文原理,还可以在受影响的元素上添加overflow:hidden来清除浮动对该区域块带来的影响。

7. position:absolute左右布局

绝对定位,通过对支点元素进行相对偏移实现左右布局

  • 支点元素:上级非static元素
  • 多绝对定位元素产生覆盖时可通过z-index控制层级,层级非越大越好

multi inherit

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;
    }

gulp & transform & 责任链

gulp通过pipe将流传递到下个转换流中处理,转换流处理逻辑

  • 完成流的转换
  • 执行done函数,实现底层将数据push到下一个转换流或输出流
  • 如果多个流中通过转换流连接至最终输出流,则形成职责链
  • transform中数据先通过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));
调用堆栈

a5a88a1e5761f1c19c05b2263

vue

  • vue.2.5 dist目录分两类:完整、runtime两类;每类按照模块化方式分3个类型:common、esm、umd;加上umd的两个压缩文件,共计6个文件

fs

  • fs.open只能打开文件,不能打开目录
  • fs.access判断文件是否存在,fs.open/readFile/writeFile操作前通过access判断文件是否存在会引入竞态条件,导致文件判断不准确,可以直接操作,不用判断access

HTTPS

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

参考

for-of & for-in

  • for-of 遍历循环需要被遍历对象继承iterator属性,例如array,且遍历的是value而非key
  • for-in 遍历的是对象的key,数组对象遍历索引下标
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);
}

cross domain

遇到了一个关于跨域的问题,简单记录下。

chrome & mobile browser

当在A域通过jsonp访问B域下的接口(js),则之前B域下的cookie是可以与接口同步到后台

android webview

当在A域通过jsonp访问B域下的接口(js),则之前B域下的cookie是不能与接口同步到后台,应该是webview的安全策略不同于主流浏览器

运算

运算

  • 平方 Math.pow(2,32),2^32
  • 开方 Math.sqrt(27,3) 27开3次方
  • 或 | 两个位只要有一个为1,那么结果都为1,否则就为0
  • 与 & 两个位只要有一个为0,那么结果都为0,否则就为1
  • 异或 ^ 两个位相同为1,不同为0
  • 反码
  • 补码 ~
  • 移位运算 0b1111>>1 === 0b0111有符号右移2位,0b1111<<1 === 0b11110,-9>>>2无符号右移2位

原码,反码,补码

  • 原码 0b11111111 ~ 0b01111111 [-127,127]
  • 反码 正数:同原码;负数:符号位不变,各个位置取反
  • 补码 正数:同原码;负数:取补码,最后加1

原码, 反码, 补码

JS 整数

  • 64位操作系统,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布局复杂度。

1. table

table标签能够实现左右布局,table布局也是页面布局中使用的最早的布局方式,但是由于存在性能和灵活性问题,已被逐渐放弃,转而在采用div布局。

  • table,display:table;块级标签,但是具有包裹性
  • tr,display:table-row,表示一行
  • td,display:table-cell,表示一列

2. inline-block

  • display:inline-block,介于行级元素(display:inline)块级元素(display:block)之间的属性,
  • 可以像行级元素一样水平布局,也可以像块级元素设置宽高属性,所以可以进行左右布局。
  • 因为存在空白点问题,所以一般不用于大区块布局,常用于与行内元素构成的内容同行
  • 不支持ie6、7浏览器,请注意,但是可以使用inline进行hack处理。

3. float

  • 由于具有先左右后上线排布特性,所以成为常见实现左右布局方式
  • 本意为脱离文档流,实现文字环绕效果
  • 属性值包括none/left/right;left属性值使该区域向父级标签区域的左侧边界放置,right属性值使该区域块向父级标签的右侧边界放置

具有特性:

  • 包裹性:可以按照区域块中子元素的实际宽度进行包裹;
  • 破坏性:由于脱离文档流,float区域块不会被父级区域块包裹,造成高度塌陷,引起下文元素布局异常,引入常见清除浮动问题;

4. flexbox

弹性盒子主要用于移动前端开发,属性不支持ie6、7、8,支持chrome,firefox,不适合面向互联网用户pc网页布局。

  • display:flex
  • flex: 0 1 auto
  • 通过容器和项目,完成通用和个性管理

5. float+margin实现左右布局

float能够使得元素向左或者向右靠边布局,在同级元素中设置正常流区域与浮动块并列,浮动块会在正常流同级区域的边界处,并影响该正常流水平方向布局,在正常流区域块的盒子设置margin等于浮动块的宽度可以清除影响。

6. (float+overflow)实现左右布局

float对元素水平方向的影响,除采用margin进行影响的清除,依据块级格式上下文原理,还可以在受影响的元素上添加overflow:hidden来清除浮动对该区域块带来的影响。

7. position:absolute左右布局

绝对定位,通过对支点元素进行相对偏移实现左右布局

  • 支点元素:上级非static元素
  • 多绝对定位元素产生覆盖时可通过z-index控制层级,层级非越大越好

替换元素

替换元素的宽度是由内部元素的大小控制,不会受display的值影响,如果想干预宽高,需要人工外部设置宽高
替换元素默认display样式:

  • inline:img(0*0),video(300*150),object(300*150),iframe(300*150)
  • none: audio(300*54)
  • inline-block:input select textarea(136*30) button

js IP

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)

nodejs http

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

读写流备注

  • 写出流拥有写出功能,可以执行write,例如客户端client实例,服务端response
  • 读入流拥有读权限,可以执行数据接收功能,例如客户端response,服务端request
  • client通过socket事件可以获取数据通信客户端socket
    = server端通过connection事件可以获取数据通信服务端socket,
  • 两个socket是duplux流,可写可读

connect代理触发

  • client端method=connect建立隧道
  • server端on('connect',function(req,socket,head){}),method=connect请求直接接入

upgrade触发

headers: {
   'Connection': 'Upgrade',
   'Upgrade': 'websocket'
}

continue触发

headers: {
    'Expect': '100-continue'
}

前端开源说明

初步

接入小程序SDK后,前端swan.js是集成在SDK中的,SDK接入的APP(以下简称“宿主”)默认无需关心SDK上swan.js的更新/运行,但是宿主需要扩展swan对于小程序开发者的API/组件时,则需要为js运行时编写特定逻辑。

扩展

扩展都能做什么

目前编写扩展,可以给宿主带来三种能力:

  1. 扩展运行在当前宿主上的swan的API,运行在该宿主上的小程序开发者,可以在小程序运行在该宿主时,使用特定的API,如:swan.tieba.publishThread()
  2. 扩展运行时在当前宿主上的组件,运行在该宿主上的小程序开发者,可以在小程序运行在该宿主时,使用特定的组件,如:<bilibili-video></bilibili-video>
  3. 宿主可以将统计逻辑,添加在extension.js中。

扩展的编写

如果宿主需要在swan上扩展API或是扩展组件的话,则需要进行两步的操作:

  1. 在客户端,需要xxx;
  2. 在前端,需要编写一个extension.js,并上传到百度(目前可以以文件的形式提供给百度)。

extension.js的基础格式

// 宿主的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中,因为需要与宿主客户端进行交互,所以会提供一些开发者所没有的特定接口,编写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.jsmethods对象中的所有字段,会自动被merge到相对应的命名空间下,并在全局对象swan上提供给小程序开发者,如:'swan.tieba.publishThread()' 。

// 宿主的extension.js逻辑
module.exports = {
    name: 'tieba',
    // 扩展API
    methods: {
        publishThread: function (id) {
            swan.invoke('');
        }
    }
};

开发者可以在逻辑中直接编写scheme,与宿主客户端进行交互。

扩展组件方法

extension.jscomponents对象中的所有字段,将会自动生成对应的组件,并被加上命名空间为前缀,如上面的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 小程序的某个页面进行隐藏

Android 开源说明

名词解释

名词 解释
宿主 特指承载百度小程序的第三方宿主应用
小程序 特指百度小程序
Swan-Native 特指百度小程序端上NA SDK。
SwanJS 特指百度小程序前端JS SDK,又称SwanCore,用于和端上通信的桥梁、小程序API、组件实现的基础。
Scheme协议 指SwanJS和Swan-Native通信的标准协议
端能力 特指Swan-Native对SwanJS中某一API的实现

小程序lib说明

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

需要接入方实现的功能

1 二维码扫描

功能说明

调起客户端扫码界面,扫码成功后返回对应的结果。

原理

小程序调用 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 扫码的内容

2 位置

功能说明

  • 小程序里提供了获取当前位置地理坐标的能力,以供小程序开发者使用,如基于定位、位置推荐的小程序等。
  • 地理位置的坐标有一定的要求,根据文档 getLocation 描述,需要支持至少 wgs84 和 gcj02 两种坐标类型,默认为 wgs84 返回 gps 坐标,可选 gcj02。

设计原理

  • 在小程序前端开发者需要使用地理位置的时候,会通过JS的调用,最终调用到端上的接口。
  • 端上的接口,会去请求定位,定位是个耗时过程,所以在定位成功之后,异步的将结果返回给开发者。

接口设计

  • 一共只有3个接口需要实现
  • 接口都在 IAiAppLocationIoc.java 中。
  • 预初始化定位服务,为了在使用定位的时候,快速的返回定位结果,减少定位耗时(第三方可不实现)
   	/**
        * 通过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);
       }
  • 以Observer形式进行定位(接口可能后续会删除,目前对外无使用场景,第三方可不用实现)
    	/**
        * 获取地理位置信息
        *
        * @param context 上下文
        * @return 地理位置信息(Observable异步)
        */
       @Nullable
       Observable<AiAppsLocationInfo> getLocationInfo(Context context);

参考实现

无,完全有第三方自己实现,没有固定格式和规范

3 分享

功能说明

小程序菜单点击分享,调起宿主分享功能,此功能需要宿主实现,调用宿主的分享面板

原理

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);

4 图片

功能说明

图片功能主要分为图片预览图片选择

  • 图片预览:查看图片

  • 图片选择:从本地相册选择图片或使用相机拍照。

实现细节

图片预览

小程序调用 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 用户所选择的本地图片路径

5 地图

功能说明

  • 地图功能包含(具体参考下文的使用文档):

    • 在指定的页面上创建指定大小的地图 View
    • 在地图的 View 里绘制气泡、折线、Marker、文字、圆形,控件等
    • 响应地图 View 上各种控件和绘制的交互,如点击事件、经纬度变化事件等等
    • 响应地图 View 的大小,经纬度,绘制元素的位置和大小更新等
    • 删除地图组件
    • 移动地图上的 Marker 图标到指定的经纬度
    • 打开指定地图并定位到指定的经纬度
    • 缩放地图视野
    • 获取地图缩放比例
    • 获取地图显示框内的边框四个点经纬度
    • 获取地图当前中心的经纬度
    • 打开一个新页面从中选择一个地点(返回经纬度)
    • 进行步行导航和 AR 导航
    • 其他细节等
  • 推荐使用百度地图的 SDK 进行接入,有详细的实现代码,接入成本较低;其他地图SDK需要重新实现下文 接口设计 中的接口。

  • 地图的使用和开发参考小程序文档:

    • API文档
    • 地图组件文档
    • 文档中暴露给小程序开发者的字段和参数,最终也会透传到端的SDK的接口里,接入者需要做的就是实现SDK里的接口,将这些参数做处理,以作出响应。

设计原理

  • 首先,webView 会和一个 NA 的 Root 容器(FrameLayout,盖在 webView上)平级,一起插入到 Android 的 View 树上。
  • 然后,所有的组件都会有一个 View,这个 View 会放入到一个子容器中(AiAppsNAViewContainer),然后这个子容器再放到NA的Root容器中进行显示,占用的 webView 的空间,JS 中会预留出来,不会被覆盖。
  • 地图组件也是一个 View,在收到创建的命令后,会产生一个地图的View,然后根据参数将整个 view 放到 AiAppsNAViewContainer,再进一步放到 NA 的R oot容器中进行显示。
  • 在把地图的 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 标记

    	/**
         * 移动指定的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;
     }
    

其他说明

参考代码只提供大概框架,具体细节未提供。

6 地址、发票

功能说明

地址、发票功能指 swan.chooseAddressswan.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 收货人手机号码

3.7 夜间模式

功能说明

夜间模式指小程序夜间模式功能,主要需要实现和宿主夜间模式的状态联动,同时需要宿主维护夜间模式状态

原理

小程序夜间模式使用蒙层压黑的方式实现
小程序运行与独立的进程,跨进程状态实现通过小程序内置的CS通信机制(Messenger实现),涉及AiAppsMessengerServiceAiAppsMessengerClient

实现

主进程 -> 小程序进程
在宿主夜间模式发生变化时,调用:

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);

8 支付

暂时未确定开源方式,后续补充

9 文档

功能说明

  • 小程序SDK对外部开发者提供了打开文档的能力,可以打开指定文件类型打开文件,有效值 doc, xls, ppt, pdf, docx, xlsx, pptx。
  • 小程序开发者使用的文档和参数定义在 openDocument
  • 小程序开发者使用的 openDocument 最终会调用到小程序的SDK内部,由端上实现具体开打的能力。

设计原理

  • 目前打开文档的能力是通过插件来实现的,当需要打开指定类型文档的时候,实际上是调启了插件去打开。
  • 接入方需要自己实现这个具体的打开过程,小程序sdk会在接口参数中,将具体的类型和其他参数传递出来。

接口设计

  • 一共只有2个接口需要实现(不稳定接口,后续可能会优化)
  • 接口都在 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);
}

10 用户反馈

功能说明

  • 小程序的菜单里,提供了用户反馈的功能,可以将用户的反馈信息上传到服务器。
  • 这个用户反馈只提供一个简单的接口,用来调起反馈的入口,所有的页面和反馈逻辑需要第三方自己实现。
  • 反馈结束回到小程序的时候,需要把反馈的结果回调过来。

设计原理

  • 小程序的菜单里有反馈的按钮,默认是显示反馈功能的,如果不需要可以直接修改menu的源码,去掉此功能即可。
  • 用户点击整个按钮的时候,会调用一个接口,这个接口负责展示用户反馈的页面,让用户填写反馈信息。

接口设计

  • 一共只有1个接口需要实现(不稳定接口,后续可能会优化)
  • 接口都在 IAiAppFeedbackIoc.java中。

调起反馈

/**
* 调启反馈页面
*
* @param params   反馈参数
* @param callback 反馈结果
*/
void feedback(Bundle params, OnFeedbackResultCallback callback);

/**
* 反馈结果回调
*/
interface OnFeedbackResultCallback {
   /**
    * 反馈的结果
    *
    * @param data 反馈结果数据
    */
   void onResult(String data);
}

参考实现

  • 无,完全有第三方自己实现,没有固定格式和规范

11 直播

功能说明

小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是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);
}

12 背景音乐

功能说明

小程序提供了背景音频播放能力,通过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);
}

13 视频

功能说明

智能小程序提供了视频播放能力,通过实现 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();
}

14 音频

功能说明

小程序提供了通用音频播放能力,通过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);
}

15 端能力回调说明

接口中回调函数说明

  • 第三方实现的,需要和JS交互的每个接口(在/aiapps/lib-ng-aiapps/src/main/java/com/baidu/searchbox/ng/ai/apps/ioc/interfaces里),都必须将端上的处理结果返回或通知到JS,否则小程序的开发者无法收到回调,逻辑无法继续。
  • 目前这个回调需要接如小程序 SDK 的第三方自己去调用,就是每个接口的 CallbackHandler handler 和 UnitedSchemeEntity entity 参数。
  • 根据逻辑的不同,端上有同步处理异步处理两种情况。
    • 同步处理:在完成相应的逻辑之后,直接调用以下代码,告诉前端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));
      }
    
  • 异步回调(又叫二级回调)

    • 以地图的移动 marker 标记为例(和地图真实接口不同,这里仅仅做示例使用)
      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, "");
                        }
                    }
                });
    }
}
 ```

.需要接入方实现的服务

1 登录

功能说明

小程序登录功能(调用宿主登录),和宿主强相关

原理

需要宿主维护自己的账号体系

实现

标准接口,后续提供

2 授权

功能说明

在符合法务要求的情况下赋予小程序调用必要功能的权限,比如用户信息、直播、地理位置等
敏感功能权限风控,可以随时由用户或者云端平台撤回授权
用户的敏感信息都从开放平台统一输出

原理

暂无,后续提供

实现

暂无,后续提供

5.小程序核心结构、流程

暂无

6.第三方接入步骤说明

在小程序正式开源前,会随时更新,不保证时效性,当前为百度内部接入流程

小程序SDK的仓库地址

http://icode.baidu.com/repos/baidu/searchbox-android/aiapps/tree/master

小程序结构说明

  • 小程序由3部分构成:客户端 SDK、前端 JS SDK、后端服务。
  • 接入 SDK 需要同时处理好这个3个部分。
  • 客户端 SDK:主要用于提供系统级能力,如视频播放、直播、弹框、登录等等。
  • JS SDK:预置在客户端 SDK 内部,可以通过网络自动更新,运行于客户端 SDK 的 WebView里,提供页面和组件的展示和处理逻辑。
  • Server:小程序JS SDK 和第三方开发的小程序包需要通过 Server 下发,登录和授权认证也需要走 Server。

初始化说明

  • 将配置好依赖的库,参考根目录的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时,应该跳转到二维码扫描界面,二维码扫描结果应该通过返回给调起的小程序。

接口设计

  • 扫描二维码功能需要接入方使用自己的扫面界面,并将扫描结果回传给小程序框架
  • 所有接口都在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 扫码的内容

位置

功能说明

  • 小程序里提供了获取当前位置地理坐标的能力,以供小程序开发者使用,如基于定位、位置推荐的小程序等。
  • 地理位置的坐标有一定的要求,根据文档getLocation描述,需要支持至少wgs84和gcj02两种坐标类型,默认为 wgs84 返回 gps 坐标,可选 gcj02。
    设计原理
  • 在小程序前端开发者需要使用地理位置的时候,会通过JS的调用,最终调用到端上的接口。
  • 端上的接口,会去请求定位,定位是个耗时过程,所以在定位成功之后,异步的将结果返回给开发者。
    接口设计
  • 一共只有3个接口需要实现。
  • 接口都在 IAiAppLocationIoc.java 中。
  • 预初始化定位服务,为了在使用定位的时候,快速的返回定位结果,减少定位耗时(第三方可不实现)。
    /**
     * 通过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);
}
```

  • 以Observer形式进行定位(接口可能后续会删除,目前对外无使用场景,第三方可不用实现)
     /**
     * 获取地理位置信息
     *
     * @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 Array|	图片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 paths);
    }

|参数名|类型|说明|
|---|---|---|
|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标记
    /**
        * 移动指定的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.chooseAddressswan.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 收货人手机号码

3.7 夜间模式

功能说明

夜间模式指小程序夜间模式功能,主要需要实现和宿主夜间模式的状态联动,同时需要宿主维护夜间模式状态

原理

小程序夜间模式使用蒙层压黑的方式实现
小程序运行与独立的进程,跨进程状态实现通过小程序内置的CS通信机制(Messenger实现),涉及AiAppsMessengerServiceAiAppsMessengerClient

实现

主进程 -> 小程序进程
在宿主夜间模式发生变化时,调用:

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);

3.8 支付

暂时未确定开源方式,后续补充

3.9 文档

功能说明

  • 小程序SDK对外部开发者提供了打开文档的能力,可以打开指定文件类型打开文件,有效值 doc, xls, ppt, pdf, docx, xlsx, pptx。
  • 小程序开发者使用的文档和参数定义在openDocument
  • 小程序开发者使用的openDocument最终会调用到小程序的SDK内部,由端上实现具体开打的能力。

设计原理

  • 目前打开文档的能力是通过插件来实现的,当需要打开指定类型文档的时候,实际上是调启了插件去打开。
  • 接入方需要自己实现这个具体的打开过程,小程序sdk会在接口参数中,将具体的类型和其他参数传递出来。

接口设计

  • 一共只有2个接口需要实现(不稳定接口,后续可能会优化)
  • 接口都在 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);
}

3.10 用户反馈

功能说明

  • 小程序的菜单里,提供了用户反馈的功能,可以将用户的反馈信息上传到服务器。
  • 这个用户反馈只提供一个简单的接口,用来调起反馈的入口,所有的页面和反馈逻辑需要第三方自己实现。
  • 反馈结束回到小程序的时候,需要把反馈的结果回调过来。

设计原理

  • 小程序的菜单里有反馈的按钮,默认是显示反馈功能的,如果不需要可以直接修改menu的源码,去掉此功能即可。
  • 用户点击整个按钮的时候,会调用一个接口,这个接口负责展示用户反馈的页面,让用户填写反馈信息。

接口设计

  • 一共只有1个接口需要实现(不稳定接口,后续可能会优化)
  • 接口都在 IAiAppFeedbackIoc.java 中。

调起反馈

/**
* 调启反馈页面
*
* @param params   反馈参数
* @param callback 反馈结果
*/
void feedback(Bundle params, OnFeedbackResultCallback callback);

/**
* 反馈结果回调
*/
interface OnFeedbackResultCallback {
   /**
    * 反馈的结果
    *
    * @param data 反馈结果数据
    */
   void onResult(String data);
}

参考实现

  • 无,完全有第三方自己实现,没有固定格式和规范

3.11 直播

功能说明

小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是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);
}

3.12 背景音乐

功能说明

小程序提供了背景音频播放能力,通过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);
}

3.13 视频

功能说明

智能小程序提供了视频播放能力,通过实现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();
}

3.14 音频

功能说明

小程序提供了通用音频播放能力,通过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);
}

3.15 端能力回调说明

接口中回调函数说明

  • 第三方实现的,需要和JS交互的每个接口(在/aiapps/lib-ng-aiapps/src/main/java/com/baidu/searchbox/ng/ai/apps/ioc/interfaces里),都必须将端上的处理结果返回或通知到JS,否则小程序的开发者无法收到回调,逻辑无法继续。
  • 目前这个回调需要接如小程序SDK的第三方自己去调用,就是每个接口的CallbackHandler handler 和 UnitedSchemeEntity entity参数。
  • 根据逻辑的不同,端上有同步处理异步处理两种情况。
    • 同步处理:在完成相应的逻辑之后,直接调用以下代码,告诉前端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));
      }
    
  • 异步回调(又叫二级回调)

    • 以地图的移动marker标记为例(和地图真实接口不同,这里仅仅做示例使用)
      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, "");
                        }
                    }
                });
    }
}
 ```

4.需要接入方实现的服务

4.1 登录

功能说明

小程序登录功能(调用宿主登录),和宿主强相关

原理

需要宿主维护自己的账号体系

实现

标准接口,后续提供

4.2 授权

功能说明

在符合法务要求的情况下赋予小程序调用必要功能的权限,比如用户信息、直播、地理位置等
敏感功能权限风控,可以随时由用户或者云端平台撤回授权
用户的敏感信息都从开放平台统一输出

原理

暂无,后续提供

实现

暂无,后续提供

5.小程序核心结构、流程

暂无

6.第三方接入步骤说明

在小程序正式开源前,会随时更新,不保证时效性,当前为百度内部接入流程

小程序SDK的仓库地址

http://icode.baidu.com/repos/baidu/searchbox-android/aiapps/tree/master

小程序结构说明

  • 小程序由3部分构成:客户端SDK、前端JS SDK、后端服务。
  • 接入SDK需要同时处理好这个3个部分。
  • 客户端SDK:主要用于提供系统级能力,如视频播放、直播、弹框、登录等等。
  • JS SDK:预置在客户端SDK内部,可以通过网络自动更新,运行于客户端SDK的WebView里,提供页面和组件的展示和处理逻辑。
  • Server:小程序JS SDK 和第三方开发的小程序包需要通过Server下发,登录和授权认证也需要走Server。

初始化说明

  • 将配置好依赖的库,参考根目录的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)


iOS开源说明

iOS 开源说明

小程序依赖第三方库

外部开源库

说明
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 加密库

小程序接入说明

  1. 将所有依赖库添加到工程文件配置中。
  2. 在appDelegate中的didFinishLaunchingWithOptions方法中 添加小程序初始化代码。如下:
    // 调起协议插件注册
    [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"]];
  1. 在宿主app中,添加一个子project,并且添加xxxxModule文件,遵守并实现pyramid的BBPModuleProtocol协议。
    // .m文件中添加该宏命令
    ModuleDefine(xxxxModule类名)
  1. 实现BBPModuleProtocol中的moduleRegister方法,注册宿主实现的功能及服务。
    - (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];
    }

接入方需实现的功能和服务

1. 分享

功能说明

小程序菜单点击分享,调起宿主分享功能,此功能需要宿主实现,调用宿主的分享面板

相关协议

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
}


2. 图片

功能说明

  • 图片预览:查看图片
  • 图片选择:从本地相册选择图片或使用相机拍照。

相关协议

  • BBAMNPPhotoProtocol - 图片选择
  • BBAMNPPlatformProtocol - 图片预览

接入方法

  • 图片选择
使用方法

遵循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
}


3. 地图

功能说明

相关协议

  • BBAMNPMapProtocol - 地图绘制、交互
  • BBAMNPMapLocationProtocol - 地图上显示路线、步行导航
  • BBAMNPMapSearchProtocol - 检索

接入方法

1、地图绘制、交互

  • 需要实现BBAMNPMapProtocol
 /**
 绑定上下文信息
 */
@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;


  • BBAMapKitControllerDelegate
   /**

 获取相对路径的全路径

 @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;


2、地图上显示路线、步行导航

  • 需要实现BBAMNPMapLocationProtocol
   //显示路线

-(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 //距离太远

};


3、检索

  • 需要实现BBAMNPMapSearchProtocol
   @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;

示例

4. 定位

功能说明

  • 小程序里提供了获取当前位置地理坐标的能力,以供小程序开发者使用,如基于定位、位置推荐的小程序等。
  • 地理位置的坐标有一定的要求,根据文档getLocation描述,需要支持至少wgs84和gcj02两种坐标类型,默认为 wgs84 返回 gps 坐标,可选 gcj02。

相关协议

  • BBAMNPLocationProtocol

接入方法

  • 需要实现BBAMNPLocationProtocol中的以下2个方法
		/**
 		* 获取位置信息-异步
 		* @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;
  • 在宿主app在实现协议方法时,添加系统权限的check。小程序不会触发和接管权限请求。
  • 定位结果BBAMNPLocationData中,需要宿主app尽可能多的提供逆地理信息。

示例

  • 无,完全有第三方自己实现,没有固定格式和规范

5. 支付

功能说明

  • 小程序为开发者提供了支付宝、微信、百度钱包三个平台的直连支付API。同时,还提供了一个聚合收银台的API供开发者使用。
  • 宿主app需要自行接入各支付平台的SDK,并实现相关的支付能力。
  • 微信支付目前使用的是H5支付流程,整个流程完全由swanNative实现,不需要宿主app做额外的工作。

相关协议

BBAMNPPaymentProtocol

接入方法

  • 需要实现BBAMNPPaymentProtocol中的以下2个方法
		/**
 		* 百度钱包
 		* @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;
  • 宿主app在实现以上两个接口时,需要注意所有的参数透传即可,不需要额外加工。
  • 聚合支付待方案确定后补充

示例

  • 无,完全有第三方自己实现,没有固定格式和规范

6. 直播

功能说明

小程序提供视频直播的能力,可以支持播放器在线推流的视频。对应开发者接口是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;

7. 视频

功能说明

智能小程序提供了视频播放能力,通过实现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实现这些接口即可

  • root导航栈
	/**
	 * 获取小程序的根导航栈
	 * @discussion 获取小程序的根导航栈
	 * @return 小程序即将push进入的UINavigationControlle
	 */
	+ (UINavigationController *)rootNavigationController;
  • UA (目前UA种类较多,后续会统一)
	+ (NSString *)userAgentForRequest;
	+ (NSString *)userAgentForWebView;
	+ (NSString *)userAgentForMNP;
  • 公共参数
	+ (NSString *)composeParameters;
  • 宿主app名称
	+ (NSString *)hostName;
  • 宿主app版本号
	+ (NSString *)hostVersion;

request ua

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'
    }
})

String&&Number--binary

  • String('a').charCodeAt // 获取字符串对应数字编码
  • String.fromCharCode
  • Number(0x1f).toString(16); // 数字转化为16进制
  • parseInt(0x1f, '16'); // 将0x1f对应10进制按照16进制解析,即0x31

mysql [insert values] vs [insert set]

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
一次只能添加一条

事件代理模拟

利用事件冒泡实现类似$('dom').on('click','.name,.age',function(e){})的调用函数;

event loop

// 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

引用自Macrotask Queue和Microtask Queue

FE interview

原文

阿里

使用过的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结构是一个类数组

    类数组和数组的区别

    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深度优先

    介绍下观察者模式

  • FIS3

    深度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;
    }

    git

    rebase

    1. git rebase refer,将(当前分支)与(refer所在版本线)的历史共同版本起始的后续版本的所有提交以refer版本为基础,每个版本重新提交,复用提交日志
    2. git rebase -i refer,交互式版本重载,后续配合指令git rebase --continue|abort|skip

    cherry-pick

    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/

    update-index 忽略已经入库文件的后续修改

    • git update-index --assume-unchanged config/config.local.ts 忽略
    • git update-index --no-assume-unchanged config/config.local.ts 取消忽略

    算法&原理

    • 冒泡排序

    • 快速排序

    • 深度复制对象

    • 继承

    • 链表

    • promise实现

    • generator

    • Sizzle

    • virtual DOM

    • vNode diff

    • vNode patch

    • b-tree

    • abstract syntax tree (AST)

    • xss、csrf、arp、xff、中间人攻击、运营商劫持、防暴刷

    • 缓存

    • AMD CMD UMD (module/define实现)

    • https://juejin.im/post/5958bac35188250d892f5c91

    Readable&Writeable&Duplex

    Readable

    可读流使用

    流模式
    • data
    • end pause resume
    暂停模式
    • readable read

    实现可读流

    • 数据获取,重写_read()方法,其中通过push完成读取数据的下游推送,当push(null)时,标识下游数据推送完毕,触发上游end事件。

    Writeable

    可写流使用

    • write(),返回bool,成功写入返回true,失败返回false,此时可监听drain事件重新继续写入
    • end() finished事件

    实现可读流

    • 读的速度远大于写的速度,实现pipe
    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);

    buffer

    buffer<--TypedArray(Uint16Array,Int32Array..)<--ArrayBuffer

    node中buffer是基于TypedArray封装,TypedArray又是基于ArrayBuffer封装,ArrayBuffer代码存储空间,以字节为单位,TypedArray代表一种存储方式,例如Uint16Array,标识使用两个字节(即两个ArrayBuffer)存储一个符号;Int32Array,4个ArrayBuffer存储一个字符

    sort

    • bubble sort
    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);
    
    • quick sort
    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);

    height与min-height

    问题参考

    <!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>
    

    我的理解:

    1. min-height表示盒子的最小高度,并不表示盒子的最终高度,站在外部盒子的角度理解是外部最终高度是由盒子的内部元素的高度与当前盒子的最小高度共同决定
    2. 在外部盒子等待内部元素计算出高度时并不知道自己的高度,内部盒子高度相对外部盒子设置高度时也未知外部高度,所以按照自己的内容的情况渲染了,最终的效果就是内部盒子高度与外部无关。

    throttle & debounce

    /**
     * 函数节流(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);

    http header host

    • http请求发出后,首先客户端会将请求url转化为http报文,其中包括请求头和请求体,请求头中host根据当前url可以得到,同时host可以人为修改为与当前url域名不一致的情况
    • 只要服务器能够在同一台机器(即,在同一个 IP 地址)上提供多个不同的主机名,服务器就可以通过 Host 首部,根据主机名来区分不同的相对 URL,所以服务器知道域名的方式就是从header的host中获取,请求的域名是无法直传给服务器的
    • http报文构造完成,报文被发送到tcp层,tcp层会依据原始请求域名通过dns获取目标机器ip,将http报文封装成传输层报文,然后转递到ip成->数据链路层->物理层,完成封装发送

    flex

    容器标签属性

    1. display:flex
    2. flex-direction: row || column || row-reverse || column-reverse; 决定子元素排列方向,决定main-axis
    3. flex-wrap: wrap || nowrap || wrap-reverse; 决定子元素过多时换行情况;纵列时过多从上向下换列
    4. flex-flow是flex-direction和flex-wrap的速记,先写direction后写wrap
    5. justify-content: flex-start || flex-end || center || space-between || space-around 决定main-axis单行方向上的对齐方式
    6. align-items: flex-start || flex-end || center || stretch || baseline 决定cross-axis方向的单列元素对齐方式
    7. align-centent: flex-start || flex-end || center || stretch(沿Cross-Axis填充) 决定多行多列的对齐方式

    项目属性

    1. order: 类似z-index,值越大越在后面,默认都是0
    2. flex-grow: 默认0,关闭自动放大
    3. flex-shrink: 默认1,开启自动缩小
    4. flex-basis: 指定项目的初始大小,默认的值是auto。flex-basis可以取任何用于width属性的任何值。比如 % || em || rem || px等
    5. flex: 是flex-grow、flex-shrink和flex-basis三个属性的速记。默认值flex: 0 1 auto; | flex: 1 1 auto | flex: 0 0 auto | flex: 2 1 0(扩张比例大)
    6. align-self: auto || flex-start || flex-end || center || baseline || stretch 改变一个弹性项目沿着侧轴的位置,而不影响相邻的弹性项目

    问题记录

    1. 高度触发方式
      flex-basis优先级是比height高的
    .auto{
      flex: 1 1 auto;
      height: 200px
    }
    
    .h100{
      flex: 1 1 100px;
      height: 200px
    }

    map

    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])));
    

    img display none

    img[src='http://g.hiphotos.baidu.com/video/pic/item/77c6a7efce1b9d16f160d9faffdeb48f8d546481.jpg'] display:none 会load图片

    vscode

    在 Ctrl+P 窗口下还可以:

    直接输入文件名,跳转到文件
    ? 列出当前可执行的动作
    ! 显示 Errors或 Warnings,也可以 Ctrl+Shift+M
    : 跳转到行数,也可以 Ctrl+G 直接进入
    @ 跳转到 symbol(搜索变量或者函数),也可以 Ctrl+Shift+O 直接进入
    @ 根据分类跳转 symbol,查找属性或函数,也可以 Ctrl+Shift+O 后输入:进入
    # 根据名字查找 symbol,也可以 Ctrl+T
    

    https://www.cnblogs.com/sxz2008/p/6595796.html

    linked list

    • 单向链表
    • 单向循环链表
    • 双向链表
    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();
    • 双向循环链表

    deepClone

    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;
    };

    vue

    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);
    };

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.