https

思路

​ 假设甲跟乙需要通信,他们之间隔着若干个中间人(路由,网关)。他们说什么这些中间人都可以截取到。

​ 如果是http的方式,甲跟乙的通话都是明文,那么这些中间人可以非常轻易的获取到甲跟乙的通话内容,如果这些通话内容涉及一些隐私内容,那么这种方式就不太合适了。

​ 对于这个问题,我们对明文加密就可以了。这样即使中间人看到了甲乙的通话内容,也没有关系。下面考虑选择什么方式进行加密呢?对称还是非对称?

​ 首先考虑对称加密,对称加密需要甲跟乙有相同的秘钥才行。那这个秘钥要怎么传输呢?(鸡生蛋,蛋生鸡)。甲跟乙当然可以私下约定一个秘钥,只有他两个知道。但是甲跟丙通信,甲跟丁通信,等等呢。在只有一对一通信的场景下,私下约定也可以作为一个不错的选择。但是实际情况大多都不是一对一的。对称加密不太合适。

​ 那我们考虑使用非对称加密。甲跟乙都有自己的公钥跟私钥,甲跟乙通信的时候都用对方的公钥加密。这样解决了使用对称加密的问题。但是非对称加密耗时且对加密的长度有限制,在实际情况中也并不十分合适。

​ https对明文加密这块选择的是非对称跟对称结合的方式。

​ 甲跟乙说我要跟你对话,乙将自己的公钥发给甲,甲生成一个随机的字符串作为对称加密的秘钥,然后用乙的公钥对该秘钥加密之后发送给乙。乙解密之后就有了对称加密的秘钥。之后的通信内容都用该秘钥进行加密。

​ 以上解决明文传输的问题。

再考虑一个新的问题,乙将自己的公钥发给甲的过程中,中间人也收到了乙的公钥,假如中间人把自己的公钥发给甲,然后甲同样生成一个随机的字符串作为对称加密的秘钥,之后用中间人发的公钥对对称加密的秘钥加密之后发给中间人,中间人再用自己的私钥解密,此时中间人就获取了对称加密的秘钥,之后它再用乙的公钥对对称加密的秘钥加密之后转发给乙。在整个过程中甲跟乙都没有办法check到有个中间人修改了通信内容。

其实这个问题说的底是甲没有办法确认自己收到的公钥是乙的公钥而不是其他人的。

为了解决上面的问题,我们需要引入如下概念:

ca

数字签名

数字证书

​ ca在网络通信中扮演的角色相当于现实世界中的公证处,它拥有自己的公钥跟私钥。我们可以认为这个私钥只有该公证处知道,那些中间人是不可能知道的。

​ 数字签名跟现实生活中的签名作用是一样的。可以唯一标识一个“人”。

​ 数字证书在现实生活中相当于身份证,它有你的个人信息,还有你的签名(身份证号),还有公钥。

​ 它们三者之间的关系是这样的,ca会给乙颁发数字证书,该证书中包含乙的公钥,乙的个人信息以及乙的数字签名。签名制作过程是这样的,ca会对数字证书中的个人信息,以及公钥做个hash,算出信息摘要,之后用自己的私钥对该信息摘要加密生成数字签名。

​ 我们接着思考上述问题,首先甲跟乙说我要跟你对话,之后乙不是将公钥而是将ca给自己的证书发给甲,首先中间人会收到该证书,假设中间人用自己的公钥替换了证书中的公钥,(因为它没有ca的私钥,所以它没有办法修改证书中的签名),之后他把伪造的证书转给甲。甲收到证书之后首先会对证书中的信息做同样的hash运行获取信息摘要。之后用ca的公钥对证书中的签名做解密,获得另一个信息摘要,之后比对这两个信息摘要,因为中间人对证书中的公钥做了修改,所以这两个信息摘要肯定是不一致的,这样甲就知道了我即将使用到的公钥是伪造的。

​ 或者中间人也跟ca申请到了自己的证书,然后它将自己的证书给了甲,这种情况下,甲会对证书中的个人信息做一个检查,很轻易的甲就会发现我想要访问的跟我真实访问的并不是同一个对象。

概念

HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

解决什么问题

  1. 通信过程中明文传输问题
  2. 通信过程中中间人攻击问题

怎么解决的

  1. 数据加密传输,对称跟非对称加密
  2. 服务端身份认证,证书(数字签名)

ArrayMap vs HashMap

HashMap

Java 8 对接口做了进一步的增强。

a. 在接口中可以添加使用 default 关键字修饰的非抽象方法。即:默认方法(或扩展方法)

b. 接口里可以声明静态方法,并且可以实现。

