HgWorts

Think big thoughts , but relish small pleasures.


  • 首页

  • 关于

  • 归档

如何上传自己的Android库到JCenter

发表于 2017-11-13 | 分类于 android | 阅读次数

介绍如何将自己的项目上传到JCenter。

前言

我们经常在Android的gradle文件中看到这些compile脚本,这些脚本其实就是因为之前库的开发者把对应库的jar或aar文件放到了远程服务器上,所以我们可以通过compile进行拉取。

1
2
3
4
5
compile 'com.android.support:recyclerview-v7:27.0.0'
compile 'com.squareup:otto:1.3.7'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.squareup.retrofit:retrofit:1.9.0'

AAR是什么

因为AndroidLibrary一般需要内置一些Android特定文件,比如Manifest,Resources,Assets或者JNI等超出jar文件标准的格式。

jar包是被嵌入在aar文件包中的一部分。

排布规则

compile后面的字符串排布规则是

GROUP_ID:ARTIFACT_ID:VERSION

GROUP_ID

通常用开发者的包名进行命名。

很有可能同一个上下文中有好几个library,这些可以共享同一个GROUP_ID

ARTIFACT_ID

定义library的真实名字,比如picasso

VERSION

版本号,一般是x.x.x格式

Android项目准备

以一个简单的demo为例,项目地址在

https://github.com/hgDendi/AndroidJCenterDemo

是一个HelloWorld项目,其中字符串来自于mylib module下的MyLib抽象类的静态方法。

mylib也是我们这次需要上传到JCenter的类。

project

Module划分

一般需要把需要上传的库作为一个独立的module,即1 module per 1 library。

一般分为库的模块和使用demo模块。

如果想要有一个以上的库,请划分多个module。

比如如下的项目结构。1510486146328

bintray

注册账号,登录bintray.com。

新建仓库

在个人管理界面点击“Add New Repository”

1510486324520

然后创建一个Maven仓库

1510486374030

创建成功之后点击”Create New Package”

WX20171112-193514@2x

输入对应信息,其中Website和VersionControl可以填写github地址,issue填写github的issue地址

WX20171112-193746@2x

点击CreatePackage,你在Bintray上的Maven服务器就已经创建成功了。

项目文件准备

Project#gradle

在Project的gradle文件中加入依赖

1
2
3
4
5
6
7
8
9
buildscript {
...

dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
}
}

local.properties

在local.properties中加入用户信息。

因为gitignore自动忽略local.properties,所以私人信息放在这里是比较安全的。

1
2
3
bintray.user=[...]
bintray.apikey=[...]
bintray.gpg.password=[...]

apikey可以在EditProfile里看到

APIKEY

module的build.gradle

根据上面步骤中的信息,在lib module的build.gradle文件中增加信息。

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
apply plugin: 'com.android.library'

ext {
// 刚刚创建的仓库名
bintrayRepo = 'testMaven'
// package的名字
bintrayName = 'mylib'

// owner的groupID
publishedGroupId = 'com.hgDendi.test'
libraryName = 'MyLib'
artifact = 'mylib'

libraryDescription = 'lib test demo'

siteUrl = 'https://github.com/hgDendi/AndroidJCenterDemo'
gitUrl = 'https://github.com/hgDendi/AndroidJCenterDemo.git'

libraryVersion = '0.0.1'

developerId = 'dendi'
developerName = 'Dendi Chan'
developerEmail = 'hg.dendi@gmail.com'

licenseName = 'The MIT License'
licenseUrl = 'https://rem.mit-license.org'
allLicenses = ["MIT"]
}

如上所示,则最后生成的gradle script会是

1
compile 'com.hgdendi.test:mylib:0.0.1'

增加脚本依赖

在lib module的build.gradle文件下增加一行apply from

1
2
apply from: 'https://raw.githubusercontent.com/hgDendi/AndroidJCenterDemo/master/bintray.gradle'
apply from: 'https://raw.githubusercontent.com/hgDendi/AndroidJCenterDemo/master/maveninstall.gradle'

上传代码

执行gradlew命令将lib上传

1
2
./gradlew install
./gradlew bintrayUpload

这时候登录bintray,就可以看到上传结果了。

uploadSuccess

同步到jcenter

此时代码还是只在你的maven仓库,并未同步到JCenter中。

这时候点进你的package,点击AddToJCenter就可以将代码上传到JCenter。

add2Jcenter

上传成功

同步到JCenter有时候可能需要半天到一天的时间。

同步成功后,便可以使用compile从服务器拉取lib了。

1
2
3
4
dependencies {
// implementation project(':mylib')
compile 'com.hgDendi:mylib:1.0.0'
}

RecyclerView实现二级菜单

发表于 2017-11-12 | 分类于 android | 阅读次数

ExpandableRecyclerView

demo

github:https://github.com/hgDendi/ExpandableRecyclerView

自定义支持二级菜单的RecyclerViewAdapter。

将展开闭合操作封装在了BaseExpandableRecyclerViewAdapter中,使整个使用方式充满弹性。

下方有具体使用方法,一般需要override 6个方法:

  • getGroupCount
  • getGroupItem
  • onCreateGroupViewHolder
  • onCreateChildViewHolder
  • onBindGroupViewHolder
  • onBindChildViewHolder

因为onCreateViewHolder和onBindViewHolder本来即是RecyclerViewAdapter需要强制Override的方法,这里按父子关系拆分成了两个方法。而getGroupCount和getGroupItem在大概率情况下都是基于List的简单一行代码即可实现,故而使用起来十分简便。

Gradle

1
2
3
dependencies{
compile 'com.hgDendi:expandable-recyclerview-adapter:0.0.2'
}

优点

  1. 使用便捷、简洁明了
  2. 最大程度保留RecyclerView的原生机制,滑动到具体条目才进行渲染,不会滑到另一个Group渲染另一个Group下所有子Item
  3. itemView的部分刷新,可以自定义展开、闭合时的刷新机制,避免GroupItem在展开闭合时刷新整个GroupItem(比如只是简单的箭头指向改变)
  4. 采用泛型,用户自定义传入参数,扩展性更高

使用方法

定义父子数据结构

其中GroupBean需要继承自BaseGroupBean并override三个方法。

  • getChildCount
    • 获取子节点个数
  • isExpandable
    • 是否为可展开的节点
    • 默认实现可以是判断子节点是否为0,但是也可以做其他处理
  • getChildAt
    • 根据index获取对应的子节点数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SampleGroupBean implements BaseExpandableRecyclerViewAdapter.BaseGroupBean<SampleChildBean> {
@Override
public int getChildCount() {
return mList.size();
}

// whether this group is expandable
@Override
public boolean isExpandable() {
return getChildCount() > 0;
}

@Override
public SampleChildBean getChildAt(int index) {
return mList.size() <= index ? null : mList.get(index);
}
}

public class SampleChildBean {
}

定义对应的ViewHolder

其中Group对应的ViewHolder要继承BaseGroupViewHolder并改写onExpandStatusChanged.

该方法是实现item局部刷新的方法,在展开、闭合时会回调,比如对于大多数情况,开关闭合状态只需要修改左边箭头指向,就无需刷新itemView的其他部分。

实现原理是使用RecyclerView的payload机制实现局部监听刷新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static class GroupVH extends BaseExpandableRecyclerViewAdapter.BaseGroupViewHolder {
GroupVH(View itemView) {
super(itemView);
}

// this method is used for partial update.Which means when expand status changed,only a part of this view need to invalidate
@Override
protected void onExpandStatusChanged(RecyclerView.Adapter relatedAdapter, boolean isExpanding) {
// 1.只更新左侧展开、闭合箭头
foldIv.setImageResource(isExpanding ? R.drawable.ic_arrow_expanding : R.drawable.ic_arrow_folding);

// 2.默认刷新整个Item
relatedAdapter.notifyItemChanged(getAdapterPosition());
}
}

static class ChildVH extends RecyclerView.ViewHolder {
ChildVH(View itemView) {
super(itemView);
}
}

使用自定义Adapter继承基类

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
// !!注意这里继承时候使用的泛型,分别为上面提到的Bean和ViewHolder
public class SampleAdapter extends BaseExpandableRecyclerViewAdapter
<SampleGroupBean, SampleChildBean, SampleAdapter.GroupVH, SampleAdapter.ChildVH>

@Override
public int getGroupCount() {
// 父节点个数
}

@Override
public GroupBean getGroupItem(int groupIndex) {
// 获取父节点
}

@Override
public GroupVH onCreateGroupViewHolder(ViewGroup parent, int groupViewType) {
}

@Override
public ChildVH onCreateChildViewHolder(ViewGroup parent, int childViewType) {
}

@Override
public void onBindGroupViewHolder(GroupVH holder, SampleGroupBean sampleGroupBean, boolean isExpand) {
}

@Override
public void onBindChildViewHolder(ChildVH holder, SampleGroupBean sampleGroupBean, SampleChildBean sampleChildBean) {
}
}

其他用法

增加父子的种类

通过改写getChildType和getGroupType方法进行控制type,该type会在onCreateGroupViewHolder和onCreateChildViewHolder时回传。

1
2
3
4
5
6
7
8
9
10
11
protected int getGroupType(GroupBean groupBean) {
return 0;
}

abstract public GroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int groupViewType);

