老司机种菜


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 公益404

  • 搜索

webrtc之信令交互流程

发表于 2017-04-27 | 分类于 webrtc

无论是使用前端JS的WebRTC API接口,还是在WebRTC源码上构建自己的对聊框架,都需要遵循以下执行流程: image

上述序列中,WebRTC并不提供Stun服务器和Signal服务器,服务器端需要自己实现。Stun服务器可以用google提供的实现stun协议的测试服务器(stun:stun.l.google.com:19302),Signal服务器则完全需要自己实现了,它需要在ClientA和ClientB之间传送彼此的SDP信息和candidate信息,ClientA和ClientB通过这些信息建立P2P连接来传送音视频数据。 stun/turn、relay服务器的实现在WebRTC源码中都有示例。

上述序列中,标注的场景是ClientA向ClientB发起对聊请求,调用描述如下:

  1. ClientA首先创建PeerConnection对象,然后打开本地音视频设备,将音视频数据封装成MediaStream添加到PeerConnection中。
  2. ClientA调用PeerConnection的CreateOffer方法创建一个用于offer的SDP对象,SDP对象中保存当前音视频的相关参数。ClientA通过PeerConnection的SetLocalDescription方法将该SDP对象保存起来,并通过Signal服务器发送给ClientB。
  3. ClientB接收到ClientA发送过的offer SDP对象,通过PeerConnection的SetRemoteDescription方法将其保存起来,并调用PeerConnection的CreateAnswer方法创建一个应答的SDP对象,通过PeerConnection的SetLocalDescription的方法保存该应答SDP对象并将它通过Signal服务器发送给ClientA。
  4. ClientA接收到ClientB发送过来的应答SDP对象,将其通过PeerConnection的SetRemoteDescription方法保存起来。
  5. 在SDP信息的offer/answer流程中,ClientA和ClientB已经根据SDP信息创建好相应的音频Channel和视频Channel并开启Candidate数据的收集,Candidate数据可以简单地理解成Client端的IP地址信息(本地IP地址、公网IP地址、Relay服务端分配的地址)。
  6. 当ClientA收集到Candidate信息后,PeerConnection会通过OnIceCandidate接口给ClientA发送通知,ClientA将收到的Candidate信息通过Signal服务器发送给ClientB,ClientB通过PeerConnection的AddIceCandidate方法保存起来。同样的操作ClientB对ClientA再来一次。
  7. 这样ClientA和ClientB就已经建立了音视频传输的P2P通道,ClientB接收到ClientA传送过来的音视频流,会通过PeerConnection的OnAddStream回调接口返回一个标识ClientA端音视频流的MediaStream对象,在ClientB端渲染出来即可。同样操作也适应ClientB到ClientA的音视频流的传输。

git服务搭建

发表于 2017-04-25 | 分类于 server

纯git server

软件安装

环境:ubuntu16.0.4

  1. 安装Git-Core:sudo apt-get install python-setuptools
  2. 安装openssh-server和openssh-client:sudo apt-get install openssh-server openssh-client
  3. 安装python tool:sudo apt-get install python-setuptools
  4. 安装gitosis:
    1
    2
    3
    4
    5
    git clone https://github.com/res0nat0r/gitosis.git

    cd gitosis/

    sudo python setup.py install

添加管理账号

1
2
3
4
5
6
7
8
sudo adduser \
--system \
--shell /bin/sh \
--gecos 'git version control' \
--group \
--disabled-password \
--home /home/git \
git

如果已有git账户,可以替换成gitmanager

创建链接映射

由于gitosis默认状态下会将仓库放在用户的repositories目录下,例如gitmanager用户的仓库地址默认在 /home/gitmanager/repositories/目录下,这里我们需要创建一个链接映射。让他指向我们前面创建的专门用于存放项目的仓库目录/home/gitrepository。

1
sudo ln -s /home/gitrepository /home/gitmanager/repositories

初始化管理用户

  1. 拷贝管理用户公钥到/tmp/下,如:

    1
    scp ~/.ssh/id_rsa.pub gitmanager@192.168.0.68:/tmp/
  2. 使用拷贝来的公钥初始化gitosis:

    1
    2
    sudo -H -u gitmanager gitosis-init < /tmp/id_ras.pub
    sudo chmod 755 /home/gitmanager/repositories/gitosis-admin.git/hooks/post-update

配置账号

  1. 验证ssh
    1
    2
    3
    4
    ssh gitmanager@192.168.0.68
    TY allocation request failed on channel 0
    ERROR:gitosis.serve.main:Need SSH_ORIGINAL_COMMAND in environment.
    Connection to gitserver closed.

说明 Gitosis 认出了该用户的身份,但由于没有运行任何 Git 命令,所以它切断了连接。

  1. 克隆gitosis管理仓库:
    1
    git clone gitmanager@192.168.0.68:gitosis-admin.git

这会得到一个名为 gitosis-admin 的工作目录,主要由两部分组成:

1
2
./gitosis.conf
./keydir

gitosis.conf 文件是用来设置用户、仓库和权限的控制文件。keydir 目录则是保存所有具有访问权限用户公钥的地方— 每人一个。在 keydir 里的文件名(比如上面的 qingkouwei.pub) 会自动从使用 gitosis-init 脚本导入的公钥尾部的描述中获取该名字。

看一下 gitosis.conf 文件的内容,它应该只包含与刚刚克隆的 gitosis-admin 相关的信息:

1
2
3
4
5
[gitosis]

[group gitosis-admin]
members = qingkouwei
writable = gitosis-admin

要创建项目demo,在里面加入:

1
2
3
[group demo]
members = qingkouwei
writable = demo

要为demo项目添加用户user1:

1
2
3
[group demo]
members = qingkouwei user1
writable = demo

并将用户user1的公钥计入到keydir,并且公钥名.pub和members里面的名字对应. 要添加对demo项目只读的用户:

1
2
3
4
5
6
7
[group demo]
members = qingkouwei user1
writable = demo

[group demo]
members = user2
readonly = demo

修改完配置文件和keydir,使用git push到gitosis-admin服务器.即可直接git add remote add suervename gitmanager@192.168.0.68:demo.git,然后直接将本地目录推送到demo仓库,不需要再服务器手动创建demo仓库,gitosis会帮忙自动创建.

常见问题

  1. ERROR:gitosis.serve.main:Repository read access denied 原因: gitosis.conf中的members与keydir中的用户名不一致,如gitosis中的members = foo@bar,但keydir中的公密名却叫foo.pub

解决方法: 使keydir的名称与gitosis中members所指的名称一致。 改为members = foo 或 公密名称改为foo@bar.pub

  1. clone时报does not appear to be a git repository

原因: clone时不能用绝对路径,直接写gitosis-admin.git即可.

参考:https://git-scm.com/book/zh/v1/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E7%9A%84-Git-Gitosis

gitlab服务搭建

VMware中Bridged,NAT,host-only三种网络连接模式的原理和区别

发表于 2017-04-22 | 分类于 linux管理

不同虚拟交换机应用在不同的联网模式Bridged、NAT、host-only、custom四种模式,下面分别介绍其具体分配:

  • VMnet0:这是VMware用于虚拟桥接网络下的虚拟交换机;
  • VMnet1:这是VMware用于虚拟Host-Only网络下的虚拟交换机;
  • VMnet8:这是VMware用于虚拟NAT网络下的虚拟交换机;
  • VMnet2~VMnet7及VMnet9:是VMware用于虚拟自定义custom网络下的虚拟交换机;
  • VMware Network Adapter VMnet1:这是宿主机用于与Host-Only虚拟网络进行通信的宿主机使用的虚拟网卡;
  • VMware Network Adapter VMnet8:这是宿主机用于与NAT虚拟网络进行通信的宿主机使用的虚拟网卡;

VMware Network Adapter VMnet1与VMware Network Adapter VMnet8可以在宿主机网络连接中看到.

1.Bridged桥接模式

VMware在桥接模式下,虚拟机使用VMware为该虚拟机分配的虚拟网卡,宿主机使用自身的物理网卡(有线或无线都行),并且默认使用虚拟交换机VMnet0来连接虚拟机的虚拟网卡和宿主机的物理网卡。在此模式下没有局域网动态地址分配DHCP服务器,也没有网络地址转换NAT服务器,虚拟交换机没有连接DHCP服务器和NAT服务器。宿主机的网口(插网线的那个口)与宿主机物理网卡相连,同时也就和虚拟机的虚拟网卡相连,也就是和虚拟交换机相连,所以虚拟机相当于在宿主机所在局域网内的一个单独的主机,他的行为和宿主机是同等地位的,没有依存关系。所有桥接下的网卡与网卡都是交换模式的,相互可以访问而不干扰。在桥接模式下,虚拟机ip地址需要与主机在同一个网段,如果需要联网,则网关与DNS需要与主机网卡一致原理图如下: bridged 配置虚拟机网卡,编辑/etc/sysconfig/network-scripts/ifcfg-eth0:

1
2
3
4
5
6
7
8
9
10
11
DEVICE=eth0
HWADDR=00:0C:29:DA:E9:99
TYPE=Ethernet
UUID=0711466f-ae1f-aa83-825cb3dfb5f7
ONBOOT=yes
MM_CONTROLLED=yes
BOOTPROTO=none
IPADDR=192.168.31.128 #设置虚拟机ip地址,与主机ip地址在同一网段
NETMASK=255.255.255.0 #设置子网掩码
GATEWAY=192.168.31.1#设置虚拟网关,与主机相同
DNS1=192.168.31.1 #设置虚拟机DNS,与主机相同

执行/etc/init.d/network restart重启虚拟机网卡,ping内网与外网测试.

2.NAT网络地址转换模式:

nat 注意:红色的方框是nat服务器,nat服务器有两个网卡一个是虚拟内网网卡,一个是宿主机的物理网卡。禁用VmNet8,虚拟机仍然可以上网,ping通主机,但是主机ping不通虚拟机的网卡。在NAT模式中,主机网卡直接与虚拟NAT设备相连,然后虚拟NAT设备与虚拟DHCP服务器一起连接在虚拟交换机VMnet8上,这样就实现了虚拟机联网。那么我们会觉得很奇怪,为什么需要虚拟网卡VMware Network Adapter VMnet8呢?原来我们的VMware Network Adapter VMnet8虚拟网卡主要是为了实现主机与虚拟机之间的通信。弥补了NAT协议中外网不能访问局域网的缺点。 具体配置:

1
2
3
4
5
6
7
8
9
10
11
12
DEVICE=eth0
HWADDR=00:0C:29:DA:E9:99
TYPE=Ethernet
UUID=0711466f-ae1f-aa83-825cb3dfb5f7
ONBOOT=yes
MM_CONTROLLED=yes
BOOTPROTO=dhcp #动态获取ip地址,如果此处设置为静态,则下面手动配置ip需要在DHCP地址范围内
#NAT模式也可以设置静态ip,但需要在DHCP地址范围内
IPADDR=192.168.31.128
NETMASK=255.255.255.0
GATEWAY=192.168.31.1
DNS1=192.168.31.1

3.Host-Only方式

host-only 注意:上图中的VmNet8应该为VmNet1。其实跟nat模式的图片是类似的,只是少了nat服务。 所以host-only上不了外网,只能实现主机的VmNet1网卡和虚拟机的虚拟网卡通信。

NAT介绍

NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。 这种方法需要在专用网连接到因特网的路由器上安装NAT软件。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。 另外,这种通过使用少量的公有IP 地址代表较多的私有IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。

功能

NAT不仅能解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。

  1. 宽带分享:这是 NAT 主机的最大功能。
  2. 安全防护:NAT 之内的 PC 联机到 Internet 上面时,他所显示的 IP 是 NAT 主机的公共 IP,所以 Client 端的 PC 当然就具有一定程度的安全了,外界在进行 portscan(端口扫描) 的时候,就侦测不到源Client 端的 PC 。

实现方式

NAT的实现方式有三种,即静态转换Static Nat、动态转换Dynamic Nat和端口多路复用OverLoad。

  1. 静态转换是指将内部网络的私有IP地址转换为公有IP地址,IP地址对是一对一的,是一成不变的,某个私有IP地址只转换为某个公有IP地址。借助于静态转换,可以实现外部网络对内部网络中某些特定设备(如服务器)的访问。
  2. 动态转换是指将内部网络的私有IP地址转换为公用IP地址时,IP地址是不确定的,是随机的,所有被授权访问上Internet的私有IP地址可随机转换为任何指定的合法IP地址。也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。动态转换可以使用多个合法外部地址集。当ISP提供的合法IP地址略少于网络内部的计算机数量时。可以采用动态转换的方式。
  3. 端口多路复用(Port address Translation,PAT)是指改变外出数据包的源端口并进行端口转换,即端口地址转换(PAT,Port Address Translation).采用端口多路复用方式。内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而可以最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,有效避免来自internet的攻击。因此,目前网络中应用最多的就是端口多路复用方式。
  4. ALG(Application Level Gateway),即应用程序级网关技术:传统的NAT技术只对IP层和传输层头部进行转换处理,但是一些应用层协议,在协议数据报文中包含了地址信息。为了使得这些应用也能透明地完成NAT转换,NAT使用一种称作ALG的技术,它能对这些应用程序在通信时所包含的地址信息也进行相应的NAT转换。例如:对于FTP协议的PORT/PASV命令、DNS协议的 “A” 和 “PTR” queries命令和部分ICMP消息类型等都需要相应的ALG来支持。 如果协议数据报文中不包含地址信息,则很容易利用传统的NAT技术来完成透明的地址转换功能,通常我们使用的如下应用就可以直接利用传统的NAT技术:HTTP、TELNET、FINGER、NTP、NFS、ARCHIE、RLOGIN、RSH、RCP等。