1
2
3
4
5
6
7
8
public interface TTT {
static void test(){
System.out.println("hhhh");
}
default void sayHi() {
System.out.println("hhh");
}
}

android setContentView源码分析

view是如何添加到屏幕上的?

从setContentView出发->

1
2
3
4
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

可以看到调用的是getWindow的setContentView方法,

那么getWindow是啥呢?

1
2
3
public Window getWindow() {
return mWindow;d
}

接着看Window

1
2
3
4
5
6
7
 <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
...
}

注意上面的注释,该类唯一的实现是phoneWindow,到这里我们就知道了这个的getWindow实质返回的是一个PhoneWindow的实例。

接着看PhoneWindow的setContentView方法

1
2
3
4
5
6
7
8
9
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}

只需要关注上面的几行代码,如果mContentParent是空,那么调用installDecor方法(如下)。这里的mContentParent是什么呢?看定义的地方它是一个ViewGroup,它里面具体是啥呢?我们继续往下跟。

1
2
3
4
5
6
7
8
9
10
private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor(-1);
}
...
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}

这里又出现了mDecor,它又是个啥呢。跟踪generateDecor

1
2
3
4
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}

可以看到mDecor是一个DecorView对象

1
public class DecorView extends FrameLayout

它其实就是一个FrameLayout,那么它里面具体是啥呢?我们往下跟

回到installDecor方法,我们再跟下generateLayout方法是如何创建mContentParent的?

1
2
3
4
5
6
7
8
9
10
11
12
13
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//这里设置一些主题相关的
...
// Inflate the window decor.
int layoutResource;
// 给layoutResource 赋值(实质是找一个系统布局资源)

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...··
return contentParent;
}

这里有一个layoutResource,这个实质就是一个系统的layout xml文件。会根据我们主题不同给它赋不同的值。

接着我们看onResourcesLoaded方法

1
2
3
4
5
6
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
...
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

重点关注这两行代码,还记得我们之前分析过mDecor实质是一个frameLayout,这里会把系统的布局文件添加到DecorView里面。到这里我们就知道了DecorView里面具体是啥了。

我们再回到generateLayout方法,会调用PhoneWindow的findViewById方法找一个id,赋值给contentParent返回。这里的findViewById方法实际是getDecorView().findViewById。实质是在mDecor中根据id找它的一个子view。到这里我们就知道了mContentParent具体是个啥了。

我们回到PhoneWindow的setContentView方法

1
2
3
4
5
6
7
8
9
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}

最终会把我们传进来的布局文件填充到mContentParent中。至此,view添加结束。

总结:

PhoneWindow —> DecorView(根据主题不同对应系统的不同布局文件) — >mContentParent(系统布局文件中中的一个子view)—> 我们自己写的布局文件会被加载到mContentParent中。

Android-gradle

VS maven ant

gradle 是基于groovy的编程语言,比maven,ant强大很多。

groovy

基本概念

属于领域特定语言 (eg: matlab(计算) html(网页))解决特定问题的语言;

基于jvm,可以将groovy编译成.class在jvm上执行,也可以直接解释执行(js);

结合了python,ruby等脚本语言的特性;

可以跟java完美结合,可以使用java库;

支持动态类型,闭包;

支持面向对象,也支持面向过程(写脚本);

环境搭建

官网下载groovy—sdk;

安装jdk;

安装idea;

新建项目

idea中new project,选择groovy,指定groovy sdk即可;