protected int getChildType(GroupBean groupBean, ChildBean childBean) {
return 0;
}

abstract public ChildViewHolder onCreateChildViewHolder(ViewGroup parent, int childViewType);

增加列表为空时候的EmptyView

1
2
3
4
5
6
7
8
9
10
11
adapter.setEmptyViewProducer(new ViewProducer() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty, parent, false);
return new DefaultEmptyViewHolder(emptyView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder) {
}
});

增加HeaderView

1
2
3
4
5
6
7
8
9
10
11
adapter.setEmptyViewProducer(new ViewProducer() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
return new DefaultEmptyViewHolder(emptyView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder) {
}
},false);

监听事件

可以通过setListener的方式设置监听事件。

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
public interface ExpandableRecyclerViewOnClickListener<GroupBean extends BaseGroupBean, ChildBean> {

/**
* 长按时的操作
*
* @param groupItem
* @return
*/
boolean onGroupLongClicked(GroupBean groupItem);

/**
* 在可展开group被点击的时候触发的回调,返回布尔值表示是否拦截该操作
*
* @param groupItem
* @param isExpand
* @return true 使点击无效。false 正常执行展开、闭合操作。
*/
boolean onInterceptGroupExpandEvent(GroupBean groupItem, boolean isExpand);

/**
* 点击GroupView时的操作(该Group的isExpandable返回false才会触发这个回调)
*
* @param groupItem
*/
void onGroupClicked(GroupBean groupItem);

/**
* 点击子View时的操作
*
* @param groupItem
* @param childItem
*/
void onChildClicked(GroupBean groupItem, ChildBean childItem);
}

实现原理

父子结构划分

  1. 通过getItemType判断所处类型,在基类中就先划分为四种类型的View。
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
private static final int TYPE_EMPTY = ViewProducer.VIEW_TYPE_EMPTY;
private static final int TYPE_HEADER = ViewProducer.VIEW_TYPE_HEADER;
private static final int TYPE_GROUP = ViewProducer.VIEW_TYPE_EMPTY >> 2;
private static final int TYPE_CHILD = ViewProducer.VIEW_TYPE_EMPTY >> 3;
private static final int TYPE_MASK = TYPE_GROUP | TYPE_CHILD | TYPE_EMPTY | TYPE_HEADER;

// 通过getItemView判断类型,这里默认是使用上面定义的MASK,可以重载使Group和Child中再划分子类,但是不允许和TYPE_MASK冲突,否则会报Exception
@Override
public final int getItemViewType(int position) {
if (mIsEmpty) {
return position == 0 && mShowHeaderViewWhenEmpty ? TYPE_HEADER : TYPE_EMPTY;
}
if (position == 0 && mHeaderViewProducer != null) {
return TYPE_HEADER;
}
int[] coord = translateToDoubleIndex(position);
GroupBean groupBean = getGroupItem(coord[0]);
if (coord[1] < 0) {
int groupType = getGroupType(groupBean);
if ((groupType & TYPE_MASK) == 0) {
return groupType | TYPE_GROUP;
} else {
throw new IllegalStateException(
String.format(Locale.getDefault(), "GroupType [%d] conflits with MASK [%d]", groupType, TYPE_MASK));
}
} else {
int childType = getChildType(groupBean, groupBean.getChildAt(coord[1]));
if ((childType & TYPE_MASK) == 0) {
return childType | TYPE_CHILD;
} else {
throw new IllegalStateException(
String.format(Locale.getDefault(), "ChildType [%d] conflits with MASK [%d]", childType, TYPE_MASK));
}
}
}
  1. 在onCreateViewHolder和onBindViewHolder中进行类型判断,调用不同的方法,这些方法在子类中进行重载。注意这里将这三个方法都置为final,防止子类重载,子类只能重载对应不同type的具体方法。
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
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_EMPTY:
return mEmptyViewProducer.onCreateViewHolder(parent);
case TYPE_HEADER:
return mHeaderViewProducer.onCreateViewHolder(parent);
case TYPE_CHILD:
return onCreateChildViewHolder(parent, viewType ^ TYPE_CHILD);
case TYPE_GROUP:
return onCreateGroupViewHolder(parent, viewType ^ TYPE_GROUP);
default:
throw new IllegalStateException(
String.format(Locale.getDefault(), "Illegal view type : viewType[%d]", viewType));
}
}

@Override
public final void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
onBindViewHolder(holder, position, null);
}

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
switch (holder.getItemViewType() & TYPE_MASK) {
case TYPE_EMPTY:
mEmptyViewProducer.onBindViewHolder(holder);
break;
case TYPE_HEADER:
mHeaderViewProducer.onBindViewHolder(holder);
break;
case TYPE_CHILD:
final int[] childCoord = translateToDoubleIndex(position);
GroupBean groupBean = getGroupItem(childCoord[0]);
bindChildViewHolder((ChildViewHolder) holder, groupBean, groupBean.getChildAt(childCoord[1]), payloads);
break;
case TYPE_GROUP:
bindGroupViewHolder((GroupViewHolder) holder,
getGroupItem(translateToDoubleIndex(position)[0]), payloads);
break;
default:
throw new IllegalStateException(
String.format(Locale.getDefault(), "Illegal view type : position [%d] ,itemViewType[%d]", position, holder.getItemViewType()));
}
}

展开闭合操作

操作

当groupBean的isExpandable返回true的时候,为itemView设置点击事件,进行展开闭合。

展开闭合的具体原理是在Set中记录展开闭合情况,当发生展开、闭合操作的时候进行更新,并使用notifyItemChange接口进行列表的局部刷新。

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
private Set<GroupBean> mExpandGroupSet;

protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean groupBean, List<Object> payload) {
// ...
if (!groupBean.isExpandable()) {
// ...
} else {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final boolean isExpand = mExpandGroupSet.contains(groupBean);
if (mListener == null || !mListener.onInterceptGroupExpandEvent(groupBean, isExpand)) {
final int adapterPosition = holder.getAdapterPosition();
holder.onExpandStatusChanged(BaseExpandableRecyclerViewAdapter.this, !isExpand);
if (isExpand) {
mExpandGroupSet.remove(groupBean);
notifyItemRangeRemoved(adapterPosition + 1, groupBean.getChildCount());
} else {
mExpandGroupSet.add(groupBean);
notifyItemRangeInserted(adapterPosition + 1, groupBean.getChildCount());
}
}
}
});
}
// 子类实现
onBindGroupViewHolder(holder, groupBean, isGroupExpand(groupBean));
}

局部刷新原理

定义了Payload,采用Payload机制进行局部刷新。

在notifyItemChange时传入payload,在onBindViewHolder操作中判断是否带有payload,若带有payload,则执行部分刷新的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static final Object EXPAND_PAYLOAD = new Object();

// 局部刷新时调用的接口(已封装好,用户无需调用)
notifyItemChanged(position, EXPAND_PAYLOAD);

// 处理payload
protected void bindGroupViewHolder(final GroupViewHolder holder, final GroupBean groupBean, List<Object> payload) {
if (payload != null && payload.size() != 0) {
if (payload.contains(EXPAND_PAYLOAD)) {
// holder方法有抽象方法,在此方法中实现具体的展开、闭合逻辑
holder.onExpandStatusChanged(BaseExpandableRecyclerViewAdapter.this, isGroupExpand(groupBean));
if (payload.size() == 1) {
return;
}
}
onBindGroupViewHolder(holder, groupBean, isGroupExpand(groupBean), payload);
return;
}
}

网络相关

发表于 2017-11-11 | 分类于 network | 阅读次数

网络

URI

Uniform Resource identifier

统一资源标识符,用来唯一的标志一个资源

File://a:1234/b/c/d.txt

  • 访问资源的命名机制
  • 主机名
  • 资源自身的名称,由路径展示

URL

Universal Resource Locator

统一资源定位器,是一种具体的URI

可以用来标志一个资源,还指明了如何locate这个资源

  • 协议
  • 存有该资源的主机IP地址
  • 主机资源具体地址

分层

  • 应用层
    • 收到传输层数据后按格式进行解读
    • HTTP、FTP、Telnet、SMTP、POP3
  • 传输层
    • 建立主机到主机的通信,为两台主机上的应用提供端到端的通信
    • TCP UDP
  • 网络层
    • 决定如何将数据从发送方路由到接收方
    • 综合考虑发送优先权、网络拥塞程度、服务质量、可选路由的花费
    • ip协议
  • 数据链路层
    • 从网络层接收到的数据被分割成特定的可被物理层传输的帧
    • 原始数据、发送方和接收方的物理地址、纠错和控制信息
  • 物理层

HTTP

是面向对象协议,使用于分布式超媒体信息系统。

  • 支持CS模式
  • 简单快速
  • 灵活,允许传输任意类型的数据
  • 无连接,限制每次连接只处理一个请求
  • 无状态,对于事务处理没有记忆能力

URL格式

1
http://host[":"port][abs_path]

Cookie和Session

Cookie

是客户端的解决方案(HTTP是无状态的),是服务器发给客户端的特殊信息,这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。

1510379800835

Session

保存在服务器上。客户端浏览器访问Server的时候,Server把客户端信息以某种形式记录在服务器上。

请求报文

requestHttp

  • 请求行
  • 请求报头
  • 请求数据