工作原理

借助于NAT,私有(保留)地址的”内部”网络通过路由器发送数据包时,私有地址被转换成合法的IP地址,一个局域网只需使用少量IP地址(甚至是1个)即可实现私有地址网络内所有计算机与Internet的通信需求。 NAT将自动修改IP报文的源IP地址和目的IP地址,Ip地址校验则在NAT处理过程中自动完成。有些应用程序将源IP地址嵌入到IP报文的数据部分中,所以还需要同时对报文的数据部分进行修改,以匹配IP头中已经修改过的源IP地址。否则,在报文数据部分嵌入IP地址的应用程序就不能正常工作。

NAPT

NAPT(Network Address Port Translation),即网络端口地址转换,可将多个内部地址映射为一个合法公网地址,但以不同的协议端口号与不同的内部地址相对应,也就是<内部地址+内部端口>与<外部地址+外部端口>之间的转换。NAPT普遍用于接入设备中,它可以将中小型的网络隐藏在一个合法的IP地址后面。NAPT也被称为“多对一”的NAT,或者叫PAT(Port Address Translations,端口地址转换)、地址超载(address overloading)。 NAPT与动态地址NAT不同,它将内部连接映射到外部网络中的一个单独的IP地址上,同时在该地址上加上一个由NAT设备选定的TCP端口号。NAPT算得上是一种较流行的NAT变体,通过转换TCP或UDP协议端口号以及地址来提供并发性。除了一对源和目的IP地址以外,这个表还包括一对源和目的协议端口号,以及NAT盒使用的一个协议端口号。 NAPT的主要优势在于,能够使用一个全球有效IP地址获得通用性。主要缺点在于其通信仅限于TCP或UDP。当所有通信都采用TCP或UDP,NAPT允许一台内部计算机访问多台外部计算机,并允许多台内部主机访问同一台外部计算机,相互之间不会发生冲突。

NAT穿透方法

目前常用的针对UDP的NAT 穿透(NAT Traversal)方法主要有:STUN、TURN、ICE、uPnP等。其中ICE方式由于其结合了STUN和TURN的特点,所以使用最为广泛。针对TCP的NAT穿透技术目前仍为难点。实用的技术仍然不多。

配置

在配置NAT(网络地址转换)之前,首先需要了解内部本地地址和内部全局地址的分配情况。根据不同的需求,执行以下不同的配置任务。

  • 内部源地址NAT配置
  • 内部源地址NAPT配置
  • 重叠地址NAT配置
  • TCP负载均衡

linux网卡配置

发表于 2017-04-22 | 分类于 linux管理

linux网卡可以通过命令和配置文件配置,如果是桌面环境还可以通过图形化界面配置.

1.ifconfig(interfaces config)命令方式

通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置(用ifconfig命令配置的网卡信息,在网卡重启后机器重启后,配置就不存在)。

1.1命令格式

1
ifconfig [网络设备] [参数]

1.2命令功能

ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置。

1.3命令参数

  1. up 启动指定网络设备/网卡。
  2. down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流,如果想永久地关闭一个接口,我们还需要从核心路由表中将该接口的路由信息全部删除。
  3. arp 设置指定网卡是否支持ARP协议。

-promisc 设置是否支持网卡的promiscuous模式,如果选择此参数,网卡将接收网络中发给它所有的数据包 -allmulti 设置是否支持多播模式,如果选择此参数,网卡将接收网络中所有的多播数据包 -a 显示全部接口信息 -s 显示摘要信息(类似于 netstat -i)

  1. add 给指定网卡配置IPv6地址
  2. del 删除指定网卡的IPv6地址
  3. <硬件地址> 配置网卡最大的传输单元
  4. mtu<字节数> 设置网卡的最大传输单元 (bytes)
  5. netmask<子网掩码> 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数,也可以是用点分开的4个十进制数。如果不打算将网络分成子网,可以不管这一选项;如果要使用子网,那么请记住,网络中每一个系统必须有相同子网掩码。
  6. tunel 建立隧道
  7. dstaddr 设定一个远端地址,建立点对点通信
  8. -broadcast<地址> 为指定网卡设置广播协议
  9. -pointtopoint<地址> 为网卡设置点对点通讯协议
  10. multicast 为网卡设置组播标志
  11. address 为网卡设置IPv4地址
  12. txqueuelen<长度> 为网卡设置传输列队的长度

1.4使用实例

1.4.1显示网络设备信息(激活状态的)

命令:ifcofig 输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:20
inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:68 errors:0 dropped:0 overruns:0 frame:0
TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB)

说明

  • eth0 表示第一块网卡, 其中 HWaddr 表示网卡的物理地址,可以看到目前这个网卡的物理地址(MAC地址)是 00:50:56:BF:26:20
  • inet addr 用来表示网卡的IP地址,此网卡的 IP地址是 192.168.120.204,广播地址, Bcast:192.168.120.255,掩码地址Mask:255.255.255.0
  • lo 是表示主机的回坏地址,这个一般是用来测试一个网络程序,但又不想让局域网或外网的用户能够查看,只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD服务器的指定到回坏地址,在浏览器输入 127.0.0.1 就能看到你所架WEB网站了。但只是您能看得到,局域网的其它主机或用户无从知道。
  • 第一行:连接类型:Ethernet(以太网)HWaddr(硬件mac地址)
  • 第二行:网卡的IP地址、子网、掩码
  • 第三行:UP(代表网卡开启状态)RUNNING(代表网卡的网线被接上)MULTICAST(支持组播)MTU:1500(最大传输单元):1500字节
  • 第四、五行:接收、发送数据包情况统计
  • 第七行:接收、发送数据字节数统计信息。
1.4.2启动关闭指定网卡

命令: ifconfig eth0 up ifconfig eth0 down 输出: 说明: ifconfig eth0 up 为启动网卡eth0 ;ifconfig eth0 down 为关闭网卡eth0。ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。

1.4.3为网卡配置和删除IPv6地址

命令: ifconfig eth0 add 33ffe:3240:800:1005::2/64 ifconfig eth0 del 33ffe:3240:800:1005::2/64 输出: 说明: ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0配置IPv6地址; ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0删除IPv6地址; 练习的时候,ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。

1.4.4用ifconfig修改MAC地址

命令: ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 输出:

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
[root@localhost ~]# ifconfig eth0 down //关闭网卡
[root@localhost ~]# ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE //修改MAC地址
[root@localhost ~]# ifconfig eth0 up //启动网卡
[root@localhost ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:AA:BB:CC:DD:EE
inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:68 errors:0 dropped:0 overruns:0 frame:0
TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB)
[root@localhost ~]# ifconfig eth0 hw ether 00:50:56:BF:26:20 //关闭网卡并修改MAC地址
[root@localhost ~]# ifconfig eth0 up //启动网卡
[root@localhost ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:20
inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0
TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:68 errors:0 dropped:0 overruns:0 frame:0
TX packets:68 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB)
1.4.5配置IP地址

输出:

1
2
3
[root@localhost ~]# ifconfig eth0 192.168.120.56
[root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0
[root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255

说明: ifconfig eth0 192.168.120.56 给eth0网卡配置IP地:192.168.120.56 ifconfig eth0 192.168.120.56 netmask 255.255.255.0 给eth0网卡配置IP地址:192.168.120.56 ,并加上子掩码:255.255.255.0 ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255 /给eth0网卡配置IP地址:192.168.120.56,加上子掩码:255.255.255.0,加上个广播地址: 192.168.120.255

1.4.6启用和关闭ARP协议

命令: ifconfig eth0 arp ifconfig eth0 -arp 输出:

1
2
[root@localhost ~]# ifconfig eth0 arp
[root@localhost ~]# ifconfig eth0 -arp

说明 ifconfig eth0 arp 开启网卡eth0 的arp协议; ifconfig eth0 -arp 关闭网卡eth0 的arp协议;

1.4.7 设置最大传输单元

命令: ifconfig eth0 mtu 1500 输出:

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
[root@localhost ~]# ifconfig eth0 mtu 1480
[root@localhost ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:1F
inet addr:192.168.120.203 Bcast:192.168.120.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1480 Metric:1
RX packets:8712395 errors:0 dropped:0 overruns:0 frame:0
TX packets:36631 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:597062089 (569.4 MiB) TX bytes:2643973 (2.5 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:9973 errors:0 dropped:0 overruns:0 frame:0
TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:518096 (505.9 KiB) TX bytes:518096 (505.9 KiB)

[root@localhost ~]# ifconfig eth0 mtu 1500
[root@localhost ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:1F
inet addr:192.168.120.203 Bcast:192.168.120.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8712548 errors:0 dropped:0 overruns:0 frame:0
TX packets:36685 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:597072333 (569.4 MiB) TX bytes:2650581 (2.5 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:9973 errors:0 dropped:0 overruns:0 frame:0
TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:518096 (505.9 KiB) TX bytes:518096 (505.9 KiB)

[root@localhost ~]#

说明: 设置能通过的最大数据包大小为1500bytes

2.配置文件方式

ubuntu配置文件:/etc/network/interfaces

1
2
3
4
5
6
7
auto lo
iface lo inet loopback
auto eth0 #配置静态ip
iface eth0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1

centos配置文件:/etc/sysconfig/network-scripts/ifcfg-eth0

1
2
3
4
5
6
7
8
9
10
11
12
DEVICE=eth0(默认)
HWADDR=00:0C:29:2E:36:16(默认)
TYPE=Ethernet(默认)
UUID=XXXXXXX(默认)
ONBOOT=yes(默认为no,修改为yes意为每次reboot后 ifup eth0)
MM_CONTROLLED=yes(默认)
#BOOTPROTO=dhcp(dhcp为自动分配ip地址,我们把他注释了,在下面另外加)
BOOTPROTO=static(新添加)
IPV6INIT=no(新添加)
USERCTL=no(新添加)
IPADDR=192.168.164.100(新添加)
NETMASK=255.255.255.0(新添加)

service network restart重启网卡服务

3.图形界面方式

添加虚拟网卡

一台服务器需要设置多个ip,但又不想添加多块网卡,那就需要设置虚拟网卡.这里介绍几种方式在linux服务器上添加虚拟网卡. 比如向eth0中添加一块虚拟网卡:

1.快速创建删除虚拟网卡

sudo ifconfig eth0: 192.168.10.10 up 以上的命令就可以在eth0网卡上创建一个叫eth0:0的虚拟网卡,他的地址是:192.168.1.63 如果不想要这个虚拟网卡了,可以使用如下命令删除:

1
sudo ifconfig eth0:0 down

重启服务器或者网络后,虚拟网卡就没有了.

2.修改网卡配置文件

在ubuntu下,网卡的配置文件是/etc/network/interfaces,所以我们修改它: sudo vim /etc/network/interfaces 在这个文件中增加如下内容并保存:

1
2
3
4
5
6
auto eth0:0
iface eth0:0 inet static
address 192.168.10.10
netmask 255.255.255.0
#network 192.168.10.1
#broadcast 192.168.1.255

保存后,我们需要重启网卡(重新加载配置文件)才会生效,使用如下命令重启:sudo /etc/init.d/networking restart 他的优点是重启服务器或者网卡配置不会丢失。

3.创建tag

前两种方法都有一个特点,创建的网卡可有不同的ip地址,但是Mac地址相同。无法用来创建虚拟机。 添加虚拟网卡tap

1
tunctl -b

其他配置命令: 显示网桥信息:brctl show 添加网桥:brctl addbr virbr0 激活网桥:ip link set virbr0 up 添加虚拟网卡tap:tunctl -b tap0 ——-> 执行上面使命就会生成一个tap,后缀从0,1,2依次递增
激活创建的tap:ip link set tap0 up

将tap0虚拟网卡添加到指定网桥上:brctl addif br0 tap0

给网桥配制ip地址:ifconfig virbr1 169.254.251.4 up 将virbr1网桥上绑定的网卡eth5解除:

brctl delif virb1 eth5                                                  ```     
给virbr1网桥添加网卡eth6:`brctl addif virbr1 eth6  `   

Android声音播放与录制

发表于 2017-04-19 | 分类于 Android

AudioTrack

AudioTrack类说明:

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
/**
* The AudioTrack class manages and plays a single audio resource for Java applications.
* It allows streaming of PCM audio buffers to the audio sink for playback. This is
* achieved by "pushing" the data to the AudioTrack object using one of the
* {@link #write(byte[], int, int)}, {@link #write(short[], int, int)},
* and {@link #write(float[], int, int, int)} methods.
*
* <p>An AudioTrack instance can operate under two modes: static or streaming.<br>
* In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using
* one of the {@code write()} methods. These are blocking and return when the data has been
* transferred from the Java layer to the native layer and queued for playback. The streaming
* mode is most useful when playing blocks of audio data that for instance are:
*
* <ul>
* <li>too big to fit in memory because of the duration of the sound to play,</li>
* <li>too big to fit in memory because of the characteristics of the audio data
* (high sampling rate, bits per sample ...)</li>
* <li>received or generated while previously queued audio is playing.</li>
* </ul>
*
* The static mode should be chosen when dealing with short sounds that fit in memory and
* that need to be played with the smallest latency possible. The static mode will
* therefore be preferred for UI and game sounds that are played often, and with the
* smallest overhead possible.
*
* <p>Upon creation, an AudioTrack object initializes its associated audio buffer.
* The size of this buffer, specified during the construction, determines how long an AudioTrack
* can play before running out of data.<br>
* For an AudioTrack using the static mode, this size is the maximum size of the sound that can
* be played from it.<br>
* For the streaming mode, data will be written to the audio sink in chunks of
* sizes less than or equal to the total buffer size.
*
* AudioTrack is not final and thus permits subclasses, but such use is not recommended.
*/
public class AudioTrack extends PlayerBase
implements AudioRouting{

}

构造方法说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//根据采样率,采样精度,单双声道来得到frame的大小。
int bufsize = AudioTrack.getMinBufferSize(8000,//每秒8K个点  AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道
AudioFormat.ENCODING_PCM_16BIT);//一个采样点16比特-2个字节
//注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。//创建AudioTrack
AudioTrack trackplayer = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,  AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
  AudioFormat.ENCODING_PCM_16BIT,
  bufsize,
AudioTrack.MODE_STREAM);//
trackplayer.play() ;//开始

trackplayer.write(bytes_pkg, 0, bytes_pkg.length) ;//往track中写数据

….

trackplayer.stop();//停止播放

trackplayer.release();//释放底层资源。

参数说明

1. AudioTrack.MODE_STREAM的意思:

AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。

  • STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。

  • 而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。 这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。

2. StreamType

这个在构造AudioTrack的第一个参数中使用。这个参数和Android中的AudioManager有关系,涉及到手机上的音频管理策略。 Android将系统的声音分为以下几类常见的(定义在AudioManager):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** The audio stream for phone calls */
public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
/** The audio stream for system sounds */
public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;
/** The audio stream for the phone ring */
public static final int STREAM_RING = AudioSystem.STREAM_RING;
/** The audio stream for music playback */
public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
/** The audio stream for alarms */
public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
/** The audio stream for notifications */
public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
/** @hide The audio stream for phone calls when connected to bluetooth */
public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
/** @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
public static final int STREAM_SYSTEM_ENFORCED = AudioSystem.STREAM_SYSTEM_ENFORCED;
/** The audio stream for DTMF Tones */
public static final int STREAM_DTMF = AudioSystem.STREAM_DTMF;
/** @hide The audio stream for text to speech (TTS) */
public static final int STREAM_TTS = AudioSystem.STREAM_TTS;

常用说明:

  • STREAM_ALARM:警告声
  • STREAM_MUSCI:音乐声,例如music等
  • STREAM_RING:铃声
  • STREAM_SYSTEM:系统声音
  • STREAM_VOCIE_CALL:电话声音

为什么要分这么多呢?以前在台式机上开发的时候很少知道有这么多的声音类型,不过仔细思考下,发现这样做是有道理的。例如你在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,你肯定不用再调节音量了。

其实系统将这几种声音的数据分开管理,所以,这个参数对AudioTrack来说,它的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。

AudioRecord

AudioRecord说明

The AudioRecord class manages the audio resources for Java applications to record audio from the audio input hardware of the platform. This is achieved by “pulling” (reading) the data from the AudioRecord object. The application is responsible for polling the AudioRecord object in time using one of the following three methods: read(byte[], int, int), read(short[], int, int) or read(ByteBuffer, int). The choice of which method to use will be based on the audio data storage format that is the most convenient for the user of AudioRecord.

Upon creation, an AudioRecord object initializes its associated audio buffer that it will fill with the new audio data. The size of this buffer, specified during the construction, determines how long an AudioRecord can record before “over-running” data that has not been read yet. Data should be read from the audio hardware in chunks of sizes inferior to the total recording buffer size.

audiosource类型

定义在MediaRecorder中

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
/** Default audio source **/
public static final int DEFAULT = 0;

/** Microphone audio source */
public static final int MIC = 1;

/** Voice call uplink (Tx) audio source */
public static final int VOICE_UPLINK = 2;

/** Voice call downlink (Rx) audio source */
public static final int VOICE_DOWNLINK = 3;

/** Voice call uplink + downlink audio source */
public static final int VOICE_CALL = 4;

/** Microphone audio source with same orientation as camera if available, the main
* device microphone otherwise */
public static final int CAMCORDER = 5;

/** Microphone audio source tuned for voice recognition if available, behaves like
* {@link #DEFAULT} otherwise. */
public static final int VOICE_RECOGNITION = 6;

/** Microphone audio source tuned for voice communications such as VoIP. It
* will for instance take advantage of echo cancellation or automatic gain control
* if available. It otherwise behaves like {@link #DEFAULT} if no voice processing
* is applied.
*/
public static final int VOICE_COMMUNICATION = 7;

AudioManager

获取系统音量

代码

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
//初始化AudioManager:
AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

//通话音量
int max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_VOICE_CALL );
int current = mAudioManager.getStreamVolume( AudioManager.STREAM_VOICE_CALL );
Log.d(“VIOCE_CALL”, “max : ” + max + ” current : ” + current);

//系统音量
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_SYSTEM );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_SYSTEM );
Log.d(“SYSTEM”, “max : ” + max + ” current : ” + current);

//铃声音量
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_RING );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_RING );
Log.d(“RING”, “max : ” + max + ” current : ” + current);

//音乐音量

max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_MUSIC );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_MUSIC );
Log.d(“MUSIC”, “max : ” + max + ” current : ” + current);

//提示声音音量

max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_ALARM );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_ALARM );
Log.d(“ALARM”, “max : ” + max + ” current : ” + current);

ps:   游戏过程中只允许调整多媒体音量,而不允许调整通话音量。

1
setVolumeControlStream(AudioManager.STREAM_MUSIC);

控制音量

AudioManager提供了设置音量的方法:

1
public void setStreamVolume(intstreamType,intindex,intflags)

其中streamType有内置的常量,去文档里面就可以看到。 使用示例:

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
//音量控制,初始化定义    
AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
//最大音量
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//当前音量
int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

//直接控制音量
if(isSilent){
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
}else{
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, tempVolume, 0); //tempVolume:音量绝对值
}

//以一步步长控制音量的增减,并弹出系统默认音量控制条:
//降低音量,调出系统音量控制
if(flag == 0){
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_LOWER,
AudioManager.FX_FOCUS_NAVIGATION_UP);
}
//增加音量,调出系统音量控制
else if(flag == 1){
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_RAISE,
AudioManager.FX_FOCUS_NAVIGATION_UP);
}

监听按键手动控制音量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
AudioManager audio = (AudioManager) getSystemService(Service.AUDIO_SERVICE);

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
audio.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI);
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
audio.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER,
AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI);
return true;
default:
break;
}
return super.onKeyDown(keyCode, event);

插入耳机状态仍使用扬声器外放音乐

插入耳机的时候也可以选择使用扬声器播放音乐,来电铃声就是这么用的。但是只能用MediaPlayer,播放音频文件。 使用AudioTrack.write播放是行不通的(有待验证)。按理说AudioRecord、AudioTrack类相对于MediaRecorder mediaPlayer来说,更加接近底层,应该也行得通的。

插入耳机,选择外放的代码如下(兼容性验证):

1
2
3
4
5
6
7
8
9
AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);  
audioManager.setMicrophoneMute(false);
audioManager.setSpeakerphoneOn(true);//使用扬声器外放,即使已经插入耳机
setVolumeControlStream(AudioManager.STREAM_MUSIC);//控制声音的大小
audioManager.setMode(AudioManager.STREAM_MUSIC);

//播放一段声音,查看效果
MediaPlayer playerSound = MediaPlayer.create(this, Uri.parse("file:///system/media/audio/ui/camera_click.ogg"));
playerSound.start();

使用STREAM_VOCIE_CALL播放声音与耳机冲突

使用STREAM_VOCIE_CALL播放声音在某些手机,比如魅蓝等上面会导致声音仍外放,耳机没声音现象. WebRtc使用STREAM_VOCIE_CALL播放声音,导致某些手机声音低,没法使用音量键调节,插入耳机声音仍外放等问题.

MarkDown中嵌入LaTex

发表于 2017-04-16 | 分类于 工具

MarkDown中使用标识符$$和$$$$即可引入LaTeX语法,前者使用时不换行,即在所使用位置使用LaTeX的格式,后者会换行后居中

部分希腊字母

命令 显示 命令 显示
\alpha α A A
\beta β B B
\gamma γ \Gamma \varGamma Γ Γ
delta δ \Delta \varDelta Δ Δ
\epsilon ϵ E E
\eta η H H
\theta θ \Theta \varTheta Θ Θ
\kappa κ K K
\lambda λ \Lambda \varLambda Λ Λ
\mu μ M M
\nu ν N N
\pi π \Pi \varPi Π Π
\rho ρ P P
\sigma σ \Sigma \varSigma Σ Σ
\tau τ T T
\phi \varphi ϕ φ \Phi \varPhi Φ Φ
\omega ω \Omega \varOmega Ω Ω

全部24个字母:

名称 大写 Tex 小写 Tex
alpha A A α \alpha
beta B B β|beta
gamma Γ \Gamma γ \gamma
delta Δ \Delta δ \delta
epsilon E E ϵ \epsilon
zeta Z Z ζ \zeta
eta H H η \eta
theta Θ \Theta θ \theta
iota I I ι \iota
kappa K K κ \kappa
lambda Λ \Lambda λ \lambda
mu M M μ \mu
nu N N ν \nu
xi Ξ \Xi ξ \xi
omicron O O ο \omicron
pi Π \Pi π \pi
rho P P ρ \rho
sigma Σ \Sigma σ \sigma
tau T T τ \tau
upsilon Υ \Upsilon υ \upsilon
phi Φ \Phi ϕ \phi
chi X X χ \chi
psi Ψ \Psi ψ \psi
omega Ω \Omega ω \omega

部分运算符

命令 显示 命令 显示
\pm ± \mp ∓
\times × \div ÷
\circ ∘ \bullet ∙
\cdot ⋅ \cup ∪
\cap ∩ \subset ⊂
\supset ⊃ \subseteq ⊆
\supseteq ⊇ \leq ≤
\geq ≥ \propto ∝

其他符号

命令 显示 命令 显示
\cdotp ⋅ \cdots ⋯
\ddots ⋱ \infty ∞
\partial ∂ \bot ⊥
\hat{a} â \tilde{a} ã
\bar{a} a¯ \vec{a} a⃗
\dot{a} a˙ \sqrt{a} a‾‾√
\sqrt[3]{2} a‾‾√3 a^{3} a3
\frac{1}{a} 1a \lim_{x \to 0} lima→0

集合关系符号

说明 命令
集合的大括号 { … }\
集合中的竖线$\mid$ \mid
属于 \in
不属于 \not\in
A包含于B A\subset B
A真包含于B A\subsetneqq B
A包含B A\supset B
A真包含B A\supsetneqq B
A不包含于B A\not\subset B
A交B A\cap B
A并B A\cup B
A的闭包 \overline{A}
A减去B A\setminus B
实数集合 \mathbb{R}
空集 \emptyset

表格中竖线用&#124;

括号总结

功能 语法 显示
不好看 ( \frac{1}{2} ) $(\frac{1}{2})$
好一点 \left( \frac{1}{2} \right) $\left ( \frac{1}{2} \right )$

可以使用\left和\right来显示不同的括号:

功能 语法 显示
圆括号,小括号 \left( \frac{a}{b} \right) $\left( \frac{a}{b} \right)$
方括号,中括号 \left[ \frac{a}{b} \right] $\left[ \frac{a}{b} \right]$
花括号,大括号 \left\{ \frac{a}{b} \right\} $ \left{ \frac{a}{b} \right}$
角括号 \left \langle \frac{a}{b} \right \rangle $\left\langle \frac{a}{b} \right \rangle$
单竖线,绝对值 \left 竖线 \frac{a}{b} \right 竖线
双竖线,范 \left \ 竖线 \frac{a}{b} \right \ 竖线
取整函数 (Floor function) \left \lfloor \frac{a}{b} \right \rfloor $ \left \lfloor \frac{a}{b} \right \rfloor$
取顶函数 (Ceiling function) \left \lceil \frac{c}{d} \right \rceil $ \left \lceil \frac{c}{d} \right \rceil$
斜线与反斜线 \left / \frac{a}{b} \right \backslash $ \left / \frac{a}{b} \right \backslash$
上下箭头 \left \uparrow \frac{a}{b} \right \downarrow \left \Uparrow \frac{a}{b} \right \Downarrow \left \updownarrow \frac{a}{b} \right \Updownarrow $\left \uparrow \frac{a}{b} \right \downarrow$ $\left \Uparrow \frac{a}{b} \right \Downarrow$ $\left \updownarrow \frac{a}{b} \right \Updownarrow$
混合括号 \left [ 0,1 \right ) \left \langle \psi ) $\left [ 0,1 \right )$ $ \left \langle \psi \right)$
单左括号 \left \{ \frac{a}{b} \right . $\left { \frac{a}{b} \right .$
单右括号 \left . \frac{a}{b} \right \} $\left . \frac{a}{b} \right }$

备注: 可以使用\big, \Big, \bigg, \Bigg控制括号的大小,比如代码\Bigg ( \bigg [ \Big \{ \big \langle \left | \| \frac{a}{b} \| \right | \big \rangle \Big \} \bigg ] \Bigg )显示

$$\Bigg ( \bigg [ \Big { \big \langle \left | | x | \right | \big \rangle \Big } \bigg ] \Bigg )$$

矩阵

基本用法

使用$$\begin{matrix}…\end{matrix}$$这样的形式来表示矩阵,在\begin与\end之间加入矩阵中的元素即可。矩阵的行之间使用\\分隔,列之间使用&分隔。

1
2
3
4
5
6
7
$$
\begin{matrix}
1 & x & x^2 \\
1 & y & y^2 \\
1 & z & z^2 \\
\end{matrix}
$$

$$ \begin{matrix} 1 & x & x^2 \ 1 & y & y^2 \ 1 & z & z^2 \ \end{matrix} $$

加括号

如果要对矩阵加括号,可以像上文中提到的一样,使用\left与\right配合表示括号符号。也可以使用特殊的matrix。即替换\begin{matrix}…\end{matrix}中的matrix为pmatrix,bmatrix,Bmatrix,vmatrix,Vmatrix.

省略元素

可以使用\cdots ⋯ \ddots ⋱ \vdots ⋮ 来省略矩阵中的元素

增广矩阵

增广矩阵需要使用前面的array来实现

1
2
3
4
5
6
7
$$ \left[
\begin{array}{cc|c}
1&2&3\\
4&5&6
\end{array}
\right]
$$

$$ \left[ \begin{array}{cc|c} 1&2&3\ 4&5&6 \end{array} \right] $$

表格

使用$$\begin{array}{列样式}…\end{array}$$这样的形式来创建表格,列样式可以是clr表示居中,左,右对齐,还可以使用|表示一条竖线。表格中 各行使用\\分隔,各列使用&分隔。使用\hline在本行前加入一条直线。 例如,

1
2
3
4
5
6
7
8
9
$$
\begin{array}{c|lcr}
n & \text{Left} & \text{Center} & \text{Right} \\
\hline
1 & 0.24 & 1 & 125 \\
2 & -1 & 189 & -8 \\
3 & -20 & 2000 & 1+10i \\
\end{array}
$$

上标与下标

上标和下标分别使用^与_,例如x_i^2:$x_i^2$ 。默认情况下,上下标符号仅仅对下一个组起作用。一个组即单个字符或者使用{..}包裹起来的内容。也就是说,如果使用10^10,会得到$10^10$ ,而10^{10}才是$10^{10}$ 。同时,大括号还能消除二义性,如x^5^6将得到一个错误,必须使用大括号来界定^的结合性,如{x^5}^6:${x^5}^6$ 或者 x^{5^6}:$x^{5^6}$ 。

对齐的公式

分类表达式

定义函数的时候经常需要分情况给出表达式,可使用\begin{cases}…\end{cases}。其中,使用\来分类,使用&指示需要对齐的位置。如:

多重积分

连分数

方程组

颜色

公式标记与引用

求和与积分

分式与根式

特殊函数与符号

空间

顶部符号

参考 http://mlworks.cn/posts/introduction-to-mathjax-and-latex-expression/

高数1.函数与极限

发表于 2017-04-13 | 分类于 高数

1.映射与函数

1.1 集合

1.1.1 集合的概念

集合(集)是指具有某种特定性质的事物的总体,组成这个集合的事物成为该集合的元素(简称元)

表示:用大写拉丁字母A,B,C…表示集合,小写拉丁字母表示集合的元素

分类:

  • 有限集
  • 无限集

表示数集的字母的右上角标*表示该数集内排除0的集,标上+来表示数集内排除0和负数的集

常用表示

  1. N={0, 1, 2, 3…};全体非负整数即自然数的集合
  2. N+={1,2,3,…n,….};全体正整数的集合
  3. Z={…,-n,…-3, -2,-1, 0, 1, 2, 3,…,n…};全体整数的集合
  4. $Q=\lbrace \frac{p}{q}|p \in Z,q \in N^{+} \rbrace$;全体有理数集

全体实数记做R,R*为排除0的实数集,R+为全体正实数集.

子集概念:

  • 子集
  • 真子集
  • 集合相等:互为子集
  • 空集 $\emptyset$

    1.1.2 集合的运算

1.1.3 区间和领域

Android线程使用总结

发表于 2017-04-10 | 分类于 Android

1. Threading Performance

在程序开发的实践当中,为了让程序表现得更加流畅,我们肯定会需要使用到多线程来提升程序的并发执行性能。但是编写多线程并发的代码一直以来都是一个相对棘手的问题,所以想要获得更佳的程序性能,我们非常有必要掌握多线程并发编程的基础技能。 众所周知,Android 程序的大多数代码操作都必须执行在主线程,例如系统事件(例如设备屏幕发生旋转),输入事件(例如用户点击滑动等),程序回调服务,UI 绘制以及闹钟事件等等。那么我们在上述事件或者方法中插入的代码也将执行在主线程。

一旦我们在主线程里面添加了操作复杂的代码,这些代码就很可能阻碍主线程去响应点击/滑动事件,阻碍主线程的 UI 绘制等等。我们知道,为了让屏幕的刷新帧率达到 60fps,我们需要确保 16ms 内完成单次刷新的操作。一旦我们在主线程里面执行的任务过于繁重就可能导致接收到刷新信号的时候因为资源被占用而无法完成这次刷新操作,这样就会产生掉帧的现象,刷新帧率自然也就跟着下降了(一旦刷新帧率降到 20fps 左右,用户就可以明显感知到卡顿不流畅了)。

为了避免上面提到的掉帧问题,我们需要使用多线程的技术方案,把那些操作复杂的任务移动到其他线程当中执行,这样就不容易阻塞主线程的操作,也就减小了出现掉帧的可能性。

为主线程减轻负的多线程方案有哪些呢?这些方案分别适合在什么场景下使用?Android 系统为我们提供了若干组工具类来帮助解决这个问题。

  • AsyncTask: 为 UI 线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。
  • HandlerThread: 为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
  • ThreadPool: 把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。
  • IntentService: 适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。

了解这些系统提供的多线程工具类分别适合在什么场景下,可以帮助我们选择合适的解决方案,避免出现不可预期的麻烦。虽然使用多线程可以提高程序的并发量,但是我们需要特别注意因为引入多线程而可能伴随而来的内存问题。举个例子,在 Activity 内部定义的一个 AsyncTask,它属于一个内部类,该类本身和外面的 Activity 是有引用关系的,如果 Activity 要销毁的时候,AsyncTask 还仍然在运行,这会导致 Activity 没有办法完全释放,从而引发内存泄漏。所以说,多线程是提升程序性能的有效手段之一,但是使用多线程却需要十分谨慎小心,如果不了解背后的执行机制以及使用的注意事项,很可能引起严重的问题。

2. Understanding Android Threading

通常来说,一个线程需要经历三个生命阶段:开始,执行,结束。线程会在任务执行完毕之后结束,那么为了确保线程的存活,我们会在执行阶段给线程赋予不同的任务,然后在里面添加退出的条件从而确保任务能够执行完毕后退出。

在很多时候,线程不仅仅是线性执行一系列的任务就结束那么简单的,我们会需要增加一个任务队列,让线程不断的从任务队列中获取任务去进行执行,另外我们还可能在线程执行的任务过程中与其他的线程进行协作。如果这些细节都交给我们自己来处理,这将会是件极其繁琐又容易出错的事情。

所幸的是,Android 系统为我们提供了 Looper,Handler,MessageQueue 来帮助实现上面的线程任务模型:

  • Looper: 能够确保线程持续存活并且可以不断的从任务队列中获取任务并进行执行。

  • Handler: 能够帮助实现队列任务的管理,不仅仅能够把任务插入到队列的头部,尾部,还可以按照一定的时间延迟来确保任务从队列中能够来得及被取消掉。

  • MessageQueue: 使用 Intent,Message,Runnable 作为任务的载体在不同的线程之间进行传递。

把上面三个组件打包到一起进行协作,这就是 HandlerThread 我们知道,当程序被启动,系统会帮忙创建进程以及相应的主线程,而这个主线程其实就是一个 HandlerThread。这个主线程会需要处理系统事件,输入事件,系统回调的任务,UI绘制等等任务,为了避免主线程任务过重,我们就会需要不断的开启新的工作线程来处理那些子任务。

3. Memory & Threading

增加并发的线程数会导致内存消耗的增加,平衡好这两者的关系是非常重要的。我们知道,多线程并发访问同一块内存区域有可能带来很多问题,例如读写的权限争夺问题,ABA 问题等等。为了解决这些问题,我们会需要引入锁的概念。 在 Android 系统中也无法避免因为多线程的引入而导致出现诸如上文提到的种种问题。Android UI 对象的创建,更新,销毁等等操作都默认是执行在主线程,但是如果我们在非主线程对UI对象进行操作,程序将可能出现异常甚至是崩溃。

另外,在非 UI 线程中直接持有 UI 对象的引用也很可能出现问题。例如Work线程中持有某个 UI 对象的引用,在 Work 线程执行完毕之前,UI 对象在主线程中被从 ViewHierarchy 中移除了,这个时候 UI 对象的任何属性都已经不再可用了,另外对这个 UI 对象的更新操作也都没有任何意义了,因为它已经从 ViewHierarchy 中被移除,不再绘制到画面上了。

不仅如此,View 对象本身对所属的 Activity 是有引用关系的,如果工作线程持续保有 View 的引用,这就可能导致 Activity 无法完全释放。除了直接显式的引用关系可能导致内存泄露之外,我们还需要特别留意隐式的引用关系也可能导致泄露。例如通常我们会看到在 Activity 里面定义的一个 AsyncTask,这种类型的 AsyncTask 与外部的 Activity 是存在隐式引用关系的,只要 Task 没有结束,引用关系就会一直存在,这很容易导致 Activity 的泄漏。更糟糕的情况是,它不仅仅发生了内存泄漏,还可能导致程序异常或者崩溃。

为了解决上面的问题,我们需要谨记的原则就是:不要在任何非 UI 线程里面去持有 UI 对象的引用。系统为了确保所有的 UI 对象都只会被 UI 线程所进行创建,更新,销毁的操作,特地设计了对应的工作机制(当 Activity 被销毁的时候,由该 Activity 所触发的非 UI 线程都将无法对UI对象进行操作,否者就会抛出程序执行异常的错误)来防止 UI 对象被错误的使用。

4. Good AsyncTask Hunting

AsyncTask 是一个让人既爱又恨的组件,它提供了一种简便的异步处理机制,但是它又同时引入了一些令人厌恶的麻烦。一旦对 AsyncTask 使用不当,很可能对程序的性能带来负面影响,同时还可能导致内存泄露。 举个例子,常遇到的一个典型的使用场景:用户切换到某个界面,触发了界面上的图片的加载操作,因为图片的加载相对来说耗时比较长,我们需要在子线程中处理图片的加载,当图片在子线程中处理完成之后,再把处理好的图片返回给主线程,交给 UI 更新到画面上。

AsyncTask 的出现就是为了快速的实现上面的使用场景,AsyncTask 把在主线程里面的准备工作放到 onPreExecute()方法里面进行执行,doInBackground()方法执行在工作线程中,用来处理那些繁重的任务,一旦任务执行完毕,就会调用 onPostExecute()方法返回到主线程。

使用 AsyncTask 需要注意的问题有哪些呢?请关注以下几点: 首先,默认情况下,所有的 AsyncTask 任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。假设你按照顺序启动20个 AsyncTask,一旦其中的某个 AsyncTask 执行时间过长,队列中的其他剩余 AsyncTask 都处于阻塞状态,必须等到该任务执行完毕之后才能够有机会执行下一个任务。

为了解决上面提到的线性队列等待的问题,我们可以使用 AsyncTask.executeOnExecutor()强制指定 AsyncTask 使用线程池并发调度任务。

其次,如何才能够真正的取消一个 AsyncTask 的执行呢?我们知道 AsyncTaks 有提供 cancel()的方法,但是这个方法实际上做了什么事情呢?线程本身并不具备中止正在执行的代码的能力,为了能够让一个线程更早的被销毁,我们需要在 doInBackground()的代码中不断的添加程序是否被中止的判断逻辑.

一旦任务被成功中止,AsyncTask 就不会继续调用 onPostExecute(),而是通过调用 onCancelled()的回调方法反馈任务执行取消的结果。我们可以根据任务回调到哪个方法(是 onPostExecute 还是 onCancelled)来决定是对 UI 进行正常的更新还是把对应的任务所占用的内存进行销毁等。 最后,使用 AsyncTask 很容易导致内存泄漏,一旦把 AsyncTask 写成 Activity 的内部类的形式就很容易因为 AsyncTask 生命周期的不确定而导致 Activity 发生泄漏。

综上所述,AsyncTask 虽然提供了一种简单便捷的异步机制,但是我们还是很有必要特别关注到他的缺点,避免出现因为使用错误而导致的严重系统性能问题。

5. Getting a HandlerThread

大多数情况下,AsyncTask 都能够满足多线程并发的场景需要(在工作线程执行任务并返回结果到主线程),但是它并不是万能的。例如打开相机之后的预览帧数据是通过 onPreviewFrame()的方法进行回调的,onPreviewFrame()和 open()相机的方法是执行在同一个线程的。

如果这个回调方法执行在 UI 线程,那么在 onPreviewFrame()里面将要执行的数据转换操作将和主线程的界面绘制,事件传递等操作争抢系统资源,这就有可能影响到主界面的表现性能。

我们需要确保 onPreviewFrame()执行在工作线程。如果使用 AsyncTask,会因为 AsyncTask 默认的线性执行的特性(即使换成并发执行)会导致因为无法把任务及时传递给工作线程而导致任务在主线程中被延迟,直到工作线程空闲,才可以把任务切换到工作线程中进行执行。

所以我们需要的是一个执行在工作线程,同时又能够处理队列中的复杂任务的功能,而 HandlerThread 的出现就是为了实现这个功能的,它组合了 Handler,MessageQueue,Looper 实现了一个长时间运行的线程,不断的从队列中获取任务进行执行的功能。

回到刚才的处理相机回调数据的例子,使用 HandlerThread 我们可以把 open()操作与 onPreviewFrame()的操作执行在同一个线程,同时还避免了 AsyncTask 的弊端。如果需要在 onPreviewFrame()里面更新 UI,只需要调用 runOnUiThread()方法把任务回调给主线程就够了。

HandlerThread 比较合适处理那些在工作线程执行,需要花费时间偏长的任务。我们只需要把任务发送给 HandlerThread,然后就只需要等待任务执行结束的时候通知返回到主线程就好了。 另外很重要的一点是,一旦我们使用了 HandlerThread,需要特别注意给 HandlerThread 设置不同的线程优先级,CPU 会根据设置的不同线程优先级对所有的线程进行调度优化。

掌握 HandlerThread 与 AsyncTask 之间的优缺点,可以帮助我们选择合适的方案。

6. Swimming in Threadpools

线程池适合用在把任务进行分解,并发进行执行的场景。通常来说,系统里面会针对不同的任务设置一个单独的守护线程用来专门处理这项任务。例如使用 Networking Thread 用来专门处理网络请求的操作,使用 IO Thread 用来专门处理系统的 I\O 操作。针对那些场景,这样设计是没有问题的,因为对应的任务单次执行的时间并不长而且可以是顺序执行的。但是这种专属的单线程并不能满足所有的情况,例如我们需要一次性 decode 40张图片,每个线程需要执行 4ms 的时间,如果我们使用专属单线程的方案,所有图片执行完毕会需要花费 160ms(40*4),但是如果我们创建10个线程,每个线程执行4个任务,那么我们就只需要16ms就能够把所有的图片处理完毕。

为了能够实现上面的线程池模型,系统为我们提供了 ThreadPoolExecutor 帮助类来简化实现,剩下需要做的就只是对任务进行分解就好了。

使用线程池需要特别注意同时并发线程数量的控制,理论上来说,我们可以设置任意你想要的并发数量,但是这样做非常的不好。因为 CPU 只能同时执行固定数量的线程数,一旦同时并发的线程数量超过 CPU 能够同时执行的阈值,CPU 就需要花费精力来判断到底哪些线程的优先级比较高,需要在不同的线程之间进行调度切换。

一旦同时并发的线程数量达到一定的量级,这个时候 CPU 在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降。另外需要关注的一点是,每开一个新的线程,都会耗费至少 64K+ 的内存。为了能够方便的对线程数量进行控制,ThreadPoolExecutor 为我们提供了初始化的并发线程数量,以及最大的并发数量进行设置。

另外需要关注的一个问题是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真实的 CPU 核心数,因为 CPU 会在某些情况下选择对部分核心进行睡眠处理,在这种情况下,返回的数量就只能是激活的 CPU 核心数。

7. The Zen of IntentService

默认的 Service 是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的 AsyncTask 与 HandlerThread,我们还可以选择使用 IntentService 来实现异步操作。IntentService 继承自普通 Service 同时又在内部创建了一个 HandlerThread,在 onHandlerIntent()的回调里面处理扔到 IntentService 的任务。所以 IntentService 就不仅仅具备了异步线程的特性,还同时保留了 Service 不受主页面生命周期影响的特点。

如此一来,我们可以在 IntentService 里面通过设置闹钟间隔性的触发异步任务,例如刷新数据,更新缓存的图片或者是分析用户操作行为等等,当然处理这些任务需要小心谨慎。

使用 IntentService 需要特别留意以下几点:

  • 首先,因为 IntentService 内置的是 HandlerThread 作为异步线程,所以每一个交给 IntentService 的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • 其次,通常使用到 IntentService 的时候,我们会结合使用 BroadcastReceiver 把工作线程的任务执行结果返回给主 UI 线程。使用广播容易引起性能问题,我们可以使用 LocalBroadcastManager 来发送只在程序内部传递的广播,从而提升广播的性能。我们也可以使用 runOnUiThread() 快速回调到主 UI 线程。
  • 最后,包含正在运行的 IntentService 的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。
  • IntentService所有任务执行完后会执行自杀操作,此时再startService的时候会创建新的IntentService

8. Threading and Loaders

当启动工作线程的 Activity 被销毁的时候,我们应该做点什么呢?为了方便的控制工作线程的启动与结束,Android 为我们引入了 Loader 来解决这个问题。我们知道 Activity 有可能因为用户的主动切换而频繁的被创建与销毁,也有可能是因为类似屏幕发生旋转等被动原因而销毁再重建。在 Activity 不停的创建与销毁的过程当中,很有可能因为工作线程持有 Activity 的 View 而导致内存泄漏(因为工作线程很可能持有 View 的强引用,另外工作线程的生命周期还无法保证和 Activity 的生命周期一致,这样就容易发生内存泄漏了)。除了可能引起内存泄漏之外,在 Activity 被销毁之后,工作线程还继续更新视图是没有意义的,因为此时视图已经不在界面上显示了。

Loader 的出现就是为了确保工作线程能够和 Activity 的生命周期保持一致,同时避免出现前面提到的问题。

LoaderManager 会对查询的操作进行缓存,只要对应 Cursor 上的数据源没有发生变化,在配置信息发生改变的时候(例如屏幕的旋转),Loader 可以直接把缓存的数据回调到 onLoadFinished(),从而避免重新查询数据。另外系统会在 Loader 不再需要使用到的时候(例如使用 Back 按钮退出当前页面)回调 onLoaderReset()方法,我们可以在这里做数据的清除等等操作。 在 Activity 或者 Fragment 中使用 Loader 可以方便的实现异步加载的框架,Loader 有诸多优点。但是实现 Loader 的这套代码还是稍微有点点复杂,Android 官方为我们提供了使用 Loader 的示例代码进行参考学习。

9. The Importance of Thread Priority

理论上来说,我们的程序可以创建出非常多的子线程一起并发执行的,可是基于 CPU 时间片轮转调度的机制,不可能所有的线程都可以同时被调度执行,CPU 需要根据线程的优先级赋予不同的时间片。

Android 系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为 forground 的那部分线程会大致占用掉 CPU 的90%左右的时间片,background 的那部分线程就总共只能分享到5%-10%左右的时间片。之所以设计成这样是因为 forground 的程序本身的优先级就更高,理应得到更多的执行时间。

默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主 UI 线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占 CPU 资源,需要把这些线程的优先级进行降低处理,这样才能给帮组 CPU 识别主次,提高主线程所能得到的系统资源。

在 Android 系统里面,我们可以通过 android.os.Process.setThreadPriority(int) 设置线程的优先级,参数范围从-20到19,数值越小优先级越高。Android 系统还为我们提供了以下的一些预设值,我们可以通过给不同的工作线程设置不同数值的优先级来达到更细粒度的控制。

大多数情况下,新创建的线程优先级会被设置为默认的0,主线程设置为0的时候,新创建的线程还可以利用 THREAD_PRIORITY_LESS_FAVORABLE 或者 THREAD_PRIORITY_MORE_FAVORABLE 来控制线程的优先级。

Android 系统里面的 AsyncTask 与 IntentService已经默认帮助我们设置线程的优先级,但是对于那些非官方提供的多线程工具类,我们需要特别留意根据需要自己手动来设置线程的优先级。

10. Profile GPU Rendering : M Update

从 Android M 系统开始,系统更新了 GPU Profiling 的工具来帮助我们定位 UI 的渲染性能问题。早期的 CPU Profiling 工具只能粗略的显示出 Process,Execute,Update 三大步骤的时间耗费情况。 但是仅仅显示三大步骤的时间耗费情况,还是不太能够清晰帮助我们定位具体的程序代码问题,所以在 Android M 版本开始,GPU Profiling 工具把渲染操作拆解成如下8个详细的步骤进行显示。

旧版本中提到的 Proces,Execute,Update 还是继续得到了保留,他们的对应关系如下:

接下去我们看下其他五个步骤分别代表了什么含义:

  • Sync & Upload:通常表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片本身的大小。
  • Measure & Layout:这里表示的是布局的 onMeasure 与 onLayout 所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题。
  • Animation:表示的是计算执行动画所需要花费的时间,包含的动画有 ObjectAnimator,ViewPropertyAnimator,Transition 等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等。
  • Input Handling:表示的是系统处理输入事件所耗费的时间,粗略等于对于的事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作。
  • Misc/Vsync Delay:如果稍加注意,我们可以在开发应用的 Log 日志里面看到这样一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。这意味着我们在主线程执行了太多的任务,导致 UI 渲染跟不上 vSync 的信号而出现掉帧的情况。

上面八种不同的颜色区分了不同的操作所耗费的时间,为了便于我们迅速找出那些有问题的步骤,GPU Profiling 工具会显示 16ms 的阈值线,这样就很容易找出那些不合理的性能问题,再仔细看对应具体哪个步骤相对来说耗费时间比例更大,结合上面介绍的细化步骤,从而快速定位问题,修复问题。

http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1022

线程生命周期

tips-android-thread-201938105046

Android系统基于精简过后的linux内核,Linux系统的调度器在分配time slice的时候,采用的CFS(Completely fair scheduler)策略,不仅会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量。优先级高的线程不一定能在争取timeslice上有绝对的优势。

Android将进程分为多个group,其中有两种比较重要:

  • default group:能获得绝大部分的timeslice(UI线程就属于此列)
  • background group:工作线程,最多被分配10%的timeslice

其中background group需要开发者显示的归位(官方建议)

1
2
3
4
5
6
7
new Thread(new Runnable(){
@Override
public void run(){
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// do sth
}
}).start();

线程间通信

  • 共享内存
  • 文件、数据库
  • Handler
  • Java里的wait notify notifyAll

UI / Main thread

系统启动时创建的线程,用来处理页面的绘制。

不可以把耗时操作放在ui线程中,如网络请求、数据库的读写等等,阻塞超过5s会发生ANR错误。

因为Android UI toolkit不是线程安全的,故而所有的页面绘制都必须放在UI线程中做。 有个黑科技是可以在Activity的onResume()前使用非UI线程绘制UI,因为检测线程是否是UI线程是在ViewRootImpl中进行检测的,而ViewRootImpl是在onResume()时才会进行初始化的

仅限了解,请勿在实际项目中尝试。

在非UI线程中更新UI

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)
  • handler
  • AsyncTask

线程使用方式

Handler

Can’t create handler inside thread that has not called Looper.prepare()

handler所在的线程必须调用过Looper.prepare方法,否则没有looper来进行工作

HandlerThread

Thread类

  • 启动了新的线程,没有任务的概念,不能做状态的管理。
  • start之后,run当中的代码就一定会执行到底,中途无法取消。 作为匿名内部类持有了外部类的引用,在线程退出之前,会阻碍GC的回收,在一段时间内造成内存泄露
  • 没有线程切换的接口,要传递处理结果到UI线程,需要些额外的线程切换代码
  • 如果从UI线程启动,该线程优先级默认为Default

AsyncTask<Params,Progress,Result>

必须遵守的规则:

  • Task实例必须在UI线程中创建
  • execute方法必须在UI线程中调用
  • 该Task只能被执行一次,多次调用时会出现异常
  • 不要手动调用onPreExecute….等生命周期方法,使用publishProgress()更新进度

ThreadPoolExecutor

Thread、AsyncTask适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务时,ThreadPoolExecutor是更好的选择。

1
2
3
4
5
6
public static Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

//execute
THREAD_POOL_EXECUTOR.execute(Runnable XX);

代价

  • 每一个新线程至少消耗64kb内存
  • 线程的切换回带来额外开销(switch context)
  • 尽量复用已有的工作线程

Android进程中线程

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
ps | grep 'joyodream'  找到 app 的pid; joyodream为包名的一部分
ps -t pid 查找指定pid的线程信息 例如:

ps -t 14500
手机
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a118 14500 375 2521896 137196 SyS_epoll_ 0000000000 S com.joyodream.bindtest
u0_a118 14506 14500 2521896 137196 do_sigtime 0000000000 S Signal Catcher
u0_a118 14507 14500 2521896 137196 poll_sched 0000000000 S JDWP
u0_a118 14508 14500 2521896 137196 futex_wait 0000000000 S ReferenceQueueD
u0_a118 14509 14500 2521896 137196 futex_wait 0000000000 S FinalizerDaemon
u0_a118 14510 14500 2521896 137196 futex_wait 0000000000 S FinalizerWatchd
u0_a118 14511 14500 2521896 137196 futex_wait 0000000000 S HeapTaskDaemon
u0_a118 14512 14500 2521896 137196 binder_thr 0000000000 S Binder_1
u0_a118 14513 14500 2521896 137196 binder_thr 0000000000 S Binder_2
u0_a118 14515 14500 2521896 137196 SyS_epoll_ 0000000000 S RenderThread
u0_a118 14517 14500 2521896 137196 futex_wait 0000000000 S mali-mem-purge
u0_a118 14518 14500 2521896 137196 kbase_read 0000000000 S mali-event-hnd
u0_a118 14519 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14520 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14521 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14522 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14523 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14524 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14525 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14526 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14527 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14528 14500 2521896 137196 futex_wait 0000000000 S mali-utility-wo
u0_a118 14529 14500 2521896 137196 futex_wait 0000000000 S mali-renderer
u0_a118 14530 14500 2521896 137196 futex_wait 0000000000 S mali-hist-dump
u0_a118 14531 14500 2521896 137196 futex_wait 0000000000 S ged-swd
u0_a118 14533 14500 2521896 137196 futex_wait 0000000000 S hwuiTask1

1+ 手机
u0_a100 19418 284 1609932 43208 ffffffff 00000000 S com.joyodream.bindtest
u0_a100 19424 19418 1609932 43208 ffffffff 00000000 S Heap thread poo
u0_a100 19425 19418 1609932 43208 ffffffff 00000000 S Heap thread poo
u0_a100 19426 19418 1609932 43208 ffffffff 00000000 S Heap thread poo
u0_a100 19427 19418 1609932 43208 ffffffff 00000000 S Signal Catcher
u0_a100 19428 19418 1609932 43208 ffffffff 00000000 S JDWP
u0_a100 19429 19418 1609932 43208 ffffffff 00000000 S ReferenceQueueD
u0_a100 19430 19418 1609932 43208 ffffffff 00000000 S FinalizerDaemon
u0_a100 19431 19418 1609932 43208 ffffffff 00000000 S FinalizerWatchd
u0_a100 19432 19418 1609932 43208 ffffffff 00000000 S HeapTrimmerDaem
u0_a100 19433 19418 1609932 43208 ffffffff 00000000 S GCDaemon
u0_a100 19434 19418 1609932 43208 ffffffff 00000000 S Binder_1
u0_a100 19435 19418 1609932 43208 ffffffff 00000000 S Binder_2
u0_a100 19438 19418 1609932 43208 ffffffff 00000000 S android.bg
u0_a100 19464 19418 1609932 43208 ffffffff 00000000 S RenderThread
u0_a100 19465 19418 1609932 43208 ffffffff 00000000 S AsyncQueryWorke
u0_a100 19470 19418 1609932 43208 ffffffff 00000000 S GL updater
u0_a100 19474 19418 1609932 43208 ffffffff 00000000 S hwuiTask1
一个Hellow world App,包含至少2个Binder线程(偶尔出现3个),FinalizerDaemon线程、FinalizerWatchd线程、RenderThread、主线程、ReferenceQueued、HeapTaskDaemon、SignalCatcher、GCDaemon、hwuiTask

三星SM N9200 7.0
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a370 19657 3183 1916084 87592 SyS_epoll_ 0000000000 S com.lianjia.demo
u0_a370 19662 19657 1916084 87592 futex_wait 0000000000 S Jit thread pool
u0_a370 19663 19657 1916084 87592 do_sigtime 0000000000 S Signal Catcher
u0_a370 19664 19657 1916084 87592 poll_sched 0000000000 S JDWP
u0_a370 19666 19657 1916084 87592 futex_wait 0000000000 S ReferenceQueueD
u0_a370 19667 19657 1916084 87592 futex_wait 0000000000 S FinalizerDaemon
u0_a370 19668 19657 1916084 87592 futex_wait 0000000000 S FinalizerWatchd
u0_a370 19669 19657 1916084 87592 futex_wait 0000000000 S HeapTaskDaemon
u0_a370 19670 19657 1916084 87592 binder_thr 0000000000 S Binder:19657_1
u0_a370 19671 19657 1916084 87592 binder_thr 0000000000 S Binder:19657_2
u0_a370 19672 19657 1916084 87592 futex_wait 0000000000 S Profile Saver
u0_a370 19676 19657 1916084 87592 SyS_epoll_ 0000000000 S RenderThread
u0_a370 19678 19657 1916084 87592 futex_wait 0000000000 S mali-mem-purge
u0_a370 19679 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19680 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19681 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19682 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19683 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19684 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19685 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19686 19657 1916084 87592 futex_wait 0000000000 S mali-utility-wo
u0_a370 19687 19657 1916084 87592 poll_sched 0000000000 S mali-cmar-backe
u0_a370 19688 19657 1916084 87592 futex_wait 0000000000 S mali-hist-dump
u0_a370 19689 19657 1916084 87592 futex_wait 0000000000 S hwuiTask1
  1. “Binder_1”@4,098 in group “main”: RUNNING 就是我们的ApplicationThread,这个类实现了IBinder接口,用于进程间通信。具体来说,是我们的应用程序和Ams通信的工具。
  2. “Binder_2”@4,099 in group “main”: RUNNING 就是我们的ViewRoot.W对象,它也是实现了IBinder接口,是用于我们的应用程序和Wms通信的工具。
  3. “FinalizerDaemon”@4,095: WAIT
  4. “FinalizerWatchdogDaemon”@4,096: WAIT
  5. “HeapTaskDaemon”@4,097: MONITOR
  6. “main”@4,092 in group “main”: SLEEPING //UI线程
  7. “ReferenceQueueDaemon”@4,094: WAIT
  8. “Signal Catcher”@4,093: WAIT

多个进程同时调用一个ContentProvider的query获取数据,ContentPrvoider是如何反应的呢?

一个content provider可以接受来自另外一个进程的数据请求。尽管ContentResolver与ContentProvider类隐藏了实现细节,但是ContentProvider所提供的query(),insert(),delete(),update()都是在ContentProvider进程的线程池中被调用执行的,而不是进程的主线程中。这个线程池是有Binder创建和维护的,其实使用的就是每个应用进程中的Binder线程池。 你觉得Android设计ContentProvider的目的是什么呢?

  1. 隐藏数据的实现方式,对外提供统一的数据访问接口;
  2. 更好的数据访问权限管理。ContentProvider可以对开发的数据进行权限设置,不同的URI可以对应不同的权限,只有符合权限要求的组件才能访问到ContentProvider的具体操作。
  3. ContentProvider封装了跨进程共享的逻辑,我们只需要Uri即可访问数据。由系统来管理ContentProvider的创建、生命周期及访问的线程分配,简化我们在应用间共享数据(进程间通信)的方式。我们只管通过ContentResolver访问ContentProvider所提示的数据接口,而不需要担心它所在进程是启动还是未启动。

运行在主线程的ContentProvider为什么不会影响主线程的UI操作? 标准答案: ContentProvider的onCreate()是运行在UI线程的,而query(),insert(),delete(),update()是运行在线程池中的工作线程的,所以调用这向个方法并不会阻塞ContentProvider所在进程的主线程,但可能会阻塞调用者所在的进程的UI线程!

所以,调用ContentProvider的操作仍然要放在子线程中去做。虽然直接的CRUD的操作是在工作线程的,但系统会让你的调用线程等待这个异步的操作完成,你才可以继续线程之前的工作

MP4格式解析

发表于 2017-04-09 | 分类于 音视频封装

目前MP4的概念被炒得很火,也很乱。最开始MP4指的是音频(MP3的升级版),即MPEG-2 AAC标准。随后MP4概念被转移到视频上,对应的是MPEG-4标准。而现在我们流行的叫法,多半是指能播放MPEG-4标准编码格式视频的播放器。但是这篇文章介绍的内容跟上面这些都无关,我们要讨论的是MP4文件封装格式,对应的标准为ISO/IEC 14496-12,即信息技术 视听对象编码的第12部分:ISO 基本媒体文件格式(Information technology Coding of audio-visual objects Part 12: ISO base media file format)。ISO/IEC组织指定的标准一般用数字表示,ISO/IEC 14496即MPEG-4标准。

MP4视频文件封装格式是基于QuickTime容器格式定义的,因此参考QuickTime的格式定义对理解MP4文件格式很有帮助。MP4文件格式是一个十分开放的容器,几乎可以用来描述所有的媒体结构,MP4文件中的媒体描述与媒体数据是分开的,并且媒体数据的组织也很自由,不一定要按照时间顺序排列,甚至媒体数据可以直接引用其他文件。同时,MP4也支持流媒体。MP4目前被广泛用于封装h.264视频和AAC音频,是高清视频的代表。MP4格式的官方文件后缀名是“.mp4”,还有其他的以mp4为基础进行的扩展或者是缩水版本的格式,包括:M4V, 3GP, F4V等。

1.概述

MP4文件中的所有数据都装在box(QuickTime中为atom)中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。

下面是一些概念:

  • track 表示一些sample的集合,对于媒体数据来说,track表示一个视频或音频序列。
  • hint track 这个特殊的track并不包含媒体数据,而是包含了一些将其他数据track打包成流媒体的指示信息。
  • sample 对于非hint track来说,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频,它们统称sample。对于hint track,sample定义一个或多个流媒体包的格式。
  • sample table 指明sampe时序和物理布局的表。
  • chunk 一个track的几个sample组成的单元。

不讨论涉及hint的内容,只关注包含媒体数据的本地MP4文件。下图为一个典型的MP4文件的结构树。 MP4文件结构树

2.Box

box中的字节序为网络字节序,也就是大端字节序(Big-Endian),简单的说,就是一个32位的4字节整数存储方式为高位字节在内存的低端。Box由header和body组成,其中header统一指明box的大小和类型,body根据类型有不同的意义和格式。

标准的box开头的4个字节(32位)为box size,该大小包括box header和box body整个box的大小,这样我们就可以在文件中定位各个box。如果size为1,则表示这个box的大小为large size,真正的size值要在largesize域上得到。(实际上只有“mdat”类型的box才有可能用到large size。)如果size为0,表示该box为文件的最后一个box,文件结尾即为该box结尾。(同样只存在于“mdat”类型的box中。)size后面紧跟的32位为box type,一般是4个字符,如“ftyp”、“moov”等,这些box type都是已经预定义好的,分别表示固定的意义。如果是“uuid”,表示该box为用户扩展类型。如果box type是未定义的,应该将其忽略。

3.File Type Box(ftyp)

该box有且只有1个,并且只能被包含在文件层,而不能被其他box包含。该box应该被放在文件的最开始,指示该MP4文件应用的相关信息。 “ftyp” body依次包括1个32位的major brand(4个字符),1个32位的minor version(整数)和1个以32位(4个字符)为单位元素的数组compatible brands。这些都是用来指示文件应用级别的信息。该box的字节实例如下:

1
2
00000000h: 00 00 00 18 66 74 79 70 6D 70 34 32 00 00 00 01 ; ....ftypmp42....
00000010h: 6D 70 34 32 6D 70 34 31 00 00 5A EB 6D 6F 6F 76 ; mp42mp41..Zmoov

4.Movie Box(moov)

该box包含了文件媒体的metadata信息,“moov”是一个container box,具体内容信息由子box诠释。同File Type Box一样,该box有且只有一个,且只被包含在文件层。一般情况下,“moov”会紧随“ftyp”出现。

一般情况下(限于篇幅,本文只讲解常见的MP4文件结构),“moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现(对于其他container box来说,header box都应作为首个子box出现)。“trak”包含了一个track的相关信息,是一个container box。下图为部分“moov”的字节实例,其中红色部分为box header,绿色为“mvhd”,黄色为一部分“trak”。 moov box

4.1 Movie Header Box(mvhd)

“mvhd”接口如下表:

字段 字节数 意义
box size 4 box大小
box type 4 box类型
version 1 box版本,0或1,一般为0。(以下字节数均按version=0)
flags 3
creation time 4 创建时间(相对于UTC时间1904-01-01零点的秒数)
modification time 4 修改时间
time scale 4 文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数
duration 4 该track的时间长度,用duration和time scale值可以计算track时长,比如audio track的time scale = 8000, duration = 560128,时长为70.016,video track的time scale = 600, duration = 42000,时长为70
rate 4 推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16] 格式,该值为1.0(0x00010000)表示正常前向播放
volume 2 与rate类似,[8.8] 格式,1.0(0x0100)表示最大音量
reserved 10 保留位
matrix 36 视频变换矩阵
pre-defined 24
next track id 4 下一个track使用的id号

mvhd

4.2Track Box(trak)

“trak”也是一个container box,其子box包含了该track的媒体数据引用和描述(hint track除外)。一个MP4文件中的媒体可以包含多个track,且至少有一个track,这些track之间彼此独立,有自己的时间和空间信息。“trak”必须包含一个“tkhd”和一个“mdia”,此外还有很多可选的box(略)。其中“tkhd”为track header box,“mdia”为media box,该box是一个包含一些track媒体数据信息box的container box。

box类型说明
ftypefile type,说明文件类型
moovmetadata container,存放媒体信息的地方
mvhdmovie header,文件的总体信息,如时长,创建时间等
mvhdmovie header,文件的总体信息,如时长,创建时间等
traktrack or stream container,存放视频/音频流的容器
tkhdtrack header,track的总体信息,如时长,宽高等
mediatrak media information container
mdhdmedia header,定义TimeScale,trak需要通过TimeScale转换成真实时间
hdlrhandler,表明本trak类型,指明是`video/audio/还是hint`
minfmedia information container,数据在子box中
stblsample table box,存放时间/偏移的映射关系表,数据在子box中
stsdsample descriptions
stts(decoding)time-to-sample,"时戳-sample序号"的映射表
stscsample-to-chunk,sample和chunk的映射表,这里的算法比较巧妙
stszsample size,每个sample的大小
stz2sample size,另一种sample size的存储算法,更节省空间
stsssync sample table,可随机访问的sample列表(关键帧列表)
stcochunk offset,每个chunk的偏移,sample的偏移可根据其他box推算出来
co6464-bit chunk offset
mdatmedia data container,具体的媒体数据

Mdat Box

引用

http://xhelmboyx.tripod.com/formats/mp4-layout.txt

Android系列基础知识

发表于 2015-04-13 | 分类于 Android

四大组件

Activity

Activity生命周期

image

不同场景下Activity生命周期的变化过程
  • 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
  • Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
  • Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
  • Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity,若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()
  • 锁定屏与解锁屏幕 只会调用onPause(),而不会调用onStop方法,开屏后则调用onResume()
Activity 四中launchMode
  • standard
  • singleTop
  • singleTask
  • singleInstance 我们可以在AndroidManifest.xml配置的android:launchMode属性为以上四种之一。
  1. standard standard模式是默认的启动模式,不用为配置android:launchMode属性即可,当然也可以指定值为standard。standard启动模式,不管有没有已存在的实例,都生成新的实例。
  2. singleTop 我们在上面的基础上为指定属性android:launchMode=”singleTop”,系统就会按照singleTop启动模式处理跳转行为。跳转时系统会先在栈结构中寻找是否有一个Activity实例正位于栈顶,如果有则不再生成新的,而是直接使用。如果系统发现存在有Activity实例,但不是位于栈顶,重新生成一个实例。 这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。
  3. singleTask 如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。
  4. singleInstance 这种启动模式比较特殊,因为它会启用一个新的栈结构,将Acitvity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。

LaunchMode使用场景

  1. singleTop适合接收通知启动的内容显示页面。例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很烦人的。
  2. singleTask适合作为程序入口点。例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
  3. singleInstance应用场景:闹铃的响铃界面。 你以前设置了一个闹铃:上午6点。在上午5点58分,你启动了闹铃设置界面,并按 Home 键回桌面;在上午5点59分时,你在微信和朋友聊天;在6点时,闹铃响了,并且弹出了一个对话框形式的 Activity(名为 AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以 SingleInstance 加载模式打开的),你按返回键,回到的是微信的聊天界面,这是因为 AlarmAlertActivity 所在的 Task 的栈只有他一个元素, 因此退出之后这个 Task 的栈空了。如果是以 SingleTask 打开 AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。
    fragment
    image
问题
  1. 若Activity已经销毁,此时AsynTask执行完并返回结果,会报异常么? 当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,折会报 view not attached to window manager 异常.同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步
  2. 内存不足时,系统会杀死后台的Activity,如果需要进行一些临时状态的保存,在哪个方法进行:Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
  3. Android两个应用能在同一个任务栈吗?栈一般以包名命名,两个应用的签名和udid要相同

Service

保证Service在后台不被kill的方法
  1. Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
  2. 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别​,除非在系统内存非常缺,否则此进程不会被 kill
  3. 双进程Service: 让2个进程互相保护**,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
  4. QQ黑科技: 在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
  5. 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用 Android4.0系列的一个漏洞,已经确认可行
  6. 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android->- Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以上的版本不可行)
  7. 用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
  8. 在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
  9. 联系厂商,加入白名单
Service进程优先级

在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

IntentServices

IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题

生成一个默认的且与线程相互独立的工作线程执行所有发送到onStartCommand()方法的Intent,可以在onHandleIntent()中处理.

串行队列,每次只运行一个任务,不存在线程安全问题,所有任务执行完后自动停止服务,不需要自己手动调用stopSelf()来停止.

BroadcastReceiver

Android引入广播机制原因
  • 从MVC的角度考虑(应用程序内) 其实回答这个问题的时候还可以这样问,android为什么要有那4大组件,现在的移动开发模型基本上也是照搬的web那一套MVC架构,只不过是改了点嫁妆而已。android的四大组件本质上就是为了实现移动或者说嵌入式设备上的MVC架构,它们之间有时候是一种相互依存的关系,有时候又是一种补充关系,引入广播机制可以方便几大组件的信息和数据交互。
  • 程序间互通消息(例如在自己的应用程序内监听系统来电)
  • 效率上(参考UDP的广播协议在局域网的方便性)
  • 设计模式上(反转控制的一种应用,类似监听者模式)
注册广播的两种方法
  1. 静态注册:在清单文件中注册, 常见的有监听设备启动,常驻注册不会随程序生命周期改变
  2. 动态注册:在代码中注册,随着程序的结束,也就停止接受广播了

    补充一点:有些广播只能通过动态方式注册,比如时间变化事件、屏幕亮灭事件、电量变更事件,因为这些事件触发频率通常很高,如果允许后台监听,会导致进程频繁创建和销毁,从而影响系统整体性能

两种广播类型

普通广播为全局广播,LocalBroadcastManager是Android Support包提供了一个工具,是用来在同一个应用内的不同组件间发送Broadcast的。

使用LocalBroadcastManager有如下好处:

  • 发送的广播只会在自己App内传播,不会泄露给其他App,确保隐私数据不会泄露
  • 其他App也无法向你的App发送该广播,不用担心其他App会来搞破坏
  • 比系统全局广播更加高效

和系统广播使用方式类似: 先通过LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 获取实例,然后通过函数 registerReceiver来注册监听器

ContentProvider

实现数据共享

当一个应用程序需要把自己的数据暴露给其他程序使用时,该就用程序就可通过提供ContentProvider来实现;其他应用程序就可通过ContentResolver来操作ContentProvider暴露的数据。 一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。

ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据。 步骤:

  1. 定义自己的ContentProvider类,该类需要继承Android提供的ContentProvider基类。
  2. 在AndroidManifest.xml文件中注册个ContentProvider,注册ContenProvider时需要为它绑定一个URL。 例: android:authorities=”com.myit.providers.MyProvider” /> 说明:authorities就相当于为该ContentProvider指定URL。 注册后,其他应用程序就可以通过该Uri来访问MyProvider所暴露的数据了。
  3. 接下来,使用ContentResolver操作数据,Context提供了如下方法来获取ContentResolver对象。 一般来说,ContentProvider是单例模式,当多个应用程序通过ContentResolver来操作 ContentProvider提供的数据时,ContentResolver调用的数据操作将会委托给同一个ContentProvider处理。 使用ContentResolver操作数据只需两步: 1、调用Activity的ContentResolver获取ContentResolver对象。 2、根据需要调用ContentResolver的insert()、delete()、update()和query()方法操作数据即可

ContentProvider的主要还是用于数据共享,其可以对Sqlite,SharePreferences,File等进行数据操作用来共享数据。而sql的可以理解为数据库的一门语言,可以使用它完成CRUD等一系列的操作

View与布局

LinearLayout和RelativeLayout性能对比

  1. RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
  2. RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
  3. 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。 最后再思考一下文章开头那个矛盾的问题,为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

绘制

自定义view的基本流程
  1. 明确需求,确定你想实现的效果
  2. 确定是使用组合控件的形式还是全新自定义的形式,组合控件即使用多个系统控件来合成一个新控件,你比如titilebar,这种形式相对简单,参考
  3. 如果是完全自定义一个view的话,你首先需要考虑继承哪个类,是View呢,还是ImageView等子类。
  4. 根据需要去复写View#onDraw、View#onMeasure、View#onLayout方法
  5. 根据需要去复写dispatchTouchEvent、onTouchEvent方法
  6. 根据需要为你的自定义view提供自定义属性,即编写attr.xml,然后在代码中通过TypedArray等类获取到自定义属性值
  7. 需要处理滑动冲突、像素转换等问题
View的绘制流程

image measure()方法,layout(),draw()三个方法主要存放了一些标识符,来判断每个View是否需要再重新测量,布局或者绘制,主要的绘制过程还是在onMeasure,onLayout,onDraw这个三个方法中

  1. onMesarue() 为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性: mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
  2. onLayout() 为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
  3. onDraw() 开始绘制图像,绘制的流程如下
    1. 首先绘制该View的背景
    2. 调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
    3. 如果该View是ViewGroup,调用dispatchDraw ()方法绘制子视图 绘制滚动条

自定义View执行invalidate()方法,不会回调onDraw()可能的原因

  1. 自定义一个view时,重写onDraw。调用view.invalidate(),会触发onDraw和computeScroll()。前提是该view被附加在当前窗口. view.postInvalidate(); //是在非UI线程上调用的
  2. 自定义一个ViewGroup,重写onDraw。onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。因此,一般直接重写dispatchDraw来绘制viewGroup.自定义一个ViewGroup,dispatchDraw会调用drawChild.
事件传递机制
  1. 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
  2. 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
  3. 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
  4. 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
  5. OnTouchListener优先于onTouchEvent()对事件进行消费。

上面的消费即表示相应函数返回值为true。 View中setOnTouchListener中的onTouch,onTouchEvent,onClick的执行顺序:onTouch优于onTouchEvent,onTouchEvent优于onClick

Android下滑冲突的常见解决思路:相关的滑动组件 重写onInterceptTouchEvent,然后判断根据xy值,来决定是否要拦截当前操作

动画

三种动画

  • 逐帧动画(Drawable Animation): 加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间
  • 补间动画(Tween Animation): Tween可以对View对象实现一系列简单的动画效果,比如位移,缩放,旋转,透明度等等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。
  • 属性动画(Property Animation): 动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了

动画原理

Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件

属性动画特性

如果你的需求中只需要对View进行移动、缩放、旋转和淡入淡出操作,那么补间动画确实已经足够健全了。但是很显然,这些功能是不足以覆盖所有的场景的,一旦我们的需求超出了移动、缩放、旋转和淡入淡出这四种对View的操作,那么补间动画就不能再帮我们忙了,也就是说它在功能和可扩展方面都有相当大的局限性,那么下面我们就来看看补间动画所不能胜任的场景。 注意上面我在介绍补间动画的时候都有使用“对View进行操作”这样的描述,没错,补间动画是只能够作用在View上的。也就是说,我们可以对一个Button、TextView、甚至是LinearLayout、或者其它任何继承自View的组件进行动画操作,但是如果我们想要对一个非View的对象进行动画操作,抱歉,补间动画就帮不上忙了。可能有的朋友会感到不能理解,我怎么会需要对一个非View的对象进行动画操作呢?这里我举一个简单的例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。显然,补间动画是不具备这个功能的,这是它的第一个缺陷。 然后补间动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,之前的补间动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。 最后,补间动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。

优化

布局优化

  • 避免OverDraw过渡绘制
  • 优化布局层级
  • 避免嵌套过多无用布局
  • 当我们在画布局的时候,如果能实现相同的功能,优先考虑相对布局,然后在考虑别的布局,不要用绝对布局。
  • 使用<include />标签把复杂的界面需要抽取出来
  • 使用<merge />标签,因为它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。核心功能就是减少冗余的层次从而达到优化UI的目的!
  • ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。

    ListView卡顿的原因以及优化策略

  • 重用converView: 通过复用converview来减少不必要的view的创建,另外Infalte操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。
  • 减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过converview的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findviewbyid操作
  • 避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glide
  • Item的布局层次结构尽量简单,避免布局太深或者不必要的重绘
  • 尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
  • 在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现
  • 使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
  • ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
  • 尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。

ViewHolder为什么要被声明成静态内部类 这个是考静态内部类和非静态内部类的主要区别之一。非静态内部类会隐式持有外部类的引用,就像大家经常将自定义的adapter在Activity类里,然后在adapter类里面是可以随意调用外部activity的方法的。当你将内部类定义为static时,你就调用不了外部类的实例方法了,因为这时候静态内部类是不持有外部类的引用的。声明ViewHolder静态内部类,可以将ViewHolder和外部类解引用。大家会说一般ViewHolder都很简单,不定义为static也没事吧。确实如此,但是如果你将它定义为static的,说明你懂这些含义。万一有一天你在这个ViewHolder加入一些复杂逻辑,做了一些耗时工作,那么如果ViewHolder是非静态内部类的话,就很容易出现内存泄露。如果是静态的话,你就不能直接引用外部类,迫使你关注如何避免相互引用。 所以将 ViewHolder内部类 定义为静态的,是一种好习惯

内存泄露问题

  1. 资源对象没有关闭造成,如查询数据库没有关闭游标
  2. 构造Adapter时,没有使用缓存ConvertView
  3. Bitmap对象在不使用时调用recycle()释放内存
  4. context逃逸问题
  5. 注册没有取消,如动态注册广播在Activity销毁前没有unregisterReceiver
  6. 集合对象未清理,如无用时没有释放对象的引用
  7. 在Activity中使用非静态的内部类,并开启一个长时间运行的线程,因为内部类持有Activity的引用,会导致Activity本来可以被gc时却长期得不到回收

OOM发生情况

  1. 类的静态变量持有大数据对象 静态变量长期维持到大数据对象的引用,阻止垃圾回收。
  2. 非静态内部类存在静态实例 非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
  3. 资源对象未关闭 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们, 以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。 解决办法: 比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭), 如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小, 对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险,记得try catch后,在finally方法中关闭连接
  4. Handler内存泄漏 Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。 解决办法:声明handler为static类,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放.如果内部类实在需要用到外部类的对象,可在其内部声明一个弱引用引用外部类 一些不良代码习惯 有些代码并不造成内存泄露,但是他们的资源没有得到重用,频繁的申请内存和销毁内存,消耗CPU资源的同时,也引起内存抖动 解决方案 如果需要频繁的申请内存对象和和释放对象,可以考虑使用对象池来增加对象的复用。 例如ListView便是采用这种思想,通过复用converview来避免频繁的GC

避免oom

  1. 使用更加轻量的数据结构 例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
  2. 避免在Android里面使用Enum Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
  3. 减小Bitmap对象的内存占用 Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施: inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 decode format:解码格式,选择ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
  4. Bitmap对象的复用 缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施 LRUCache : “最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据, inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
  5. 使用更小的图片 在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
  6. StringBuilder 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
  7. 避免在onDraw方法里面执行对象的创建 类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

ANR

ANR全称Application Not Responding,意思就是程序未响应。如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,用户可以自行选择继续等待亦或者是停止当前程序。一旦出现下面两种情况,则弹出ANR对话框

  1. 应用在5秒内未响应用户的输入事件(如按键或者触摸)
  2. BroadcastReceiver未在10秒内完成相关的处理

Service在特定的时间内无法处理完成 超时的原因一般有两种: (1)当前的事件没有机会得到处理(UI线程正在处理前一个事件没有及时完成或者looper被某种原因阻塞住) (2)当前的事件正在处理,但没有及时完成 UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍UI线程的操作)放入单独的线程处理,尽量用Handler来处理UI thread和thread之间的交互。 UI线程主要包括如下:

  • Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick()
  • AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel()
  • Mainthread handler: handleMessage(), post(runnable r)
如何定位ANR错误

开发机器上,查看/data/anr/traces.text.最新的ANR信息在最开始部分.

如何避免ANR

避免ANR最核心的一点就是在主线程减少耗时操作.通常需要从以下几个方案下手:

  1. 使用子线程处理耗时IO操作。
  2. 降低子线程优先级使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
  3. 使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
  4. Activity的onCreate和onResume回调中尽量避免耗时的代码
  5. BroadcastReceiver中onReceive代码也要尽量减少耗时操作建议使用IntentService处理。IntentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题

native

Dalvik,JVM,ART区别

什么是Dalvik?

Dalvik是Google公司自己设计用于Android平台的虚拟机。 Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。 它可以支持已转换为**.dex格式**的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。 Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 很长时间以来,Dalvik虚拟机一直被用户指责为拖慢安卓系统运行速度不如IOS的根源。 2014年6月25日,Android L 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除Dalvik,代替它的是传闻已久的ART。

什么是ART?

即Android Runtime ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。

ART优缺点 优点:

  1. 系统性能的显著提升。
  2. 用启动更快、运行更快、体验更流畅、触感反馈更及时。
  3. 更长的电池续航能力。
  4. 支持更低的硬件。 缺点:
  5. 机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
  6. 应用的安装时间会变长。

JVM

JVM其核心目的,是为了构建一个真正跨OS平台,跨指令集的程序运行环境(VM)。DVM的目的是为了将android OS的本地资源和环境,以一种统一的界面提供给应用程序开发。严格来说,DVM不是真正的VM,它只是开发的时候提供了VM的环境,并不是在运行的时候提供真正的VM容器。这也是为什么JVM必须设计成stack-based的原因。

Dalvik和JVM有啥关系?

主要区别:

  • Dalvik是基于寄存器的,而JVM是基于栈的。
  • Dalvik运行dex文件,而JVM运行java字节码

自Android 2.2开始,Dalvik支持JIT(just-in-time,即时编译技术)。优化后的Dalvik较其他标准虚拟机存在一些不同特性: 

  1. 占用更少空间 
  2. 为简化翻译,常量池只使用32位索引  
  3. 标准Java字节码实行8位堆栈指令,Dalvik使用16位指令集直接作用于局部变量。局部变量通常来自4位的“虚拟寄存器”区。这样减少了Dalvik的指令计数,提高了翻译速度。 

当Android启动时,Dalvik VM 监视所有的程序(APK),并且创建依存关系树,为每个程序优化代码并存储在Dalvik缓存中。Dalvik第一次加载后会生成Cache文件,以提供下次快速加载,所以第一次会很慢。 Dalvik解释器采用预先算好的Goto地址,每个指令对内存的访问都在64字节边界上对齐。这样可以节省一个指令后进行查表的时间。为了强化功能, Dalvik还提供了快速翻译器(Fast Interpreter)。

一般来说,基于堆栈的机器必须使用指令才能从堆栈上的加载和操作数据,因此,相对基于寄存器的机器,它们需要更多的指令才能实现相同的性能。但是基于寄存器机器上的指令必须经过编码,因此,它们的指令往往更大。

Dalvik虚拟机既不支持Java SE 也不支持Java ME类库(如:Java类,AWT和Swing都不支持)。 相反,它使用自己建立的类库(Apache Harmony Java的一个子集)。

JVM:所有的jar程序,其运行环境完全是由JVM来提供,包括运行时,各类资源的调度,而JVM的架构,其设计为一个JVM里面可以运行多个java程序,JVM就像一个真正的“机器”,可以跑着多个程序。如果去看看一些企业级的JVM(例如tom cat,WAS),从OS的进程管理中,一般你只能看见一个JVM的进程(当然,你也可以起多个JVM,但JVM架构就是OS-JVM-APP的3层运行时模式),而看不见JVM里面运行的程序,而一个JVM里,可以跑多个java app。简单得说,JVM完全屏蔽了应用程序和OS之间的联系,而改用JVM充当了中间层,这也是一个真正跨平台运行时VM必须要做到的。只要是相同的 JDK,JVM为所有在其中运行的程序,提供了完全一致的运行环境,而不论你是什么样的底层OS和硬件条件。因此这也是我在其他一篇答案中提到,JVM的特点是取底层OS和硬件环境的交集,从而保障这种一致性。而所有应用程序和底层资源的互动,一定是依赖JVM的传递和转换来实现。JVM真正实现了一个 OS对应用程序运行时管理的所有功能。从开发环境角度和运行时角度,都是完全一致的真正VM

DVM:而DVM的特点在于使用了Zygote,Zygote有几个非常有意思的特点。

  一是Zygote采用预加载,由其首先判定安装的APK的需要以及相互依存树,以及OS及硬件环境的特点,在每次启动的时候进行预加载(现在你明白为什么 android的app在应用管理里你能轻易查到它都调用了那些重庆肛肠科关键性的本地资源的原因了吧?),这就意味着,你安装的应用越多,Zygote的加载就越慢,一般来说你的手机启动就会越慢。另外来说,在不同的硬件环境里(例如有无GPS芯片)Zygote初始化的实例是不同的。也就是说,zygote并不提供一个统一的运行环境,具有更好的弹性,这种机制意味着DVM可以取底层资源的合集来提供上层应用使用,差别只是在程序安装或者启动的过程中,DVM可以提示程序需求资源,本地环境可能未能满足而导致无法运行。DVM的Zygote并不是提供一个运行时容器,它提供的只是一个用于共享的进程,所有的应用程序运行,都是独立的,OS级别的进程,直接受到OS层面的资源控制以及调度的影响,只是他们共享Zygote说预加载的类而已。这也就是我为什么说,DVM就像是给每个应用程序在底层加了个套子,而不是提供了一个真正的运行时的VM。也就是说,DVM在开发环境中说提供的VM平台,和运行时的环境是很有可能不一致的。开发环境中提供的VM平台,是一个各种运行时可能环境的合集。

  从这点上来说,一般我们认为,JVM中的JAVA程序的崩溃,最多导致JVM的崩溃,而不会导致OS崩溃,但是apk的崩溃,可以直接导致OS崩溃,android手机会因为应用程序死机,大家应该是很常见了。但是大家一般是不会看到java程序导致死机吧?因为运行时中间隔着一个JVM。(当然,其实还是有些小门道可以用java程序让OS崩溃,因为这个,我和某些JAVA大拿打赌赢过饭局,呵呵,不过这是其他话题,不在这里展开了)

  除此之外,在JVM的机制中,不同的程序,打包以后,他们都是在运行层级真正独立的程序(指程序应用重庆妇科医院他们相互之间的关系,而不是和JVM的关系),即便他们在包里使用了同样的类,运行时都是单独加载,单独运行的(及加载多遍)。

  DVM这种预加载-共享的机制,使得不同应用之间,在运行时,是共享相同的类的,一般来说,在系统资源消耗方面,拥有更高的效率。

  最后,补充一点,byte code并不意味着就是解释执行,也能是加载编译,安装编译,预编译等等。实际上,不同的byte code的程序,不同的技术,不同的具体语言,其真正执行的情况挺复杂,难以一概而论的,好多都是混合技术的案例。

在智能手机大部分都可以让用户选择使用Dalvik还是ART模式。当然默认还是使用Dalvik模式。 用法:设置-辅助功能-开发者选项(开发人员工具)-选择运行环境(不同的手机设置的步骤可能不一样)。

问题整理

1.View 的onDraw与dispatchDraw

绘制View本身的内容,通过调用View.onDraw(canvas)函数实现 绘制自己的孩子通过dispatchDraw(canvas)实现 View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds()然后是draw(Canvas c)方法.有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过getIntrinsicWidth()和getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大小

画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(),

getIntrinsicHeight()方法,然后设为背景。

2.opengl可编程管线与固定区别

1)、固定渲染管线 ——这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和采制参数。 2)、顶点着色器——图形开发人员可以对渲染管线中的顶点运算和像素运算分别进行编程处理了,而无须象以前那样套用一些固定函数,取代设置参数来控制管线,最早出现与DX8,包括PS和VS两部分。

3.光照

4.深度测试

像素归属测试:这一步骤由OpenGL ES内部进行,不由开发人员控制;测试确定帧缓冲区的位置的像素是否归属当前OpenGL ES所有,如不属于或被另一个窗口遮挡,从而完全不显示这些像素。

裁剪测试:判断像素是否在由 glScissor 定义的剪裁矩形内,不在该剪裁区域内的像素就会被剪裁掉;

模板和深度测试:测试输入片段的模板和深度值上进行,以确定片段是否应该被拒绝;深度测试比较下一个片段与帧缓冲区中的片段的深度,从而决定哪一个像素在前面,哪一个像素被遮挡;

混合:是将片段的颜色和帧缓冲区中已有的颜色值进行混合,并将混合所得的新值写入帧缓冲;

抖动:可用于最小化因为使用有限精度在帧缓冲区中保存颜色值而产生的伪像。

Framebuffer:这是流水线的最后一个阶段,Framebuffer 中存储这可以用于渲染到屏幕或纹理中的像素值,也可以从Framebuffer 中读回像素值,但不能读取其他值(如深度值,模版值等)。

5.Intent为什么可序列化的数据

大家都知道进行Android开发的时候,无法将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递 序列化的原因基本三种情况:

  1. 永久性保存对象,保存对象的字节序列到本地文件中;
  2. 对象在网络中传递;
  3. 对象在IPC间传递。

6.SharedPreference

之前为了解决应用的内存压力,在同一个应用中使用了多进程,但在程序自测的过程中发现不同进程之间的SharedPreferences数据不能共享,但应用内很多数据都是通过SharedPreferences来保存的,如果改成其它多进程通信的方式改动比较大。通过查看源码发现,在API Level>=11即Android 3.0可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享,具体使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PreferencesUtils {
public static String PREFERENCE_NAME = "SharedPreferencesDemo";

private PreferencesUtils(){

}

public static boolean putString(Context context, String key, String value) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_MULTI_PROCESS);
SharedPreferences.Editor editor = settings.edit();
editor.putString(key, value);
return editor.commit();
}

public static String getString(Context context, String key, String defaultValue) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_MULTI_PROCESS);
return settings.getString(key, defaultValue);
}
}

本来以为通过MODE_MULTI_PROCESS属性使用SharedPreferences就可以解决不同进程之间不能共享数据的问题了,但SQA总是反馈一些随机但出现频率比较大的bug,比如在使用过程中没有清除程序数据的前提下,会出现欢迎界面和操作指引,这是通过保存在SharedPreferences的标志来判断用户是否是第一次启动程序的,分析发现保存在SharedPreferences中的数据丢失了,但代码中并没有去清除这些数据,所以推测可能是不同进程同一时间对SharedPreferences操作导致的,经验证确实如此,去掉多进程就不会再出现这个问题了。

由于进程间是不能内存共享的,每个进程操作的SharedPreferences都是一个单独的实例,上述的问题并不能通过锁来解决,这导致了多进程间通过SharedPreferences来共享数据是不安全的,这个问题只能通过多进程间其它的通信方式或者是在确保不会同时操作SharedPreferences数据的前提下使用SharedPreferences来解决。

apply和commit都是提交保存,区别在于apply是异步执行的,不需要等待。不论删除,修改,增加都必须调用apply或者commit提交保存。

7.保存sd卡怎么加锁

8.Handler中Queue的排序方式

9.IOC 控制反转

10.Android IPC

11.OKHttp与httpclient区别

1…1819
轻口味

轻口味

190 日志
27 分类
63 标签
RSS
GitHub 微博 豆瓣 知乎
友情链接
  • SRS
© 2015 - 2019 轻口味
京ICP备17018543号
本站访客数 人次 本站总访问量 次