语法
  1. 基本语法

    变量类型:跟java一致,没有基本类型,自动装箱;

    变量定义:def + 强定义 自己用的话使用def,提供给外界用的话,使用强定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int x = 10
    println x.class

    def y = 2.3
    println y.class

    def name = "name"
    println name.class


    结果:
    class java.lang.Integer
    class java.math.BigDecimal
    class java.lang.String
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    package variable;
    /***********字符串定义**********/

    //三引号
    def s = '''\
    line one
    line two
    line three '''
    println s
    //可扩展字符串 GString
    def s_1 = "this is a common String"

    def temp = "temp"
    def s_2 = "hello: ${temp}"

    println s_1.class
    println s_2.class

    def sum2 = "2+3=${2 + 3}"
    println sum2
    //不需要强转
    def result1 = echo(sum2)
    println result1

    String echo(String msg) {
    return msg
    }

    /**********字符串方法*************/
    def str = "groovy"
    println str.center(8, 'a')
    println str.padLeft(8, 'a')

    def str2 = 'vy'
    println str > str2

    println str[2]
    println str2[0..1]

    println str.minus(str2)
    println str-str2

    println str.reverse()

    println str.capitalize()

    def str3="1"
    println str3.isInteger()
    println str3.toInteger()

    /**********swith case***********/
    def x = 1.23
    def result
    switch (x) {
    case "foo":
    result = 'found foo'
    break;
    case [4, 5, '123']:
    result = 'list'
    break
    case 1..4:
    result = 'range'
    break
    case Integer:
    result = 'integer'
    break
    case BigDecimal:
    result = 'big decimal'
    break
    default:
    result = 'default'
    break;
    }
    println result

    /**********for ***********/
    def sum = 0
    for (i in 0..9) {
    sum += i
    }
    println sum

    for (i in [1, 2, 3]) {
    sum += i
    }

    println sum

    for (i in ['lili': 1, 'zs': 3]) {
    sum += i.value
    }

    println sum
  2. 闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    package variable
    /**
    * 闭包
    */
    /***************基础****************/
    //箭头前面是参数,后面是方法体
    def clouser = { String params, int age ->
    println "hello ${params} , ${age}"
    }
    clouser.call('clousure', 3)
    clouser('clousure', 7)

    def it = {
    println "hello ${it}"
    }
    it(1)

    def re = { String name ->
    return "hello ${name}"
    }
    println re("clousure")

    /**************进阶********************/
    //求10的阶乘

    int fab(int number) {
    int result = 1
    1.upto(number, { num -> result *= num })
    return result
    }

    println fab(5)

    int fab2(int number) {
    int result = 1
    number.downto(1) {
    num ->
    result *= num
    }
    return result
    }

    println fab2(5)

    //累加
    int cal(int number) {
    int result = 0
    number.times {
    num -> result += num
    }
    return result
    }

    println cal(5)


    def tes = "a.b.c"
    println tes.substring(tes.lastIndexOf("."))
    /***********字符串跟clousure结合**************/
    String wzq = "wzq and zpy 1"
    wzq.each {
    String temp ->
    print temp
    }
    println()

    println wzq.find {
    String temp ->
    (temp == "d")
    }

    println wzq.findAll {
    String temp ->
    temp == "z"
    }

    println wzq.any {
    String s -> s.isNumber()
    }

    println wzq.every {
    String s -> s.isNumber()
    }

    def collect = wzq.collect {
    it.toUpperCase()
    }
    println collect.toListString()
    /************进阶2*************/
    //this owner delegate
    def wzqClouser = {
    println this
    println owner
    println delegate
    }
    wzqClouser.call()

    class Person {
    def pClousure = {
    println this
    println owner
    println delegate
    }

    def say() {
    def pClousure = {
    println this
    println owner
    println delegate
    }
    pClousure.call()
    }
    }

    def person = new Person()
    person.pClousure.call()
    person.say()

    def outClousure={
    def innerClousure={
    println this //clousure
    println owner //outClousure
    println delegate //outClousure
    }
    innerClousure.call()
    }
    outClousure.call()
    //委托策略

    class Student{
    String name
    def pretty ={"My name is ${name}"}

    String toString(){
    pretty.call()
    }
    }

    class Teacher{
    String name
    }

    def stu=new Student(name: "wzq")
    def tea=new Teacher(name: "zpy")
    stu.pretty.delegate=tea
    stu.pretty.resolveStrategy=Closure.DELEGATE_FIRST
    println stu.toString()
  3. 数据结构

    List:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package variable

    //定义list
    def list = [1, 2, 3]
    //定义数组
    def array = [1, 2, 3] as int[]
    int[] array2 = [1, 2]

    //排序
    def sortList=[2,4,1,-3]
    Comparator mc={
    a,b->
    a==b?0:Math.abs(a)<Math.abs(b)?-1:1
    }

    Collections.sort(sortList,mc)
    println(sortList)

    sortList.sort {
    a,b->
    a==b?0:Math.abs(a)<Math.abs(b)?-1:1
    }
    println(sortList)

    def stringList=["w","wa","f","fdaf",""]
    stringList.sort{
    it.size()
    }
    println stringList


    //查找
    def findList=[-1,2,5,1,6,5]
    def find = findList.find { a ->
    a > 3
    }
    println find
    def all = findList.findAll {
    it > 2
    }
    println all.toListString()

    def any = findList.any {
    it > 7
    }
    println any

    def min = findList.min()
    println min

    def count = findList.count {
    it > 4
    }
    println(count)

    Map:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package variable
    //映射
    def colors=[red:"ff0000",green:"00ff00"]

    println colors['red']
    println colors.green
    colors.yellow="ffff00"
    colors.temp=[a:1,b:2]
    println colors.toMapString()
    println colors.getClass()


    //遍历
    def students=[1:[name:'wzq',age:18],
    2:[name:'zpy',age:17],
    3:[name:'wrr',age: 20]]

    students.each {key,value->
    println "${key}->${value}"
    }
    students.eachWithIndex{ entry, int i ->
    println "${i}->${entry.key}->${entry.value}"
    }
    //查找
    def find = students.find { student ->
    return student.value.age > 17
    }
    println(find)

    def all = students.findAll { student ->
    return student.value.age > 10
    }
    println(all.toMapString())

    def collect = students.findAll { student ->
    return student.value.age > 10
    }.collect { student ->
    return student.value.name
    }
    println(collect.toListString())

    def by = students.groupBy { student ->
    return student.value.age > 17 ? "good" : "bad"
    }
    println(by.toMapString())
    //排序
    def sort = students.sort {
    student1, student2 ->
    return student1.value.age == student2.value.age ? 0 : student1.value.age < student2.value.age ? -1 : 1
    }
    println(sort.toMapString())

    range:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package variable
    //定义
    def range= 1..10

    println range[0]
    //遍历
    range.each {
    print(it)
    }
    println()
    //swith-case
    println(test(10))
    def test(Number number){
    def result
    switch (number){
    case 0..<60:
    result="不及格"
    break
    case 60..100:
    result="及格"
    break
    default:
    break
    }
    return result
    }
  4. 面向对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package variable.clazz.study

    /**
    @author kb_jay
    @time 2019/4/18
    * */
    class Person implements Action {
    String name;
    int age;

    def sayHello() {
    println("${name}->${age} say hello")
    }

    @Override
    void play() {

    }

    @Override
    void eat() {

    }

    //运行时 1
    @Override
    Object invokeMethod(String s, Object o) {
    return "method->${s},args->${o}"
    }
    }

    //类的使用!!!!!!!
    package variable.clazz.study

    def person = new Person(name: "wzq", age: 18)

    println(person.name)

    person.sayHello()

    //metaClass
    def cry = person.cry()
    println(cry)

    //为类动态添加属性
    Person.metaClass.sex='male'

    def person1 = new Person(name: "wzq", age: 18)
    println(person1.sex)

    //动态添加方法
    Person.metaClass.newMethod={return "${name}"}

    println(person1.newMethod())
    //动态添加静态方法
    Person.metaClass.static.newStaticMethod={String name,int age->
    new Person(name:name,age:age)
    }

    def person2=Person.newStaticMethod("zpy",17)
    println person2.name