请求行

Method Request-URI HTTP-VERSION CRLF

GET http://blog.csdn.net/ HTTP/1.1

八种请求方法:

  • GET
    • 请求获取Request-URI所标识的资源
    • 大小有限制
  • POST
    • 在Request-URI所标识的资源后附加新的数据
    • 数据放在body中,无大小限制
  • HEAD
    • 请求获取由URI所标志的资源的响应消息报头
  • PUT
    • 请求服务器存储一个资源,并用URI作为其标识
  • DELETE
    • 请求服务器删除URI所标识的资源
  • TRACE
    • 请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT
    • 预留给能够将连接改为管道方式的代理服务器
  • OPTIONS
    • 能够查询服务器的性能

响应报文

responseHTTP

  • 状态行
  • 响应报头
  • 空行

状态行

HTTP-Version Status-Code Reason-phrase CRLF

HTTP/1.1 200 OK

  • 100~199
    • 收到请求,需要请求者继续执行操作
  • 200~299
    • 请求成功,请求已被成功接收并处理
    • 200 OK
  • 300~399
    • 重定向,要完成请求必须进行更进一步的操作
  • 400~499
    • 客户端错误,请求有语法错误或请求无法实现
    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
  • 500~599
    • 服务器错误,服务器不能实现合法的请求
    • 500 Internal Server Error
    • 503 Server Unavailable

消息报头

通用部分

  • Date
  • Connection
  • Cache-Control

请求报头

  • Host
    • 请求主机名
  • User-Agent
    • 发送请求的浏览器类型
  • Accept
    • 指定客户端接收哪些类型的信息
    • 比如/
  • Refer
    • 从哪个链接链接过来的
  • Accept-Encoding
    • 客户端可识别的数据编码
  • Accept-Language
    • 浏览器支持的语言类型
  • Connection
    • 允许客户端和服务器指定与请求、响应连接有关的选项
    • 如Keep-Alive
  • if-none-match
    • 缓存
    • 返回304,直接使用本地缓存
  • If-modified-since
    • 返回304,直接使用本地缓存
  • Transfer-Encoding
    • 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式

响应报头

  • Location
    • 用于重定向接收者到一个新的位置
  • Server
    • 包含服务器用来处理请求的系统信息
  • ETag
    • 标志服务端信息的标志位
    • 和if-none-match配对
  • Expires
    • 缓存时间控制
  • Cache-Control
  • Proxy-Connection
    • keep-alive

实体报头

  • Content-Type
    • 发送给接收者的实体正文的媒体类型
  • Content-Length
    • 实体正文的长度
  • Content-Language
  • Content-Encoding
  • Last-Modified
    • 指示资源的最后修改时间和日期
  • Expires
    • 响应过期的日期和时间

HTTP 1.X 协议存在的问题

  • 传输数据时,每次都要重新建立链接
  • 所有传输内容都是明文,客户端和服务器无法验证对方身份
  • header里携带的内容过大,在一定程度上增加了传输成本
  • 虽然支持keep-alive,来弥补多次创建连接产生的延迟,但是使用多了同样会给服务器带来大量的性能压力

HTTPS

HTTPS

不是一个单独的协议,是对工作在加密连接(SSL/TLS)上的常规HTTP协议。

通过在TCP和HTTP之间加入TLS来加密。

SSL/TLS

SSL协议是一种安全传输协议。

1510380517163

TCP和UDP

TCP UDP
基于连接,全双工的可靠信道 无连接,不可靠信道
要求系统资源较多 较少
流模式 数据报模式
保证数据正确性 可能丢包
保证数据顺序 不保证
文件传输、重要状态的更新 视频传输、实时通信

Android中的网络通信框架

HttpURLConnection

API简单,体积较小,适应于Android项目,而且其压缩和缓存机制可以有效的减少网络访问的流量。

GET

1
2
3
4
5
6
7
8
9
10
11
12
13
URL mURL = new URL(url);
httpURLConnection = (HttpURLConnection) mURL.openConnection();
httpURLConnection.setConnectTimeout(15000);
httpURLConnection.setReadTimeout(15000);
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
httpURLConnection.setDoInput(true);
httpURLConnection.connect();

inputStream = httpURLConnection.getInputStream();
int code = httpURLConnection.getResponseCode();
String response = UrlConnManager.convertStreamToString(inputStream);
inputStream.close();

POST

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
URL mURL = new URL(url);
httpURLConnection = (HttpURLConnection) mURL.openConnection();

httpURLConnection.setConnectTimeout(15000);
httpURLConnection.setReadTimeout(15000);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
httpURLConnection.setDoInput(true);
//post传递参数时需要开启
httpURLConnection.setDoOutput(true);

//post body
OutputStream outputStream = httpURLConnection.getOutputStream();
StringBuilder sb = new StringBuilder();
for (NameValuePair nameValuePair : paramsList) {
if (!TextUtils.isEmpty(sb)) {
sb.append("&");
}
sb.append(URLEncoder.encode(nameValuePair.getName(), "UTF-8"));
sb.append("=");
sb.append(URLEncoder.encode(nameValuePair.getValue(), "UTF-8"));
}
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
writer.write(sb.toString());
writer.flush();
writer.close();

httpURLConnection.connect();
inputStream = httpURLConnection.getInputStream();
int code = httpURLConnection.getResponseCode();
String response = UrlConnManager.convertStreamToString(inputStream);
inputStream.close();

Volley

适合进行数据量不大但通信频繁的网络操作

对于大数据量的网络操作,比如下载文件等,表现就比较糟糕。

基于HttpURLConnection。

基于队列,有四个NetworkDispatcher,一个CacheDispatcher。

  1. 创建RequestQueue
  1. 创建XXRequest
  2. RequestQueue#add
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
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
StringRequest stringRequest = new StringRequest(
Request.Method.GET,
"https://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d(TAG, "====" + response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
"http://ip.taobao.com/service/getIpInfo.php?ip=59.108.54.37",
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
IpModel ipModel = new Gson().fromJson(response.toString(), IpModel.class);
Log.d(TAG, "====ip" + ipModel);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {

}
}
);
queue.add(stringRequest);
queue.add(jsonObjectRequest);

OKHttp

会从很多常用的连接问题中自动恢复,比如服务器配置了多个IP,当第一个IP连接失败时,会自动尝试下一个IP。(原理是通过拦截器进行实现)

Fresco、Glide、Picasso、Retrofit都使用了OkHttp

使用方法:

  1. 通过RequestBuilder创建Request
  2. 创建OkHttpClient
  3. 通过OkHttpClient#newCall(Request)创建Call
  4. 调用Call#enqueue或execute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder().url("http://blog.csdn.net/itachi85");
//POST请求需要用FormBody封装请求的参数
requestBuilder.method("GET", null);
okhttp3.Request request = requestBuilder.build();

OkHttpClient okHttpClient = new OkHttpClient();
Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {

}

@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
Log.d(TAG, "====" + response.body().toString());
}
});

Retrofit

底层是基于OkHttp实现的。

与其他框架不同的是,更多使用运行时注解的方式提供功能。

使用方法:

  1. 创建Retrofit
  2. 使用Retrofit创建对应的接口,使用接口返回Call
  3. Call#enqueue或者execute
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
//POST
public interface IpServiceForPost {
@FormUrlEncoded
@POST("{path}/getIpInfo.php")
Call<IpModel> getIpMsg(@Path("path") String path, @Field("ip") String first);
}

//GET
public interface IpService {
@GET("{path}/getIpInfo.php")
Call<IpModel> getIpMsg(@Path("path") String path, @Query("ip") String ip);
}

//其他注解
//Path:地址
//Query(Map)
//Field
//Body 数据传输类型JSON字符串
//Part 单个文件上传
//Header 消息报头


String url = "http://ip.taobao.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpService ipService = retrofit.create(IpService.class);
retrofit2.Call<IpModel> call = ipService.getIpMsg("service","59.108.54.37");

// 同步方法使用execute
call.enqueue(new retrofit2.Callback<IpModel>() {
@Override
public void onResponse(retrofit2.Call<IpModel> call, retrofit2.Response<IpModel> response) {
String country = response.body().toString();
Log.d(TAG, "==== coutry is " + country);
}

@Override
public void onFailure(retrofit2.Call<IpModel> call, Throwable t) {

}
});

Gradle初探

发表于 2017-05-11 | 分类于 groovy | 阅读次数

[TOC]

Gradle

基于Groovy脚本语言进行构建的。Google选用它作为默认的Android编译工具。

Gradle的官方文档

分类

全局build.gradle

可以为项目整体配置一些属性。

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
buildscript {
repositories {
jcenter() //指定使用jcenter代码仓库
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
}
// 好处是这样写之后,可以将配置进行统一管理。坏处是更新通知检查机制就无效了。
ext.global_compileSdkVersion = 25
ext.global_buildToolsVersion = "25.0.0"
ext.global_minSdkVersion = 16
ext.global_targetSdkVersion = 25
ext.global_ndkCommand = getNdkCommand()
}

//windows下使用.cmd,Linux和MacOS使用无后缀
def static getNdkCommand() {
if (System.properties['os.name'].toLowerCase().contains('windows')) {
return "ndk-build.cmd"
}
return "ndk-build"
}