进阶语法
  1. json文件操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package variable

    import groovy.json.JsonOutput
    import groovy.json.JsonParser
    import groovy.json.JsonSlurper

    //object -> jsonString
    def list = [new Test(name: "wzq", age: 16), new Test(name: "zpy", age: 15)]

    def json = JsonOutput.toJson(list)
    def print = JsonOutput.prettyPrint(json)
    println(print)


    class Test {
    String name;
    int age;
    }

    //jsonString 2 object
    def slurper = new JsonSlurper()
    def text = slurper.parseText(json)
    println text[0].name

    //call net
    def response=callNet('http://yuexibo.top/yxApp/course_detail.json')
    println response.data.head.name

    def callNet(String path) {
    def connection = new URL(path).openConnection()
    connection.setRequestMethod("GET")
    connection.connect()

    def response = connection.content.text

    def slurper = new JsonSlurper()
    return slurper.parseText(response)
    }
  1. 文件操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    package variable

    //copy文件

    def file = new File('../../HelloGroovy.iml')

    def b = copy('../../HelloGroovy.iml', '../../HelloGroovy1.iml')
    println(b)

    def copy(String sourcePath, String desPath) {
    // check des
    def destFile = new File(desPath)
    if (!destFile.exists()) {
    destFile.createNewFile()
    }
    // copy
    try {
    def sourceFile = new File(sourcePath)
    sourceFile.withReader { reader ->
    def lines = reader.readLines();
    destFile.withWriter { writer ->
    lines.each { line ->
    writer.writeLine(line)
    }
    }
    }
    return true
    } catch (Exception e) {
    }
    return false
    }

    //copy Object

    def saveObj(Object o, String desPath) {
    // check des
    def destFile = new File(desPath)
    if (!destFile.exists()) {
    destFile.createNewFile()
    }
    //copy
    try {
    destFile.withObjectOutputStream { oos ->
    oos.writeObject(o)
    }
    return true
    } catch (Exception e) {
    }
    return false
    }

    def readObj(String path) {
    def obj = null;
    def destFile = new File(path)
    if (!destFile.exists()) {
    return null
    }
    try {
    destFile.withObjectInputStream { ois ->
    obj = ois.readObject();
    }
    } catch (Exception e) {
    println(e.getMessage())
    }
    return obj
    }

    def obj = new ObTest(name: "wzq", age: 18)
    def save = saveObj(obj, '../../obj.bin')
    println("save${save}")

    def test = readObj('../../obj.bin')
    println "${test.name}->${test.age}"

    class ObTest implements Serializable {
    String name;
    int age;
    }

Gradle

生命周期
  1. 初始化(init):解析project,生成project对象
  2. 配置(config):解析project的task,构建task的拓扑图
  3. 执行(exec):执行task以及依赖的task
Project
  1. 根project跟module都是project,执行
1
./gradlew projects
  1. 属性
Task
  1. Task 定义跟配置
  2. Task执行
  3. Task依赖跟执行顺序
  4. Task的类型
  5. 挂接到生命周期
  6. Task实战
Settings
SourceSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 //修改jnilib->lib
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
//资源分模块
sourceSets {
main{
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
自定义plugin

源码地址:https://github.com/kbjay/Kj_ReleasInfoPlugin

Android-jsbridge

简介

java跟js交互的框架。

git地址:https://github.com/lzyzsd/JsBridge

使用

git地址:https://github.com/kbjay/kj_jsbridge_use

  1. 关于导包,可以按照jsbridge的使用说明来

    1
    2
    3
    4
    5
    6
    7
    8
    repositories {
    // ...
    maven { url "https://jitpack.io" }
    }

    dependencies {
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
    }

    为了方便分析源码,我这边的demo(kj_kjbrigde_use)是将jsbridge作为一个module(library)引入。

  2. java传递消息给js:两种方式一种是默认方式,一种是spec方式

    java中代码如下:

    webView.callHandler(tag, msg,callback):发送消息msg给js中tag对应的function,function执行完成之后有一个callback。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * java 发送消息给 js
    */
    private void java2jsSpec() {
    // java2js-> step1
    webView.callHandler("functionInJs", "发送消息给js(spec方式!!)", new CallBackFunction() {
    @Override
    public void onCallBack(String data) {
    Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
    }
    });
    }

    /**
    * java 发送消息给 js
    */
    private void java2jsDefault() {
    webView.send("发送消息给js(默认方式!)", new CallBackFunction() {
    @Override
    public void onCallBack(String data) {
    Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
    }
    });
    }

    对应的js代码如下:

    js中需要注册接受消息handler(同样有两种类型:默认跟spec),处理完消息之后调用responseCallback(callbackMsg)将处理结果回传给java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //注册事件监听,初始化
    // Java2js-> step2
    function initJsBridge(callback) {
    if (window.WebViewJavascriptBridge) {
    callback(WebViewJavascriptBridge)
    } else {
    document.addEventListener(
    'WebViewJavascriptBridgeReady'
    , function () {
    callback(WebViewJavascriptBridge)
    },
    false
    );
    }
    }

    //回调函数,接收java发送来的数据
    initJsBridge(function (bridge) {
    //默认接收
    bridge.init(function (message, responseCallback) {
    document.getElementById("show").innerHTML = '默认接收到Java的数据: ' + message;

    var responseData = 'js默认接收完毕,并回传数据给java';
    responseCallback(responseData); //回传数据给java
    });

    //指定接收,参数functionInJs 与java保持一致
    //java2js -> step 3;
    bridge.registerHandler("functionInJs", function (data, responseCallback) {
    document.getElementById("show").innerHTML = '指定接收到Java的数据: ' + data;

    var responseData = 'js指定接收完毕,并回传数据给java';
    responseCallback(responseData); //回传数据给java
    });
    })
  3. js传递消息给java,两种方式:默认跟spec

    js中发送消息,接收callback代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function jsToJavaDefault() {
    var msg = "js发送给java-->默认消息";
    window.WebViewJavascriptBridge.send(msg, function (resp) {
    document.getElementById("show").innerHTML = resp;
    });
    }

    //js2java-> step1
    function jsToJavaSpec() {
    var msg = "js发送给java-->指定消息";
    window.WebViewJavascriptBridge.callHandler('submitFromWeb', msg, function (resp) {
    document.getElementById("show").innerHTML = resp;
    });
    }

    java中处理消息,回传callback代码如下:同样两种方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 初始化jsbridge用于接受js发来的消息,并利用回调将传递数据给js。
    */
    private void initJs() {
    webView.setDefaultHandler(new BridgeHandler() {
    @Override
    public void handler(String data, CallBackFunction function) {
    Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
    function.onCallBack("java默认接收完毕,并回传数据给js");
    }
    });
    //js2java-> step2
    webView.registerHandler("submitFromWeb", new BridgeHandler() {
    @Override
    public void handler(String data, CallBackFunction function) {
    Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
    function.onCallBack("java指定接收完毕,并回传数据给js");
    }
    });
    }

源码解析

基础知识
  1. java跟js交互的原始方式

    java->js:

    • webView.loadUrl(“javascript:functionInJs(msg)”) jsBridge中选择的方式
      webView.evaluateJavascript(“javascript:functionInJs(msg)”,callback) API19(4.4)之后都可以,带有callback

    js->java:

    • 在java声明JsJavaBridge.class,类中的方法用@JavaScriptInterface标记,设置webView.addJavascriptInterface(new JsJavaBridge(),”bridge”)之后,可以在js中使用bridge.xxx()调用JsJavaBridge中的方法,这种方式在4.4之前有安全问题,不建议使用(不过现在基本上不会去适配4.4以下的了)
    • 自定义WebViewClient,在shouldOverrideUrlLoading()中拦截url之后调用java代码。(这个是jsbridge中选择的方式)需要注意版本适配 API24 (7.0)
    • 自定义WebChromeClient,拦截console之后调用java代码
    • 自定义WebChromeClient,拦截弹框事件之后调用Java 代码

    参考:https://www.jianshu.com/p/eb52ae7633b5

  2. 可以通过重写webviewClient的onPageFinished方法,在方法中通过loadurl的方式给webview注入js代码。

  3. 关于H5中的iframe:通过修改iframe的src来触发webViewClient的shouldOverrideUrlLoading方法。

流程分析

先简单描述下整个流程:

首先在webViewClient的onPageFinished的方法里加载了assets中的WebViewJavascriptBridge.js。

java->js :

  • Java处理消息体:生成一个唯一标示(callbackId)+ msg实体 + functionTag封装成message对象;其中functionTag是js中消息map的key,js可以根据这个找到java希望js调用的方法。

  • 将callbackId作为key,handler作为value放入java中的消息map;(我们给js发一个request,js处理完成之后会有一个response,这个handler就是处理response的

  • 上面的js消息map需要我们在js中注册,其实就是把functionTag作为key,把回调的方法作为value放入map中。

  • 将message对象通过loadurl的方式传递给js

  • js收到消息会先判读是否有callbackId(原因在js传递消息给java中),因为我们传递过来了,那么肯定是有的,之后会去判断js的消息map中是否有functionTag这个key(原因在java传递消息给js的默认方式中),因为我们在js中注册过了,那么肯定是有的,然后取出key对应的回调,处理java传过来的msg实体。

  • 将callbackId跟处理完成的结果放入js中的消息队列queue。

  • 之后会修改iframe控件src的url,在url上会拼接上“我有新消息了”,这样就会触发WebViewClient中的shouldOverrideUrlLoading方法,这样java就知道了js消息队列中有新消息了。

  • java通过loadurl的方式通知js取出js消息队列中的所有消息,搞成jsonString。

  • 修改iframe控件的url,在url上会拼接上“我的所有消息:jsonString”,此时会触发WebViewClient中的shouldOverrideUrlLoading方法,这样java就收到了js消息队列中的所有消息。

  • 最后我们取出消息,根据消息中的callbackId在java的消息map中找到对应的handler,之后处理js的response。

对应的源码流程如下:

1
2
3
4
5
6
7
//发送消息给js
webView.callHandler("functionInJs", "发送消息给js(spec方式!!)", new CallBackFunction() {
@Override
public void onCallBack(String data) {
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
}
});

进入callHandler方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* call javascript registered handler
* 调用javascript处理程序注册
* @param handlerName handlerName
* @param data data
* @param callBack CallBackFunction
*/
@Override
public void callHandler(String handlerName, String data, CallBackFunction callBack) {
send(handlerName, data, callBack);
}

进入send方法,作用就是上面提到的封装message。并且把callbackId跟处理response的handler放入了responseCallbacks中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 保存message到java消息队列
* @param handlerName handlerName
* @param data data
* @param responseCallback CallBackFunction
*/
private void send(String handlerName, String data, CallBackFunction responseCallback) {
Message m = new Message();
if (!TextUtils.isEmpty(data)) {
m.setData(data);
}
if (responseCallback != null) {
//生成唯一标示
String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
//重点!!!!!!
responseCallbacks.put(callbackStr, responseCallback);

m.setCallbackId(callbackStr);
}
if (!TextUtils.isEmpty(handlerName)) {
m.setHandlerName(handlerName);
}
queueMessage(m);
}

进入queueMessage方法

1
2
3
4
5
6
7
8
9
10
11
/**
* list<message> != null 添加到消息集合否则分发消息
* @param m Message
*/
private void queueMessage(Message m) {
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}

进入dispatchMessage,重点最后一句(将消息传递给了js)。

1
2
3
4
5
6
7
8
9
10
11
/**
* 分发message 必须在主线程才分发成功
* @param m Message
*/
void dispatchMessage(Message m) {
String messageJson = m.toJson();
...
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
this.loadUrl(javascriptCommand);
}
}

进入js的_handleMessageFromNative方法

1
2
3
4
5
6
//提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
function _handleMessageFromNative(messageJSON) {
...
_dispatchMessageFromNative(messageJSON);

}

进入_dispatchMessageFromNative方法(根据functionTag找到在js中注册处理方法handler并调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//提供给native使用,
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
var message = JSON.parse(messageJSON);
var responseCallback;
...

var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({
responseId: callbackResponseId,
responseData: responseData
});
};
...
handler = messageHandlers[message.handlerName];
//查找指定handler
...
handler(message.data, responseCallback);
});
}