task clean(type: Delete) {
delete rootProject.buildDir
}

Module build.gradle

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
//描述Gradle所引入的插件,如果该模块是Library,则将application改为library
apply plugin: 'com.android.application'

//该Module构建过程中所用到的所有参数
android {
compileSdkVersion global_compileSdkVersion
buildToolsVersion global_buildToolsVersion
sourceSets.main {
jni.srcDirs = [] // disable auto ndk build
jniLibs.srcDir 'src/main/libs'
res.srcDir = ['src/main/res/',
'src/main/res/layout/activity']
}
defaultConfig {
applicationId "com.xxx.xxx"
minSdkVersion global_minSdkVersion
// 此参数表示能适应该版本的新特性,如设置为23,就需要支持动态权限
targetSdkVersion global_targetSdkVersion
versionCode 1
versionName "1.0"
}

// NDK build task,global_ndkCommand是因为各平台ndk-build命令不同做的区分处理
task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
def ndkDir = android.ndkDirectory
commandLine "$ndkDir/$global_ndkCommand",,
'-C', file('src/main/jni').absolutePath,
'-j', Runtime.runtime.availableProcessors(),
'all',
'NDK_DEBUG=1'
}

task cleanNative(type: Exec, description: 'Clean JNI object files') {
def ndkDir = android.ndkDirectory
commandLine "$ndkDir/ndk-build.cmd",
'-C', file('src/main/jni').absolutePath,
'clean'
}

clean.dependsOn 'cleanNative'

tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}

//签名,使用签名需要到buildTypes里面进行signingConfig设置
signingConfigs{
ch{
storeFile file("aaa_key.jks")
storePassword "1234567"
keyAlias "aaa"
keyPassword "1234567"
}
}