handler处理完成之后有个回调responseCallback,会调用_doSend方法。(将java传递过来的callbackid和js处理完成之后的response封装成一个对象作为参数)

进入_doSend方法,将消息放入消息队列。之后修改iframe控件的src。

1
2
3
4
5
6
//sendMessage add message, 触发native处理 sendMessage
function _doSend(message, responseCallback) {
...
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

进入WebViewClient的shouldOverrideUrlLoading方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
...
String url = request.getUrl().toString();
...
url = URLDecoder.decode(url, "UTF-8");
...
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {
//js返回消息队列中的消息
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
//js返回有新消息的标示
webView.flushMessageQueue();
return true;
}

进入flushMessageQueue方法,其实就调用了一个方法loadUrl,有一个callback(可以在回调的时候再看)

1
2
3
4
5
6
7
8
9
10
11
/**
* 刷新消息队列
*/
void flushMessageQueue() {
...
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
@Override
public void onCallBack(String data) {
。。。
});
}

loadUrl方法:调用js的_fetchQueue方法并把“_fetchQueue”作为key,上面的callback作为value放入responseCallbacks中。

1
2
3
4
5
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);
// 添加至 Map<String, CallBackFunction>
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
}

进入js中的_fetchQueue方法:取出js中所有的消息,放在url的后面