buildTypes {
debug {
debuggable true
//是否开启混淆,另外还有功能是将非使用到的东西不进行打包进apk
minifyEnabled false
//混淆规则
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
shrinkResources true//此配置只有在minifyEnabled成功情况下才能开启,资源精简
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
ch.initWith(buildTypes.release)
ch{
//applicationID增加后缀,Android系统以此为主键,故而可以通过增加后缀的方式同时安装app
applicationIdSuffix ".ch"
signingConfig signingConfigs.ch
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:design:25.0.0'
compile project(':pulltorefresh')
}

local.properties

1
2
ndk.dir=G\:\\android-ndk-r10
sdk.dir=E\:\\sdk

可选配置

在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
//java编译版本
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

//控制lint代码检查,Lint的编译检查是Gradle编译的耗时大户
lintOptions{
abortOnError false
}

//Proguard
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'

//定义Task,执行Task方式是gradle taskName
task testTask << {
println project
println project.name
println project.buildDir
println project.buildFile
println project.version
println name
}

gradle.properties

进行一些动态配置,将一些键、值写在SystemProp中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 配置到SystemProp中
systemProp.keyAlias = ch
//在signingConfig中引用
keyAlias System.properties['keyAlias']

//2. 使用KEY/VALUE进行配置
ch.keyAlias = ch
//在signingConfig中引用
keyAlias project.property['ch.keyAlias']

//3. 属性方式进行配置,缺点是不可以在命令行中设置参数
pKeyAlias = ch
//在signingConfig中引用
keyAlias pKeyAlias

flavor

测试代码和实际代码可以使用flavor进行配置。

然后从AS的build variants可以选择自己需要运行的配置参数即可运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
productFlavors{
mock{}

prod{}
}

//release版本不需要mock这个flavor
android.variantFilter{ variant ->
if(variant.buildType.name.equals('release')){
variant.getFlavors.name.equals('release'){
variant.getFlavors().each() {flavor ->
if(flavor.name.equals('mock')){
variant.setIgnore(true);
}
}
}
}

}

多渠道打包

实际上就是在代码层面上标记不同的渠道名

  1. 创建渠道占位符(比如${CHANNEL_VALUE}就是渠道占位符)

    1
    2
    3
    <meta-data
    android:name="PRODUCT"
    android:value="${CHANNEL_VALUE}"/>
  2. 配置Gradle脚本,一般在android领域中添加productFlavors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    productFlavors{
    product1{
    manifestPlaceholders = [CHANNEL_VALUE:"PRODUCT 1"]
    }

    product2{
    manifestPlaceholders = [CHANNEL_VALUE:"PRODUCT 2"]
    }
    }

    // 可以这样优化,即不在productX中定义,直接统一定义在外部
    productFlavors.all{
    flavor-> flavor.manifestPlaceholders = [CHANNEL_VALUE:name]
    }

重命名包

包的默认命名是 app-渠道名-buildType.apk

1
2
3
4
5
6
7
8
9
10
11
12
13
applicationVariants.all{
variant ->
variant.output.each{
output ->
if(output.outputFile !=null
&& output.outputFile.name.endsWith('.apk')
&& 'release'.equals(variant.buildType.name)){
def apkFile = new File(output.outputFile.getParent(),
"CHApp_${variant.flavorName}_ver${variant.versionName}.apk")
output.outputFile = apkFile
}
}
}

为不同版本添加不同代码

buildTypes下的具体领域中,通过buildConfigField的三个参数,类型、名称、值,可以将一个变量设置到不同的buildType中去。

这些变量会生成在系统的BuildConfig中

1
2
3
4
buildConfigField "boolean" "apkFlag" "true"

// 更改app的名称,可以放在buildTypes中,但是注意需要删除string.xml中的app_name字段,否则会冲突
resValue("string","app_name","hello_world")

上传aar到Maven库

普通libriay的aar文件会存放在build/outputs/aar中

如果要上传aar到Maven库的话,是通过gradle脚本进行提交

具体脚本可以上网查询。

常用命令

  • check
  • assemble
    • 组合项目的所有输出,包含assembleDebug和assembleRelease两个Task
  • build
    • 相当于check和assemble
  • clean
    • 清理所有的中间编译结果

Gradle依赖

1
2
3
4
5
6
7
8
9
10
// 检查依赖关系
gradle androidDependencies

// 关闭项目的依赖传递
compile 'com.xxx.xxxxx:xxxxx:1.0.0-SNAPSHOT@aar'

// 可以使用exclude module排除一个库中引用的其他库
compule('xxxx'){
exclude module:'com.xxx.asdfa'
}

Gradle编译加速

Gradle本身已经内置了性能分析工具profile

build的时候只需要增加profile参数即可执行以下脚本

执行后在根目录的build/reports目录下会生成profile文件,里面有各项耗时

1
gradle build --profile

关闭Lint

1
2
3
4
5
// 命令行
gradle build -x lint

// 脚本中动态增加编译参数
project.gradle.startParameter.excludedTaskNames.add('lint')

Gradle加速

1
2
3
4
5
6
7
8
9
10
//gradle.properties,开启多线程和多核心支持
org.gradle.daemon = true
org.gradle.parallel = true
org.gradle.configureondemand = true

//build.gradle,开启增量编译,内存资源增加
dexOptions{
incremental
javaMaxHeapSize "4g"
}

自定义插件

创建自定义插件的三种方式:

  • 在build.gradle脚本中直接使用
  • buildSrc中使用
  • 独立Module中使用

Android的IPC机制

发表于 2017-05-07 | 分类于 android | 阅读次数

[TOC]

IPC机制

进程间通信

IPC InterProcess Communication

RPC Remote Procedure Call

Android默认每个app是一个进程,但也可以通过android:process属性使每个app有多个进程,或者多个app共享某个进程。

有时候通过多进程的方式获取多份内存空间。一般是指定android:process属性,还有一种非常规的方法,通过JNI在native层去fork一个新的进程。

  • 进程名以”:“开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在一个进程中
  • 进程名不以 “:” 开头的程序属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
    • 如果两个应用需要通过ShareUID跑在同一个进程,需要这两个应用有相同的ShareUID并且签名相同才可以
    • 在这种情况下,可以互相访问对方的私有数据,比如data目录、组件信息等,当然还可以共享内存数据
1
2
android:process = ":remote"
android:process = "com.dendi.helloworld.remote"

多进程的特性

Android为每个进程都分配一个独立的虚拟机,在内存分配上有不同的地址空间,这就导致了在不同的虚拟机中访问一个类的对象会产生多份副本

故而多进程的APP会拥有独立的虚拟机、Application以及内存空间。

相当于两个不同的应用采用了SharedUID的模式(Android系统为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据)

  • 不同的内存空间,数据无法共享
  • 需要谨慎处理代码中的线程同步
  • 需要提防多进程并发导致的文件锁和数据库锁时效的问题

具体问题:

1
2
3
4
1. 静态成员和单例模式完全失效
2. 线程同步机制失效
3. SharedPreferences可靠性下降(底层通过操作XML文件实现,并发写显然容易出问题)
4. Application会多次重建

三个基础知识

Serializable

Serializable使用简单但是开销很大,序列化和反序列化过程需要大量的IO操作,一般用于将对象序列化到存储设备中或者将对象序列化后通过网络传输。

可以通过ObjectOutputStream和ObjectInputStream轻松实现。

其中serialVersionUID的作用的辅助序列化和反序列化,原则上序列化后的数据中的serialVersionUID只有和当前类的相同才能够被正常的反序列化。

静态变量和用transient关键字标记的成员变量不参与序列化的过程。

Parcelable

Parcelable使用麻烦,但效率很高。

  • 序列化 writeToParcel
  • 反序列化 CREATOR
  • 内容描述符 describeContents
    • 都应该返回0
    • 对象中存在文件描述符时,返回1

Binder

binder是Android中的一种跨进程通信方式.

可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder.

从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。

从Android应用层来说,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Binder更安全,比如socket的ip地址可以进行伪造,而Binder机制从协议本身就支持对通信双方做身份校验,因而大大提升了安全性,这个也是Android权限模型的基础。

Binder通信模型

如下图,伪装。即代理模式。对代理对象的操作会通过驱动最终转发到Binder本地对象上去完成,当然使用者无需关心这些细节。

Binder伪装

Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,它的引用却遍布于系统的各个进程中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给另一个进程,让大家都能访问同一Server,就像一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。

AILD的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。

Service中的Binder

Android中Binder主要用在Service中,包括AIDL和messager(Messager底层实际就是AIDL)

为什么是Binder

  • 可靠性
    • 若在底层架设一套协议来实现Clinet-Service通信,增加了系统的复杂性
    • 在资源有限的手机上实现复杂环境,可靠性难以保证
  • 传输性能
    • Socket主要用于跨网络的进程间通信和本级上的进程间的通信,传输效率低,开销大
    • 消息队列和管道采用存储-转发格式,数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。
    • 共享内存无需拷贝但是控制复杂。
    • IPC方式的数据拷贝次数,共享内存0次,Binder1次,Socket/管道/消息队列 2次。
  • 安全性

IPC方式

摘自任玉刚的《把玩Android多进程》ppt

Screen Shot 2017-01-07 at 16.37.37

Bundle

可以在一个进程中启动另一个进程的Activity、Service、Receiver

文件共享

适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理好并发读、写的问题。

SharedPreferences因为系统对它的读写有有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读、写就变得不可靠。

Messenger

可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据。

是一种轻量级的IPC方案,底层实现是AIDL。

一次只处理一个请求,因此在服务端我们不用考虑线程同步的问题。

通过Messgenger来传递Message,能使用的载体只有what、arg1、arg2、Bundle和replayTo。

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
//Messenger的构造方法

// 初始化
Messenger(Handler);

// 从服务端获得IBinder后初始化
Messenger(IBinder);

//服务端
public class MessengerService extends Service{
private Messenger mMessenger = new Messenger(new Handler());

@Override
public IBinder onBind(Intent intent){
return mMessenger.getBinder();
}
}

//客户端
private mMessenger;

public ServiceConnection mConnection = new ServiceConnection(){
public void onServiceConnected(ComponentName className,IBinder service){
mMessenger = new Messenger(service);
}
}

onCreate(){
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}

AIDL

使用步骤:

  • 服务端创建Service,实现AIDL接口,并在onBind中返回它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Server实现AIDL接口
    private Binder mBinder = new IBookManager.Stub(){

    }

    @Override
    public IBinder onBind(Intent intent){
    return mBinder;
    }
  • 客户端绑定Service,将服务端返回的Binder对象转换成AIDL接口所属的类型

    1
    2
    3
    4
    5
    public ServiceConnection mConnection = new ServiceConnection(){
    public void onServiceConnected(ComponentName className,IBinder service){
    IBookManager bookManager = IBookManager.Stub.asInterface(service);
    }
    }

需要注意:

  1. 对象的跨进程传输本质上是反序列化的过程,通过Binder传递后的对象会是全新的对象,但是这些对象会有一个共同点,他们底层的Binder是同一个。

    所以若要跨进程使用观察者模式,需要使用RemoteCallbackList。

    这个类还有一个很有用的功能,就是当客户端进程终止后,能够自动移除客户端所注册的listener。

  2. Binder是会意外死亡的

    • 给Binder设置DeathRecipient监听
    • 在onServiceDisconnected中重连远程服务
  3. 远程方法调用发生在binder线程池中,需要小心因为耗时操作导致本端阻塞

  4. 服务端验证客户端权限

    • onBind中通过checkCallingOrsELFpermission(“permission”)检查客户端是否声明该权限
    • 验证包名,getPackageManager().getPackagesForUid(getCallingUid())

ContentProvider

底层实现也是Binder,用于不同应用间进行数据共享的方式。

创建一个ContentProvider

创建一个自定义的ContentProvider需要继承ContentProvider并实现六个抽象方法:

另外,是存在多进程并发访问的,故而四大方法需要做好线程同步准备。

  • onCreate
    • 代表ContentProvider的创建,做一些初始化的操作
  • query
  • update
  • insert
  • delete
  • getType
    • 返回一个Uri请求所对应的MIME类型,比如图片或者视频等
    • 不关注这个选项可以返回null或者“/”

需要注册:

1
2
3
4
5
6
<provider
android:name = ".provider.BookProvider"
//唯一标识
android:authorities="com.dendi.chapter.book.provider"
//权限声明
android:permission="com.dendi.Provider"/>

查询时

1
2
3
4
5
6
Uri userUri = Uri.parse("content://com.dendi.chapter.book.provider/user");
Cursor userCursor = getContentResolver().query(
userUri,new String[]{"_id","name","sex"},null,null,null);
while(userCursor.moveToNext()){
//print
}

特性

以表格的形式来组织数据

Socket

  • 流式套接字
    • TCP
    • 面向连接,提供稳定的双向通信功能(本身提供了超时重传),有很高的稳定性
  • 用户数据报套接字
    • UDP
    • 无连接,提供不稳定的单向通信功能
    • 效率更高,但是不能保证数据一定能够正确传输,尤其在网络拥塞的情况下

注意不要在UI线程中访问网络,会抛出异常。

另外Socket的ip地址是可以伪造的,故而不一定具有安全性。

APP启动优化

发表于 2017-03-14 | 分类于 android | 阅读次数

[TOC]

APP启动优化

启动方式

冷启动

后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。

application以及activity初始化是一个比较复杂的过程,而白屏或黑屏就是这个过程的产物。

在应用程序刚启动到应用程序初始化完全之间有一段时间空隙,这时候系统创建了一个空白窗口,叫StartingWindow。这个窗口是一个临时窗口,对应的WindowType是TYPE_APPLICATION_STARTING。应用程序加载完第一帧后,这个窗口就会被移除。

Window的顶层是DecorView,所以StartingWindow显示一个空DecorView,但是会给这个DecorView应用当前Activity指定的Theme,如果这个activity没有指定Theme就用application的,如果Theme是白色的,屏幕就是白色的;如果是Dark,就会显示黑屏。

热启动

后台已有该应用的进程(按back键、home键)

热启动在直到应用完成渲染之前都会一直显示一个空白窗口。

App启动过程

ActivityThread运行在应用进程的主线程上,响应AMS启动、暂停Activity,广播启动等消息。

AMS:统一调度各应用程序的Activity、内存管理、进程管理

  1. 点击Launcher,启动程序,通知ActivityManagerService
  2. AMS通知zygote进程孵化出应用进程,分配内存空间等
  3. 执行该应用ActivityThread的main方法
  4. APP通知AMS,AMS保存一个该应用的代理对象,AMS通过它可以控制应用进程
  5. AMS通知应用进程创建入口的Activity实例,执行它的生命周期
    • Application的构造方法
    • attachBaseContext()
    • onCreate()
    • LauncherActivity的构造方法
    • setTheme()设置主题等信息
    • Activity的生命周期

启动页优化

设置一个启动页SplashFragment进行过度。并在MainActivity中优先展示SplashFragment,显示完毕后再进行remove。当然MainActivity的主布局可以采用ViewStub进行懒加载。

1
2
3
4
5
6
7
8
9
10
11
protected void onCreate(Bundle savedInstanceState){
setContentView();
//显示splashFragment

getWindow().getDecorView().post(new Runnable(){
//渲染主界面,用队列
viewStub.inflate();
});

//等时间结束后,取消splashFragment,呈现主界面
}

评估

  1. 系统提供的启动时间方案

    logcat的输出,关键字为Displayed

  2. ADB命令提供的启动时间方案

    1
    adb shell am start -w packagename/activity

    ThisTime:一连串启动的activity中的最后一个activity的启动耗时
    TotalTime:新进程的Activity的启动耗时,但不包括前一个应用activity中pause()方法的耗时
    WaitTime:总的耗时

    一般情况下ThisTime和TotalTime耗时是相同的。有一个场景会导致这两个值不同,比如点击App图标,先启动一个无界面的activity做逻辑处理,接着又启动一个有界面的activity

  3. 利用TraceView进行分析

    • 使用方法
      • AS中的startMethodTracing
      • 代码中使用Debug.startMethodTracing(“xxx.trace”)和stop,然后sdcard下会生成trace文件
    • 最关心的数据
      • Calls+Recur Calls / Total, 查找自身占用时间不长,但是调用频繁的操作
      • Cpu Time / Call , 某个函数消耗CPU的平均时间,一般用来查找调用次数不多,但每次调用却需要花费很长时间的方法

界面优化

  1. 尽量使用include、merge、ViewStub标签
  2. 不使用冗余嵌套和过于复杂布局
  3. 尽量使用GONE替换INVISIBLE
  4. 使用weight后尽量将width和height设置为0
  5. item存在复杂嵌套关系时用自定义View替代,减少measure与layout次数
  6. 主题上使用windowbackground为启动Activty设置简单的自定义Drawable
  7. 第三方SDK在使用到的地方再进行初始化

Android事件分发

发表于 2017-02-21 | 分类于 android | 阅读次数

[TOC]

参考:

Kelin的图解Android事件分发机制

源码解读

概览(以ACTION_DOWN为例)

分发流程图

从上到下依次为Activity、ViewGroup、View

  1. dispatchTouchEvent和onTouchEvent一旦返回true,事件就停止传递了
  2. dispatchTouchEvent和onTouchEvent返回false,都会回传给父控件的onTouchEvent处理(其中Activity会终止)

但其实调用顺序完整的说法应该是

Activity>PhoneWindow>DecorView>ViewGroup>View

onTouch

dispatchTouchEvent相当于onTouch

这几个方法的优先级是onTouch>onTouchEvent>onClick

onTouch返回true,相当于dispatchTouchEvent返回true

而onClick是在onTouchEvent中调用的

关于ACTION_MOVE和ACTION_UP

  • 当dispatchTouchEvent进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到MOVE和UP的事件。且一个View一旦决定拦截事件,那么这一个事件序列都由它来处理,而不会再调用onInterceptTouchEvent了
  • 如果在某个控件的dispatchTouchEvent返回true消费事件,那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

事件消费

  • 如果我们在onTouchEvent中消费了事件,那么MOVE和UP事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent并结束本次事件传递过程

事件分发

结合代码分析

dispatchTouchEvent

Activity#dispatchTouchEvent

  1. Activity调用了window的dispatchTouchEvent
  2. window调用了mDecor的dispatchTouchEvent
  3. mDecor调用了super即FrameLayout的dispatchTouchEvent
1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

ViewGroup#dispatchTouchEvent

伪代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false; // 默认状态为没有消费过

if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View
result = child.dispatchTouchEvent(ev);
}

if (!result) { // 如果事件没有被消费,询问自身onTouchEvent
result = onTouchEvent(ev);
}

return result;
}
真实代码
  1. 首先判断是否需要调用onInterceptTouchEvent,mFirstTouchTarget在事件不被拦截并且交给子元素处理时不为空,即有子View处理事件时,此值不为空
  2. disallowIntercept由子View的requestDisallowInterceptTouchEvent决定,但是这个对ACTION_DOWN无效,因为在dispatchTouchEvent开头会重置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// check for interception
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
  1. 接下来遍历子View,满足:

    • 没有进行动画
    • 点击的位置在View的坐标范围内的View会被调用dispatchTouchEvent

View#dispatchTouchEvent

调用步骤:

  1. onTouch(注意View是disabled的话,只要是clickable或者longclickable仍然可以消费点击事件,只是不会生效)
  2. onTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean dispatchTouchEvent(MotionEvent event) {

boolean result = false;

if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}
}

return result;
}

人生苦短,我用python

发表于 2017-02-12 | 分类于 python | 阅读次数

人生苦短,我用Python

廖雪峰的个人网站

缺点

  1. 运行速度慢
  2. 代码不能加密(解释型语言)

入门

变量

py变量本身类型不固定,是一门动态语言,一个变量可以被赋值成不同类型。

比如之前为int,后来为string

判断常量类型可以使用isinstance(x, (int, float))

命名规则

  • 常量
    • 全大写

tips

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
# output
# py中使用r''表示括号中的东西不转义
print "%s %d" % ("",1)

# input
user = raw_input("Enter your name:")

# getHelp
help(funcName)

# Maths
# / 普通除法,结果会是浮点数
# // 地板除法,结果不一定
# ** 双星号

# 空值 None
name = None

# and or not
5 > 3 and 3 > 1

#string
string[index]
string[start:end]


#if
if expression:
if_suite
elif expression:
elif_suite
else:
else_suite

#for iterable
for XX in dict|str|list|set|tuple|generator:
for k,v in aDict.items()

#while
while expression:

变量不需要预先声明类型,在赋值的那一刻被初始化。

字符编码

  • UNICODE
    • 把所有语言都统一到一套编码里
    • 通常2个字节
    • 计算机内存中统一使用UNICODE编码,按需进行转换
  • ASCII
    • 1个字节,通常用来保存英文
  • UTF-8
    • 英文字符1个字节,中文字符3个字节
    • 有一个额外的好处,ASCII编码可以看成是UTF8编码的一部分

list

方括号

有序的集合,可以随时添加和删除其中的元素。 元素可以是任意元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
aList = [1,2,3,4]
#取最后一个元素可以用-1
aList[-1]
#insert
aList.append()
aList.insert(1,'hello')
#pop
aList.pop()
aList.pop(int)

#列表生成
#使用list函数
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 循环生成
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 全排列
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

扩展:generator

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
#列表生成的[]改为()
g = (x * x for x in range(1, 11))
#记录生成算法,需要的时候再进行计算
for n in g:
print(g)
next(g)

#如果一个函数中包含yield关键字,这个函数就是一个generator
#generator的函数在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
#next可能跑出StopIteration错误,需要捕获
while True:
try:
XXX
except StopIteration as e:
print("generator return value",e.value)
break
else:
finally:

Tuple

圆括号,不可更改

1
2
3
4
# 元组不可以被更改(内容可以)
aTuple = ('robot',2,3,4)
# 定义一个元素的tuple需要加逗号,否则t就是1这个数了
t = (1,)

字典dict(Map)

大括号

注意key是不可变的(字符串、整数)

1
2
3
4
5
6
7
8
aDict = {'host':'earth'}
aDict['port']=80
aDict.keys()
aDict['port']
# 判断是否in
'port' in aDict
# 删除
aDict.pop('')

set

是一组key的集合,且key不能重复

需要提供一个list作为输入集合,会自动过滤重复项

1
2
3
4
s = set([1,2,3])
s.add()
s.remove()
#可以做数学意义上的& |

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClassName(base_class[es]):
"optional documentation string"
static_member_declarations
method_declarations

#example
class FooClass(object):
"test"
version = 0.1
#invoke after construction,self = this
def __init__(self,nm='Hg Dendi'):
func_suite

#etc

dendi = Student();
slots

可以动态给对象或类绑定属性和方法

slots表示限制对象的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

# property 表示可以通过属性访问
class Student(object):
@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

s = Student()
s.score = 100

Enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 2 子类继承Enum
from enum import Enum, unique

@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

day1 = Weekday.Mon
metaclass

控制类的创建行为

1
2
3
4
5
6
7
8
9
10
11
12
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)

class MyList(list, metaclass=ListMetaclass):
pass

>>> L = MyList()
>>> L.add(1)
>> L
[1]

标识符

1
2
_xxx_	系统定义、特殊变量,可以被直接饮用
_xxx private

build-in func

Func
operator.eq() Compare
str(obj) get description
type(obj) getType
repr(obj) or. ‘obj’ 字符串表示
ord(char) 字符的证书表示
chr(int) 整数对应的char
isinstance(x, str) 判断前者是不是后者的类型
map(func,list) 将list中的元素通过func进行转换
reduce(func,list) 将list中的元素通过func进行减少
filter(func,list) 根据func指示的布尔值进行过滤,返回Iterator,一般需要list()
sorted(list,key=func,reverse = bool) 排序,根据key的大小排序
dir() 一个对象所有的方法

buildInFunc

函数

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
# annotation & func
def function_name([arguments]):
"optional documentation string."
function_suite

#etc
def my_abs(x):
if x>=0:
return x
else:
return -x

#可以使用默认参数
def power(a,b=2):
do sth
#默认参数必须指向不变对象!否则多次执行会出现问题
def add_end(L=[]):
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END', 'END']