1
2
3
4
5
6
7
8
9
// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
//android can't read directly the return data, so we can reload iframe src to communicate with java
if (messageQueueString !== '[]') {
bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
}

进入WebViewClient的shouldOverrideUrlLoading方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
...
String url = request.getUrl().toString();
...
url = URLDecoder.decode(url, "UTF-8");
...
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {
//js返回消息队列中的消息
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
//js返回有新消息的标示
webView.flushMessageQueue();
return true;
}

进入handlerReturnData方法,根据callbackid在responseCallbacks中找到处理response的handler。执行handler的callback方法。流程结束!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取到CallBackFunction data执行调用并且从数据集移除
* @param url
*/
void handlerReturnData(String url) {
String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
CallBackFunction f = responseCallbacks.get(functionName);
String data = BridgeUtil.getDataFromReturnUrl(url);
if (f != null) {
f.onCallBack(data);
responseCallbacks.remove(functionName);
return;
}
}

js->java:

跟java->js在本质上没有什么区别,有兴趣可以自己跟一下(如果理解了上面的流程,会很顺利的跟下来的~)。

源码中的一个小问题

源码中BridgeWebView实现了WebViewJavascriptBridge接口,该接口是对外暴露的发送消息给js的接口,源码里面只声明了两种方式(1->默认方式发送消息给js,不带回调 ; 2->默认方式发送消息给js,带回调 ),但是实际上BridgeWebView对外暴露的有3种方式,还有一种(3-> callHandler(spec方式,带回调的)),所以我把callHandler也声明到了接口里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 对外暴露的方法接口
*/
public interface WebViewJavascriptBridge {
/**
* 发送消息给js(默认方式不带回调)
*
* @param data
*/
void send(String data);

/**
* 发送消息给js (默认方式带回调)
*
* @param data
* @param responseCallback
*/
void send(String data, CallBackFunction responseCallback);

/**
* 发送消息给js(spec带回调)
*
* @param funNameInJs
* @param data
* @param responseCallback
*/
void callHandler(String funNameInJs, String data, CallBackFunction responseCallback);

}

设计模式-工厂模式

简介