#可变参数,传入0个或任意个参数,在调用时自动组装为tuple
def cal(*numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum

cal(1,2)
cal(0)
aList = [1,2,3]
cal(*aList)

#关键字参数,传入任意个含参数名的参数,在函数内部自动组装为一个dict
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

# 命名关键字参数
# 限制关键字参数的名字,比如只接受city和job作为关键字参数
# 命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名参数关键字
# 可变参数后不需要再额外加*,默认变为关键字参数
def person(name,age,*,city=“Beijing”,job)
print(name,age,city,job)

需要注意,默认参数必须指向不变对象,否则会出现默认参数更改后,之后再次调用函数的默认参数为之前更改过的对象。

懒加载函数

比如不需要立即求和,而是在后面的代码中根据需要再进行计算

注意返回的函数不要引用循环变量,或者后续会发生变化的变量

1
2
3
4
5
6
7
8
9
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
f()
匿名函数
1
2
3
4
lambda x:x*x
#就是
def f(x):
return x*x
wrapper

函数也有对象,可以拿到属性.

若想在代码运行期间增加功能,称为装饰器。

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
def log(func):
# 使wrapper和func的__name__相同
@functools.wraps(func)
def wrapper(*args,**kw):
print('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper

#相当于执行了now = log(now)
@log
def now()
print("2017-01-01")
#通过属性拿到函数名'now'
now.__name__

#三层嵌套,可以log加参数
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

#相当于now = log('execute')(now)
@log('execute')
def now():
print('2015-3-25')
偏函数

把一个函数的某个参数固定住

1
2
3
import functools
int2 = functools.partial(int , base=2)
int2('1000000')

编码

  1. ASCII 英语和数字,一个字节表示一个字符
  2. Unicode 通常两个字节表示一个字符,特殊符号可能需要四个(Python3)
  3. UTF-8 把字符编码成1-6个字节,汉字通常3个字节,英文字母1个字节
1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

文件读取

1
2
3
4
5
6
7
8
f = open('//','r',encoding='gbk')
#更推荐用with来写
with open('','r') as f:
do sth
# 无参即一次读完,可以调用read(size),readline()
f.read()
f.write('error')
f.close()

进程

Linux操作系统提供了一个fork()系统调用,调用一次,返回两次,因为操作系统把当前进程赋值了一份,即创建了一个子进程,故而分别在父进程和子进程内返回了。

其中子进程返回0,而父进程返回子进程的id。因为父进程可以fork出很多子进程,所以父进程要记下每个子进程的id,而子进程只需要调用getppid()就可以拿到父进程的ID

1
2
3
4
5
6
7
8
9
import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
multiprocessing

因为windows没有fork,故而需要一个跨平台的多进程支持库

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。

join() 方法用于进程间的同步,表示等待子进程结束后再继续往下运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
pool

大量启动子进程,可以用进程池的方式批量创建子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
#pool默认大小为CPU的核数
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')

线程

任何进程默认就会启动一个线程,我们把它称为主线程。

使用threading开启新线程,threading.Thread(target=loop,name=’’)

Lock

多进程中,同一个变量,各自有一份拷贝存在每个进程中,互不影响。而多线程中,所有变量都由线程共享,故而任何一个变量都可以被任何一个线程修改。

1
2
3
4
5
6
lock = threading.Lock()
lock.acquire()
try:
do something
finally:
lock.release()

Python线程虽然是真正意义上的线程,但是解释器执行代码时,都会有一个GIL锁,

任何Python线程执行前,必须先获得GIL锁,然后执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个全局锁实际上把所有线程的执行代码都给上了锁,所以,即使跑在100核的CPU上,也只能用到1个核

GIL Global Interpreter Lock

Threadlocal

同java中的threadlocal

1
2
local_school = threading.local()
local_school.student

进程和线程比较

多进程稳定性高,一个子进程崩溃了不会影响其他进程。但是创建进程的代价大

多线程速度快一些,但是任何一个线程挂掉都可能直接造成整个代码崩溃,因为所有线程共享进程的内存。比如Windows上的“该程序执行了非法操作,即将关闭”,往往是某个线程出了问题,但是操作系统会强制结束整个进程。

切换是有代价的,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降。

  • 保存现场(CPU寄存器状态,内存页等)
  • 准备新环境(恢复上次的寄存器状态,内存页等)

正则表达式

表达 含义
\d num
\w 数字
. 任意一个字符
* 任意个字符(包括0个)
+ 至少一个字符
? 0或1个字符
{n} n个字符
{n,m} n-m个字符
\s 空格
[] 表示范围,比如[0-9a-zA-Z_]
\ 特殊符号需要在前面加\进行转义
A\ B A或B
^ 行的开头,^\d表示以数字开头
$ 结尾,\d$表示以数字结尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
s = 'ABC\\-001'
# 可以使用r前缀,这样就不用考虑转义了
s = r'ABC\-001'

#python使用re库进行正则匹配
import re
re.match(pattern,str)
#成功返回一个Match对象,否则返回None
if re.match(XX,str):
print('OK')
else:
print('Fail')

# 提取子串,用()表示的就是要提取的分组
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
# 在Match对象上用group()方法提取出自串
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

python中的正则匹配采用的是贪婪算法,若需要采用非贪婪算法,需要在后面加一个?

比如\d+?

网络编程

TCP

客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('www.sina.com.cn',80))
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
buffer = []
while True:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
s.close()

header,html = data.split(b'\r\n\r\n',1)
print(header.decode('utf-8'))
with open('sina.html','wb') as f:
f.write(html)
服务器端
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
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1',9999))
s.listen(5)
print('Waiting for connection...')
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()

# 配套的客户端
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

UDP

无连接协议,只需要知道对方的IP和端口号,就可以发送数据包,但是无法确认是否到达。

速度快,但传输数据不可靠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)

# Client
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print(s.recv(1024).decode('utf-8'))
s.close()

数据库

SQLite

轻量级,可嵌入,但是不能承受高并发访问

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
# 导入SQLite驱动:
>>> import sqlite3
# 连接到SQLite数据库
# 数据库文件是test.db
# 如果文件不存在,会自动在当前目录创建:
>>> conn = sqlite3.connect('test.db')
# 创建一个Cursor:
>>> cursor = conn.cursor()
# 执行一条SQL语句,创建user表:
>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
<sqlite3.Cursor object at 0x10f8aa260>
# 继续执行一条SQL语句,插入一条记录:
>>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
<sqlite3.Cursor object at 0x10f8aa260>
# 通过rowcount获得插入的行数:
>>> cursor.rowcount
1
# 关闭Cursor:
>>> cursor.close()
# 提交事务:
>>> conn.commit()
# 关闭Connection:
>>> conn.close()

# 查询
>>> conn = sqlite3.connect('test.db')
>>> cursor = conn.cursor()
# 执行查询语句:
>>> cursor.execute('select * from user where id=?', ('1',))
<sqlite3.Cursor object at 0x10f8aa340>
# 获得查询结果集:
>>> values = cursor.fetchall()
>>> values
[('1', 'Michael')]
>>> cursor.close()
>>> conn.close()

MySQL

为服务器端设计的数据库,能承受高并发访问,占用内存页高

内部有多种数据库引擎,最常用的是支持数据库事务的InnoDB

异步IO

当代码需要执行一个耗时的IO操作时,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理

一般采用一个消息循环,主线程不断的重复“读取消息-处理消息”这一过程

1
2
3
4
loop = get_event_loop()
while True:
event = loop.get_event()
process_event(event)

Coroutine 协程

Coroutine看上去也是函数,但在执行过程中,可以中断,然后执行别的函数,在适当的时候再回来接着执行。

看起来像多线程,但实际上在同一个线程中运行,运行效率极高,且不需要多线程的锁机制。

Python对coroutine的支持是通过generator实现的。Python的yield不但可以返回一个值,还可以接收调用者发出的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def consumer():
r = ''
while True:
# 通过yield拿到消息,处理,并返回
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'

def produce(c):
# 启动生成器
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()

c = consumer()
produce(c)

asyncio(async await)

asyncio就是一个消息循环,从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

同一个线程并发执行两个coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import threading
import asyncio

# 简化为async def hello()
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
# 简化为 await asyncio.sleep(1)
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

Cpp

发表于 2017-02-10 | 分类于 C++ | 阅读次数

函数

cpp对函数之间的的排列要满足先定义后使用。后定义先调用的函数必须声明。因为在函数被调用之前,应当让编译器知道该函数的原型。

内存区域

  • 代码区
    • 存放程序的可执行代码
  • 全局数据区
    • 静态变量
    • 一般的全局变量(自动初始化为0)
  • 栈区
    • 局部变量(不处理原内存中的值)
  • 堆区
    • 与指针有关的动态数据

变量

全局变量若和局部变量同名,需要加上::

static

static修饰的变量为静态变量、内部变量。其中局部静态变量具有局部作用域,但是却具有全局生存期。

static变量作为类的静态变量时,需要在类外部(.cpp)进行定义,如

float Stub::staticVariable=0;

静态函数成员的调用

Stub::stubmethod();

extern作用域从定义处到当前文件结束

默认参数

1
void showArea(int a = 0,int b = 10)

模板

函数模板
1
2
3
4
template <class T>
void swap(T &left , T &right){
//todo
}
类模板

编译器看到类模板时并不为它分配内存空间,直到定义了一个模板对象,即模板参数由编译器替换时,才为其分配空间。

1
2
3
4
5
6
template < class T >
class Stub{

}

Stub<int> initStub;

内联函数

一定要放在头文件中

方法是加上inline关键字

编译器在编译时,会将内联函数采用函数体进行替换,是以增加目标代码为代价来换取时间的。

指针

1
2
3
4
5
6
7
8
9
10
11
//返回指针 
float *search(float (*p)[4],int n){
return *(p+n);
 }

//一种错误情况,局部变量导致系统释放空间,使得返回的地址无效
char *getName(){
char name[81];

return name;
}

void指针表示任何类型。但是不能对其使用算数操作。

const可以修饰类型、变量名

  • 修饰类型表示不可以通过指针改变现在所指的内容
  • 修饰变量名,表示可以改变指针所指向空间的内容

Main

1
2
3
int main(int argc , char *argv[]){

}

内存的动态分配和释放

new和delete

1
2
3
4
5
6
7
8
9
10
11
12
13
int *iptr = new int;
int *iptr = new int(30);

int *a;
a = new int[100];
//若空间不足 new int会返回0或NULL
if(NULL == a){
exit(0);
}

//释放
delete a;
delete [] a;

malloc和free

1
2
3
4
NODE * NODE;
NODE = (NODE*) malloc(sizeof(NODE));

free(NODE);

结构体

1
typedef char NAME[100];

类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Stub{
private:
int a;
public:
void calculate();
}

//类外部定义函数需要用两个引号
void stub::calculate(){
return;
}

//新建类
 Stub stub;

.h文件和.cpp文件

将类的定义存储在头文件中,函数成员放在cpp文件中

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
//Rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
class Rectangle{
public:
void setData(float,float);
void calculate();
private:
float width;
float height;
}

//Rectangle.cpp
#include <iostream>
using namespace std;
#include "Rectangle.h"

void Rectangle::setData(float w,float l){
width = w;
height = l;
}

void Rectangle::calculate(){

}

友元函数

友元函数不是类中的函数成员,但它可以访问类中定义的私有成员。

友元破坏了数据的封装和隐藏,尽量不使用或少适用友元。

外部函数作为类的友元

1
2
3
4
5
6
7
//类中定义
friend double Distance(Point &a,Point &b);

//外部函数定义
double Distance(Point &a , Point &b){

}

类的成员函数作为另一个类的友元

这样的成员函数也被称为友元成员。既可以访问所在类对象的私有成员,也可以访问friend声明语句所在类对象中的私有成员和公有成员。

1
2
3
4
5
6
7
8
9
10
11
class Budget{
private:
float divBudget;
public:
friend void Aux::addBudget(float , Budget &);
}

//Aux.cpp
void Aux::addBudget(float , Budget &){
divBudget++;
}

一个类作为另外一个类的友元

B类中的每个函数成员都能访问A类中的私有成员。

1
2
3
4
5
class A{
public:
void Display();
friend class B;
}

拷贝构造函数

并不调用构造函数,而是采用对象按位拷贝操作,将b对象的每个成员复制到a中。这样如果b中有指针,a中也会有指针,而a对象在析构的时候可能会删除指针指向的内容,导致b对象错乱。

1
Stub a = b;

拷贝构造函数是特殊的构造函数,当采用一个对象初始化另一个对象时,将自动调用该函数。

拷贝构造函数在对象被创建时调用,而赋值函数在对象已经存在的情况下调用。

1
2
3
4
5
6
7
8
9
10
11
class Stub{
public:
Stub(const Stub &obj){
name = new char[strlen(obj.name)+1];
strcpy(name,obj.name);
age = obj.age;
}
private:
char *name;
int age;
}

操作符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Stub{
public:
void operator =(const Stub &right){

}

Stub operator ++();//prefix
Stub operator ++(int);//post
ostream &operator <<(ostream &strm , Stub &stub){
strm <<stub.XXX;
return strm;
}

//类型转换
operator float(){
return 12.0;
}
}

技巧

创建一个临时对象将其返回 和 创建一个局部变量并返回 是不同的。

场景1:

  1. 创建一个temp对象,调用构造函数完成初始化;
  2. 拷贝构造函数将temp赋值到内存的外部存储中;
  3. 在函数结束时,摧毁temp对象

而场景2:直接把临时对象创建并初始化在内存的外部存储单元中,省去了调用拷贝构造函数和析构函数的过程,提高了效率。

1
2
3
4
5
6
7
8
// 1
string temp(s1+s2);
return temp;

// 2
return string(s1+s2);

1和2

继承

CPP可以多重继承,其中子类的同名函数需要作用域分隔符::来确定要调用的函数

1
2
3
4
5
6
7
8
class Son : public Father {

}

//构造函数
Son::Son(string name):Father(name)

//public、protected、private为继承修饰符,控制可见性。

异常

1
2
3
4
5
6
7
8
9
10
11
12
13
float divide(int dividend , int divisor){
if(divisor == 0 ){
throw "error";
}else{
return float (dividend)/divisor;
}
}

try{
divide(a,b);
}catch(char* exceptionString){
cout << exceptionString;
}

初始化列表

构造函数参数表和函数体之间,在构造函数的函数体执行之前完成初始化列表中指定的工作。

  1. 具有继承关系的子类必须在初始化列表中调用基类的构造函数
  2. 类中的const常量成员只能在初始化列表中进行初始化
  3. 对象类型的成员的初始化放在初始化列表中则程序的效率较高

Virtual

CPP编译器在默认情况下,对函数成员的调用实施的是静态绑定。如需改为动态绑定,需要增加前缀virtual

虚函数是一个函数成员,唯一要求是在子类中一定要覆盖它。

纯虚函数

virtual <> <>() = 0;

其他

CPP中字符串末尾要有\0

Java类的生命周期

发表于 2017-01-16 | 分类于 java | 阅读次数

[TOC]

重要内存区域

方法区

运行时常量池、字段和方法信息、静态变量等数据。

已经加载的类信息、常量、静态变量以及方法代码的内存区域

堆区

Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显式的销毁。

从内存回收的角度,Java堆可以粗略的分为新生代和老年代。从内存分配的角度Java堆中可能划分出多个线程私有的分配缓冲区。不管如何划分,Java堆存储的内容是不变的,进行划分是为了能更快的回收或者分配内存。

对象在堆内存的布局

  • 对象头
    • MarkWorld,用于存储对象运行时的数据,如hashcode,锁状态标志,GC分代年龄等
    • 元数据,指向方法区中目标类的类型信息,通过元数据指针确定对象的具体类型
  • 实例数据
    • 存储对象中的各种类型的字段信息
  • 用于对齐的padding

Java虚拟机栈

每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈(Java Virtual Machine Stacks)。它的生命周期与线程相同,与线程是同时创建的。

存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常所说的栈内存(Stack)指的就是Java虚拟机栈。

本地方法栈

支持Native方法,与JVM栈相似

程序计数器

为了保证程序能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器正是起到这种作用。

运行时数据区域

线程隔离

  1. Program Counter Register 程序计数器
  2. JVM Stacks 虚拟机栈
  3. Native Stack

线程共享

  1. Java Heap 堆
    1. 是java虚拟机所管理的内存中最大的一块,用来存放对象实例
    2. 可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可
    3. 可用过-Xmx和-Xms控制
  2. Method Area 方法区
    1. 存储加载过的类信息、常量、静态变量、即时编译器编译后的代码等数据
    2. 别名为Non-Heap(非堆),是PermentGeneration永久代
    3. 包含运行时常量池,用于存放各种字面量和符号引用,不要求常量只有编译器才能产生,比如String的intern

类的生命周期

Screen Shot 2017-01-16 at 20.38.17

Loading 加载

找到需要加载的类并把类的信息加载到方法区中,然后在堆区中实例化一个Class对象,作为方法区中这个类的信息的入口

  1. 根据类的全名,生成一份二进制的字节码
  2. 将字节码解析成方法区对应的数据结构
  3. 生成Class对象的实例表示该类

Linking 连接

不一定要等到加载完成后,有时可以同时执行(比如Loading和Verification可以同时执行)

Verification

保证类符合Java的语法规范。

比如:bytecode的完整性、final方法的检查、方法签名的检查

Preparation

JVM为类的成员变量分配内存空间并且赋予默认初始值,需要注意的是这个阶段不会执行任何代码,而只是根据变量类型决定初始值。即所有int赋值为0,代码中的初始化赋值不进行运行。

也可能会为有助于提高程序性能的数据结构分配内存,比如method table。

method table这个数据结构包含了指向所有类方法的指针(包括从父类集成的方法)

Resulotion

确认类、接口、属性和方法在类的run-time constant pool的位置,并且把这些符号引用替换为直接引用。

symbolic references ==》 direct reference

Screen Shot 2017-01-16 at 20.55.10

Initialization 初始化

如果一个类第一次被直接引用,就会触发类的初始化

在此之前会先触发父类的初始化

过程

会按照代码书写顺序进行初始化,但总体规则是

  1. static先于非static
  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
class Singleton {

private static Singleton mInstance = new Singleton();// 位置1,输出1,0
public static int counter1;
public static int counter2 = 0;

// private static Singleton mInstance = new Singleton();// 位置2,输出1,1

private Singleton() {
counter1++;
counter2++;
}

public static Singleton getInstantce() {
return mInstance;
}
}

public class InitDemo {

public static void main(String[] args) {

Singleton singleton = Singleton.getInstantce();
System.out.println("counter1: " + singleton.counter1);
System.out.println("counter2: " + singleton.counter2);
}
}

直接引用

  1. 创建实例(new、反射、cloning、反序列化)
  2. 调用类的static方法
  3. 使用或对类、接口的static属性进行赋值(不包括final的与在编译器确定的常量表达式,如果使用的父类的静态变量,则只需要加载父类即可)
  4. 调用API中的某些反射方法
  5. 子类被初始化
  6. 具有main方法的类

被动使用

  1. 引用父类的静态字段,只会引起父类的初始化,不会引起子类的初始化
  2. 定义类数组,不会引起类的初始化
  3. 引用类的常量,不会引起类的初始化

Unloading 卸载

满足下面的情况,类就会被卸载

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过反射访问该类的方法

ClassLoader

程序在启动的时候,并不会一次性加载所有要用的class文件,而是根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其他class文件所引用。所以ClassLoader就是用来动态加载class文件到内存当中的。

Bootstrap ClassLoader

Extension ClassLoader

App ClassLoader

12
hgDendi

hgDendi

17 日志
6 分类
6 标签
GitHub Weibo
© 2017 - 2018 hgDendi
由 Hexo 强力驱动
主题 - NexT.Mist
0%