工厂模式是最常用的一类创建型设计模式,通常我们所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式。简单工厂模式是工厂方法模式的“小弟”,它不属于GoF 23种设计模式,但在软件开发中应用也较为频繁,通常将它作为学习其他工厂模式的入门。此外,工厂方法模式还有一位“大哥”——抽象工厂模式。这三种工厂模式各具特色,难度也逐个加大,在软件开发中它们都得到了广泛的应用,成为面向对象软件中常用的创建对象的工具。

简单工厂

定义

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。

角色

在简单工厂模式结构图中包含如下几个角色:

● Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。

● Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。

● ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
interface Car {
void run();
}

public class BenchiCar implements Car {
@Override
public void run() {
Log.d("kb_jay","BenchiCar run");
}
}

public class BiyadiCar implements Car{

@Override
public void run() {
Log.d("kb_jay","BiyadiCar run");
}
}

public class SimpleFactory {
public static final int TYPE_BENCHI = 1;
public static final int TYPE_BIYADI = 2;

public static Car createCar(int type) {
Car car = null;
switch (type) {
case TYPE_BENCHI:
car = new BenchiCar();
break;
case TYPE_BIYADI:
car = new BiyadiCar();
break;
default:
break;
}
return car;
}
}

客户端测试代码如下

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
//1:简单工厂
Car car = SimpleFactory.createCar(SimpleFactory.TYPE_BENCHI);
car.run();
}
}

运行结果如下

1
D/kb_jay: BenchiCar run

工厂方法

定义

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,针对不同的产品提供不同的工厂。

角色

在工厂方法模式结构图中包含如下几个角色:

● Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。

● ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。

● Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。

● ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface Car {
void run();

class BiyadiCar implements Car{
@Override
public void run() {
Log.d("kb_jay","biyadi run!!");
}
}
class BenchiCar implements Car{

@Override
public void run() {
Log.d("kb_jay","benchi run!!");
}
}
}

public interface CarFactory {
Car createCar();

class BenchiFactory implements CarFactory {
@Override
public Car createCar() {
return new Car.BenchiCar();
}
}

class BiyadiFatory implements CarFactory {
@Override
public Car createCar() {
return new Car.BiyadiCar();
}
}

}

客户端测试代码如下

1
2
3
4

CarFactory carFactory = new CarFactory.BenchiFactory();
Car car1 = carFactory.createCar();
car1.run();

测试运行结果如下

1
D/kb_jay: benchi run!!

抽象工厂

定义

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。

角色

在抽象工厂模式结构图中包含如下几个角色:

● AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。

● ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。

● AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。

● ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

public interface Engine {
void engine();

class GoodEngine implements Engine{
@Override
public void engine() {
Log.d("kb_jay","good engine!!!!");
}
}
class BadEngine implements Engine{
@Override
public void engine() {
Log.d("kb_jay","bad engine!!!!");
}
}
}

public interface LingjianFactory {
Engine createEngine();
Seater createSeater();


class GoodLingjianFactory implements LingjianFactory{
@Override
public Engine createEngine() {
return new Engine.GoodEngine();
}

@Override
public Seater createSeater() {
return new Seater.GoodSeater();
}
}

class BadLingjianFactory implements LingjianFactory{
@Override
public Engine createEngine() {
return new Engine.BadEngine();
}

@Override
public Seater createSeater() {
return new Seater.BadSeater();
}
}
}

public interface Seater {
void seat();

class GoodSeater implements Seater {
@Override
public void seat() {
Log.d("kb_jay", "good seater!!!!");
}
}

class BadSeater implements Seater {
@Override
public void seat() {
Log.d("kb_jay", "bad Seater!!!!");
}
}
}

客户端测试代码如下

1
2
3
4
//3:抽象工厂
LingjianFactory lingjianFactory = new LingjianFactory.BadLingjianFactory();
Engine engine = lingjianFactory.createEngine();
engine.engine();

测试运行结果如下

1
D/kb_jay: bad engine!!!!

总结:

  1. 简单工厂(静态工厂):

    抽象产品,不抽象工厂

    一个汽车工厂,生产多种car(一对多)

    我想买一辆奔驰,那么我就到这个厂里告诉它我想要奔驰(type),之后它就给我奔驰;

    朋羽毛想买一辆比亚迪,他同样到这个厂子里告诉老板,他想要比亚迪(type),那么老板就给他一辆比亚迪

  2. 工厂方法:

    抽象产品,也抽象工厂

    一个工厂对应一个产品(一对一) 我想买一辆奔驰,那么我到奔驰专卖店买;

    朋羽毛想买一辆比亚迪,那么她到比亚迪专卖店去买;

  3. 抽象工厂:

    抽象工厂,也抽象产品

    一个工厂对应一组不同但是相关的产品 产品簇,这些产品是相关的,

    比如:生产汽车零件的工厂