记一次优化列表(BaseMultiItemQuickAdapter 源码分析)

Android中列表是每个应用都会有的UI效果,而与用户的交互无非就是用户上下滑动、左右滑动、点击item 等等,本文就从小编遇到一次加载大量数据而影响体验优化之旅。

项目的列表采用RecycleView + BaseMultiItemQuickAdapter 分组效果,数据量10000~20000以上

数据拉取、缓存

首先是数据的获取方式,分页?还是全部获取? 这得考虑到后端的查询效率,数据库可以采用建立索引,提高效率,前端可以采用分页获取,并且为了提高体验,当然是存储在本地数据库了(NoSql),这里就有一个问题分页获取分页 再存储,数据库存储会导致阻塞线程,

果然出现了:Message Blocked in Main Thread XXXX ms,这是anr日志,说明,执行了耗时任务,UI 无响应。。。。。。。。。。
所以就想到了
这里采用了线程控制存储(建议采用线程池的方式来实现,不然物极必反,过多创建线程,浪费了资源。。。)

1
2
3
4
public CustomerReposity(){
mThreadPool = new ThreadPoolExecutor(3, 5,
10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));
}

1
2
3
4
5
6
7
mThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.d("customer","thread save to db ::"+data.list.size());
saveCustomerToLocalDB(data.list, pageNumer == 1);
}
});

从后端拉取到解析-再到数据库的时间

这边其实考虑到序列化,序列化有两种 Serializable\Parcelable,Serializable 采用反射机制,会导致大量对象产生,所以,考虑到性能的话,采用Parcelable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void writeToParcel(Parcel out, int i) {
out.writeString(cid);
pingYin = in.readString();

}

public static final Parcelable.Creator<Customer> CREATOR = new Parcelable.Creator<Customer>() {
@Override
public Customer createFromParcel(Parcel source) {
return new Customer(source);
}

@Override
public Customer[] newArray(int size) {
return new Customer[size];
}
};

总不能全部加载完毕才显示给用户看吧?

刚开始采用了数据监听(LiveData的方式,项目架构本身采用MVVM的方式)

1
2
3
4
5
6
7

mReposity.getCustomerDefaultTeam().observe(this, new Observer<List<Customer>>() {
@Override
public void onChanged(@Nullable List<Customer> customers) {
mAdapter.setNewData(customers);
}
});

又会有问题:数据边加载边刷新??? 列表一直notifyDataChanged………这边采用preFetch思想,先给用户展示部分数据

RecycleView 优化

布局优化

也就是item 布局,减少层级,这样就避免多次绘制UI,如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源,如果不要求动画,可以通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提神效率,通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间(显示范围之外,应该额外缓存的空间),如下所示

1
2
3
4
5
6
7
8
9
10
11
mViewDataBinding.rvCustomers.setLayoutManager(new LinearLayoutManager(this){
@Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
return 100;
}
});
mViewDataBinding.rvCustomers.setAdapter(mAdapter);
mViewDataBinding.rvCustomers.setHasFixedSize(true);
mViewDataBinding.rvCustomers.setItemViewCacheSize(20);
((SimpleItemAnimator) mViewDataBinding.rvCustomers.getItemAnimator()).setSupportsChangeAnima mAdapter.expandAll();
mCustomerReposity = CustomerReposity.getmInstances();

可参考资料来源:https://blankj.com/2018/09/29/optimize-recycler-view/

List Remove效率

在页面点击展开分组、收缩分组的时候发现,展开的时候很顺利,收缩的时候即卡顿个几秒,,看下日志ANR。。。。。,查看  BaseQuickAdapter  源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int collapse(@IntRange(from = 0) int position, boolean animate, boolean notify) {
position -= getHeaderLayoutCount();

IExpandable expandable = getExpandableItem(position);
if (expandable == null) {
return 0;
}
int subItemCount = recursiveCollapse(position);
expandable.setExpanded(false);
int parentPos = position + getHeaderLayoutCount();
if (notify) {
if (animate) {
notifyItemChanged(parentPos);
notifyItemRangeRemoved(parentPos + 1, subItemCount);
} else {
notifyDataSetChanged();
}
}
return subItemCount;
}

因此分析 原因估计是出在recursiveCollapse(position)这个方法 ,继续往下看

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

@SuppressWarnings("unchecked")
private int recursiveCollapse(@IntRange(from = 0) int position) {
T item = getItem(position);
if (!isExpandable(item)) {
return 0;
}
IExpandable expandable = (IExpandable) item;
int subItemCount = 0;
if (expandable.isExpanded()) {
List<T> subItems = expandable.getSubItems();
if (null == subItems) return 0;

for (int i = subItems.size() - 1; i >= 0; i--) {
T subItem = subItems.get(i);
int pos = getItemPosition(subItem);
if (pos < 0) {
continue;
}
if (subItem instanceof IExpandable) {
subItemCount += recursiveCollapse(pos);
}
mData.remove(pos);
subItemCount++;
}
}
return subItemCount;
}

看了下代码好像没做什么,就是删除分组下的item,就想到了,我一个分组有几十W+的item,它这样remove,是不是时间花太久了??????》〉》〉
果然 remove 方法执行耗时了。。。。。。开始改造

```java

/**
 * 改造源码****###
 * @param position
 * @return
 */
private int recursiveCollapse(@IntRange(from = 0) int position) {
    MultiItemEntity item = getItem(position);
    if (!isExpandable(item)) {
        return 0;
    }
    IExpandable expandable = (IExpandable) item;
    if (expandable.isExpanded()) {
        List<MultiItemEntity> subItems = expandable.getSubItems();
        mData = removeAllChildList(mData,subItems);
    }
    return expandable.getSubItems().size();
}

public int collapsew(@IntRange(from = 0) int position, boolean animate) {
    return collapses(position, animate, true);
}


/**
 * 替代List.removeAll()的方法提高效率
 * @param source
 * @param destination
 * @return
 */
public List<MultiItemEntity> removeAllChildList(List<MultiItemEntity> source, List<MultiItemEntity> destination) {
    List<MultiItemEntity> afterRemove = new LinkedList<MultiItemEntity>();
    Set<MultiItemEntity> destinaToRemove = new HashSet<MultiItemEntity>(destination);
    for (MultiItemEntity t : source) {
        if (!destinaToRemove.contains(t)) {
            afterRemove.add(t);
        }
    }
    return afterRemove;
}

```java

Gradle插件自定义

删除java文件夹、res文件夹

在main下面新建 groovy 文件夹

新建 com.guosen.plugin (名字自己定义)

新建resources 文件夹 和groovy同级,在resources里面新建META-INF。gradle-plugins目录,底下有guosen-plugin(插件名称).properties

1
2
implementation-class=com.guosen.plugin.MyHelloWorldPlugin
//后面为实现类

插件model 的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
apply plugin: 'java-library'
apply plugin: 'java-gradle-plugin'
apply plugin: 'groovy'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation gradleApi()
implementation localGroovy()
}




apply plugin: 'maven'
uploadArchives {

repositories {
mavenDeployer {
pom.groupId = 'com.guosen.implePlugin'
pom.artifactId = 'guosen-plugin'
pom.version = 1.0
//本地的Maven地址设置为D:/repos
repository(url: uri('../repo'))
}
}
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

整个项目的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

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply plugin: 'guosen-plugin'
buildscript {

repositories {
google()
jcenter()
maven {
//cooker-plugin 所在的仓库
//这里是发布在本地文件夹了
url uri('./repo')
}
}
dependencies {

classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.guosen.implePlugin:guosen-plugin:1.0'

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

allprojects {
repositories {
google()
jcenter()
}
}

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

数据结构

String

1
2
3
4
5
6
7
8
9
10
11
12
13
String s1 = new String();
String s2 = "lgs";
int s2Len = s2.length();
s2.substring(4, 8); // return "ryan"
StringBuilder s3 = new StringBuilder(s2.substring(4, 8));
s3.append("bill");
String s2New = s3.toString(); // return "ryanbill"
// convert String to char array
char[] s2Char = s2.toCharArray();
// char at index 4
char ch = s2.charAt(4); // return 'r'
// find index at first
int index = s2.indexOf('r'); // return 4. if not found, return -1

Linked List - 链表

链表是线性表的一种。线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构。我们常用的数组就是一种典型的顺序存储结构。

相反,链式存储结构就是两个相邻的元素在内存中可能不是相邻的,每一个元素都有一个指针域,指针域一般是存储着到下一个元素的指针。这种存储方式的优点是定点插入和定点删除的时间复杂度为 O(1),不会浪费太多内存,添加元素的时候才会申请内存,删除元素会释放内存。缺点是访问的时间复杂度最坏为 O(n)
顺序表的特性是随机读取,也就是访问一个元素的时间复杂度是O(1),链式表的特性是插入和删除的时间复杂度为O(1)。
链表就是链式存储的线性表。根据指针域的不同,链表分为单向链表、双向链表、循环链表等等。

1
2
3
4
5
6
7
8
9
10
11
12
public class ListNode {
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}

public void add(int val){
ListNode listNode = new ListNode(val);
next = listNode;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static ListNode reverse(ListNode head) {
if (head.next == null)
return head;
ListNode reversedListNode = reverse(head.next);
head.next.next = head;
head.next = null;
return reversedListNode;
}


/**
* 反转链表
* @param node
*/

public static void printListFromTailToHeadByReverseList(ListNode node) {
ListNode reversedNode = reverse(node);
while (reversedNode != null) {
System.out.print(reversedNode.val + ",");
reversedNode = reversedNode.next;
}

}

#二叉树
二叉树是每个节点最多有两个子树的树结构,子树有左右之分,二叉树常被用于实现二叉查找树和二叉堆。

二叉树的第i层至多有 2^{i-1}2
​i−1
​​ 个结点;深度为k的二叉树至多有 2^k-12
​k
​​ −1 个结点;对任何一棵二叉树T,如果其终端结点数为 n_0n
​0
​​ , 度为2的结点数为 n_2n
​2
​​ , 则 n_0=n_2+1n
​0
​​ =n
​2
​​ +1。因为度为1的节点对度为0的节点数目不会有影响,而每增加一个度为2的节点总的来说则会相应增加一个度为0的节点。

一棵深度为 kk, 且有 2^k-12
​k
​​ −1 个节点称之为满二叉树;深度为 k k,有 nn 个节点的二叉树,当且仅当其每一个节点都与深度为 kk 的满二叉树中序号为 11 至 nn 的节点对应时,称之为完全二叉树。完全二叉树中重在节点标号对应

1
2
3
4
5
6
7
8
9
10
11
public class TreeNode {
private int val;

public TreeNode(int val){
this.val = val;
this.left = null;
this.right = null;
}
TreeNode left,right;

}

树的遍历

从二叉树的根节点出发,节点的遍历分为三个主要步骤:对当前节点进行操作(称为“访问”节点,或者根节点)、遍历左边子节点、遍历右边子节点。访问节点顺序的不同也就形成了不同的遍历方式。需要注意的是树的遍历通常使用递归的方法进行理解和实现,在访问元素时也需要使用递归的思想去理解。实际实现中对于前序和中序遍历可尝试使用递归实现。

按照访问根元素(当前元素)的前后顺序,遍历方式可划分为如下几种:

深度优先:先访问子节点,再访问父节点,最后访问第二个子节点。根据根节点相对于左右子节点的访问先后顺序又可细分为以下三种方式。
前序(pre-order):先根后左再右
中序(in-order):先左后根再右
后序(post-order):先左后右再根
广度优先:先访问根节点,沿着树的宽度遍历子节点,直到所有节点均被访问为止。
如下图所示,遍历顺序在右侧框中,红色A为根节点。使用递归和整体的思想去分析遍历顺序较为清晰。

二叉树的广度优先遍历和树的前序/中序/后序遍历不太一样,前/中/后序遍历使用递归,也就是栈的思想对二叉树进行遍历,广度优先一般使用队列的思想对二叉树进行遍历。

如果已知中序遍历和前序遍历或者后序遍历,那么就可以完全恢复出原二叉树结构。其中最为关键的是前序遍历中第一个一定是根,而后序遍历最后一个一定是根,中序遍历在得知根节点后又可进一步递归得知左右子树的根节点。但是这种方法也是有适用范围的:元素不能重复!否则无法完成定位。

Binary Search Tree - 二叉查找树

一颗二叉查找树(BST)是一颗二叉树,其中每个节点都含有一个可进行比较的键及相应的值,且每个节点的键都大于等于左子树中的任意节点的键,而小于右子树中的任意节点的键。

使用中序遍历可得到有序数组,这是二叉查找树的又一个重要特征。

二叉查找树使用的每个节点含有两个链接,它是将链表插入的灵活性和有序数组查找的高效性结合起来的高效符号表实现。

Stack - 栈

Set

1
2
3
Set<String> hash = new HashSet<String>();
hash.add("billryan");
hash.contains("billryan");

Map

排序

冒泡排序、选择排序、插入排序、归并排序、堆排序、桶排序、计数排序

Android 编码规范

安卓开发规范(updating)

摘要

1 前言

为了利于项目维护以及规范开发,促进成员之间Code Review的效率,故提出以下开发规范,如有更好建议,欢迎到GitHub提issue,原文地址:安卓开发规范(updating)

2 AS规范

工欲善其事,必先利其器。

  1. 尽量使用最新版的IDE进行开发;
  2. 编码格式统一为UTF-8
  3. 编辑完.java.xml等文件后一定要格式化(基本格式方面使用 AS 默认模板即可);
  4. 删除多余的import,减少警告出现,可利用AS的Optimize Imports(Settings → Keymap → Optimize Imports)快捷键;
  5. Android开发者工具可以参考这里~ Android开发者工具

3 命名规范

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。正确的英文拼写和语法可以让阅读者易于理解,避免歧义。

注意:即使纯拼音命名方式也要避免采用。但alibabataobaoyoukuhangzhou等国际通用的名称,可视同英文。

3.1 包名

包名全部小写,连续的单词只是简单地连接起来,不使用下划线,采用反域名命名规则,全部使用小写字母。一级包名是顶级域名,通常为com,edu,gov,net,org等,二级包名为公司名,三级包名根据应用进行命名,后面就是对包名的划分了,关于包名的划分,推荐使用按功能分,一开始我们也是按照层去分包的,很坑爹。按照功能分可能你不是很好区分在哪个功能中,不过也比你按照层区分要好找很多。具体可以参考这篇博文~ Package by features, not layers,当然,我们大谷歌也有相应的sample~ iosched,其结构如下所示,很值得学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
java
└─com
└─google
└─samples
└─apps
└─iosched
│ AppApplication.java 定义Application类
│ Config.java 定义配置数据(常量)

├─about
│ AboutActivity.java

├─appwidget
│ ScheduleWidgetProvider.java
│ ScheduleWidgetRemoteViewsService.java

├─debug
│ │ DebugAction.java
│ │ DebugActivity.java
│ │ DebugFragment.java
│ │
│ └─actions
│ DisplayUserDataDebugAction.java
│ ForceAppDataSyncNowAction.java
│ ForceSyncNowAction.java
│ ...

├─explore
│ │ ExploreIOActivity.java
│ │ ExploreIOFragment.java
│ │ ExploreModel.java
│ │ ...
│ │
│ └─data
│ ItemGroup.java
│ LiveStreamData.java
│ MessageData.java
│ ...

├─feedback
│ FeedbackApiHelper.java
│ FeedbackConstants.java
│ FeedbackHelper.java
│ ...

├─framework
│ FragmentListener.java
│ LoaderIdlingResource.java
│ Model.java
│ ...定义interface并实现

├─gcm
│ │ GCMCommand.java
│ │ GCMIntentService.java
│ │ GCMRedirectedBroadcastReceiver.java
│ │ ...
│ │
│ └─command
│ AnnouncementCommand.java
│ NotificationCommand.java
│ SyncCommand.java
│ ...

├─io
│ │ BlocksHandler.java
│ │ HandlerException.java
│ │ HashtagsHandler.java
│ │ ...处理model
│ │
│ ├─map
│ │ └─model
│ │ MapData.java
│ │ Marker.java
│ │ Tile.java
│ │
│ └─model
│ Block.java
│ DataManifest.java
│ Hashtag.java
│ ...

├─map
│ │ InlineInfoFragment.java
│ │ MapActivity.java
│ │ MapFragment.java
│ │ ...
│ │
│ └─util
│ CachedTileProvider.java
│ MarkerLoadingTask.java
│ MarkerModel.java
│ ...

├─model
│ ScheduleHelper.java
│ ScheduleItem.java
│ ScheduleItemHelper.java
│ ...定义model以及实现model相关操作

├─myschedule
│ MyScheduleActivity.java
│ MyScheduleAdapter.java
│ MyScheduleFragment.java
│ ...

├─provider
│ ScheduleContract.java
│ ScheduleContractHelper.java
│ ScheduleDatabase.java
│ ...实现ContentProvider
│ (也在此处定义provider依赖的其它类,比如db操作)

├─receiver
│ SessionAlarmReceiver.java

├─service
│ DataBootstrapService.java
│ SessionAlarmService.java
│ SessionCalendarService.java

├─session
│ SessionDetailActivity.java
│ SessionDetailConstants.java
│ SessionDetailFragment.java
│ ...

├─settings
│ ConfMessageCardUtils.java
│ SettingsActivity.java
│ SettingsUtils.java

├─social
│ SocialActivity.java
│ SocialFragment.java
│ SocialModel.java

├─sync
│ │ ConferenceDataHandler.java
│ │ RemoteConferenceDataFetcher.java
│ │ SyncAdapter.java
│ │ ...
│ │
│ └─userdata
│ │ AbstractUserDataSyncHelper.java
│ │ OnSuccessListener.java
│ │ UserAction.java
│ │ ...
│ │
│ ├─gms
│ │ DriveHelper.java
│ │ GMSUserDataSyncHelper.java
│ │
│ └─util
│ UserActionHelper.java
│ UserDataHelper.java

├─ui
│ │ BaseActivity.java
│ │ CheckableLinearLayout.java
│ │ SearchActivity.java
│ │ ...BaseActivity以及自定义UI组件
│ │
│ └─widget
│ AspectRatioView.java
│ BakedBezierInterpolator.java
│ BezelImageView.java
│ ...自定义小UI控件

├─util
│ AboutUtils.java
│ AccountUtils.java
│ AnalyticsHelper.java
│ ...工具类,提供静态方法

├─videolibrary
│ VideoLibraryActivity.java
│ VideoLibraryFilteredActivity.java
│ VideoLibraryFilteredFragment.java
│ ...

└─welcome
AccountFragment.java
AttendingFragment.java
ConductFragment.java
...

参考Google I/O 2015的代码结构,按功能分包具体可以这样做:

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
src
└─com
└─domain
└─app
│ AppApplication.java 定义Application类
│ Config.java 定义配置数据(常量)

├─framework
│ 定义interface以及相关基类

├─io
│ 数据定义(model)、数据操作(比如json解析,但不包括db操作)

├─model
│ 定义model(数据结构以及getter/setter、compareTo、equals等等,不含复杂操作)
│ 以及modelHelper(提供便于操作model的api)

├─provider
│ 实现ContentProvider,及其依赖的db操作

├─receiver
│ 实现Receiver

├─service
│ 实现Service(比如IntentService),用于在独立线程中异步do stuff

├─ui
│ 实现BaseActivity,以及自定义view和widget,相关的Adapter也放这里

├─util
│ 实现工具类,提供静态方法

├─feature1
│ Item.java 定义model
│ ItemHelper.java 实现modelHelper
│ feature1Activity.java 定义UI
│ feature1DAO.java 私有db操作
│ feature1Utils.java 私有工具函数
│ ...其它私有class

├─...其它feature

PBF(按功能分包Package By Feature)与PBL(按层分包Package By Layer)相比较有如下优势:

  • package内高内聚,package间低耦合

哪块要添新功能,只改某一个package下的东西

按class职能分层(PBL)降低了代码耦合,但带来了package耦合,要添新功能,需要改model、dbHelper、view、service等等,需要改动好几个package下的代码,改动的地方越多,越容易产生新问题,不是吗?

按功能分包(PBF),featureA相关的所有东西都在featureA包,feature内高内聚高度模块化,不同feature之间低耦合,相关的东西都放在一起,还好找

  • package有私有作用域(package-private scope)

你负责开发这块功能,这个目录下所有东西都是你的

PBL的方式是把所有工具方法都放在util包下,小张开发新功能时候发现需要一个xxUtil,但它又不是通用的,那应该放在哪里?没办法,按照分层原则,我们还得放在util包下,好像不太合适,但放在其它包更不合适,功能越来越多,util类也越定义越多。后来小李负责开发一块功能时发现需要一个xxUtil,同样不通用,去util包一看,怎么已经有了,而且还没法复用,只好放弃xx这个名字,改为xxxUtil……因为PBL的package没有私有作用域,每一个包都是public(跨包方法调用是很平常的事情,每一个包对其它包来说都是可访问的)

如果是PBF,小张的xxUtil自然放在feautreA下,小李的xxUtil在featureB下,如果觉得util好像是通用的,就去util包看看要不要把工具方法添进xxUtil,class命名冲突没有了

PBF的package有私有作用域,featureA不应该访问featureB下的任何东西(如果非访问不可,那就说明接口定义有问题)

  • 很容易删除功能

统计发现新功能没人用,这个版本那块功能得去掉

如果是PBL,得从功能入口到整个业务流程把受到牵连的所有能删的代码和class都揪出来删掉,一不小心就完蛋

如果是PBF,好说,先删掉对应包,再删掉功能入口(删掉包后入口肯定报错了),完事

  • 高度抽象

解决问题的一般方法是从抽象到具体,PBF包名是对功能模块的抽象,包内的class是实现细节,符合从抽象到具体,而PBL弄反了

PBF从确定AppName开始,根据功能模块划分package,再考虑每块的具体实现细节,而PBL从一开始就要考虑要不要dao层,要不要com层等等

  • 只通过class来分离逻辑代码

PBL既分离class又分离package,而PBF只通过class来分离逻辑代码

没有必要通过package分离,因为PBL中也可能出现尴尬的情况:

1
2
├─service
MainServ.java

按照PBL,service包下的所有东西都是Controller,应该不需要Serv后缀,但实际上通常为了码起来方便,直接import service包,Serv后缀是为了避免引入的class和当前包下的class命名冲突,当然,不用后缀也可以,得写清楚包路径,比如new net.ayqy.service.Main(),麻烦

而PBF就很方便,无需import,直接new MainServ()即可

  • package的大小有意义了

PBL中包的大小无限增长是合理的,因为功能越添越多

而PBF中包太大(包里class太多)表示这块需要重构(划分子包)

3.2 类名

类名都以UpperCamelCase风格编写。

类名通常是名词或名词短语,接口名称有时可能是形容词或形容词短语。现在还没有特定的规则或行之有效的约定来命名注解类型。

名词,采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的, 比如HTML, URL,如果类名称中包含单词缩写,则单词缩写的每个字母均应大写。

描述 例如
Activity 类 Activity为后缀标识 欢迎页面类WelcomeActivity
Adapter类 Adapter 为后缀标识 新闻详情适配器 NewDetailAdapter
解析类 Parser为后缀标识 首页解析类HomePosterParser
工具方法类 Utils或Manager为后缀标识(与系统或第三方的Utils区分)或功能+Utils 线程池管理类:ThreadPoolManager日志工具类:LogUtils(Logger也可)打印工具类:PrinterUtils
数据库类 以DBHelper后缀标识 新闻数据库:NewDBHelper
Service类 以Service为后缀标识 时间服务TimeService
BroadcastReceiver类 以Receiver为后缀标识 推送接收JPushReceiver
ContentProvider类 以Provider为后缀标识 ShareProvider
自定义的共享基础类 以Base开头 BaseActivity,BaseFragment

测试类的命名以它要测试的类的名称开始,以Test结束。例如:HashTestHashIntegrationTest

接口(interface):命名规则与类一样采用大驼峰命名法,多以able或ible结尾,如
interface Runnableinterface Accessible

注意:如果项目采用MVP,所有Model、View、Presenter的接口都以I为前缀,不加后缀,其他的接口采用上述命名规则。

3.3 方法名

方法名都以lowerCamelCase风格编写。

方法名通常是动词或动词短语。

方法 说明
initXX() 初始化相关方法,使用init为前缀标识,如初始化布局initView()
isXX() checkXX() 方法返回值为boolean型的请使用is或check为前缀标识
getXX() 返回某个值的方法,使用get为前缀标识
setXX() 设置某个属性值
handleXX()/processXX() 对数据进行处理的方法
displayXX()/showXX() 弹出提示框和提示信息,使用display/show为前缀标识
updateXX() 更新数据
saveXX() 保存数据
resetXX() 重置数据
clearXX() 清除数据
removeXX() 移除数据或者视图等,如removeView();
drawXX() 绘制数据或效果相关的,使用draw前缀标识

3.4 常量名

常量名命名模式为CONSTANT_CASE,全部字母大写,用下划线分隔单词。那,到底什么算是一个常量?

每个常量都是一个静态final字段,但不是所有静态final字段都是常量。在决定一个字段是否是一个常量时,考虑它是否真的感觉像是一个常量。例如,如果任何一个该实例的观测状态是可变的,则它几乎肯定不会是一个常量。只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它示为常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Constants
static final int NUMBER = 5;
static final ImmutableListNAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }

// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final SetmutableCollection = new HashSet();
static final ImmutableSetmutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};

3.5 非常量字段名

非常量字段名以lowerCamelCase风格的基础上改造为如下风格:基本结构为scopeVariableNameType

scope:范围

非公有,非静态字段命名以m开头。

静态字段命名以s开头。

公有非静态字段命名以p开头。

公有静态字段(全局变量)命名以g开头。

例子:

1
2
3
4
5
6
7
8
public class MyClass {
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
private static MyClass sSingleton;
public int pField;
public static int gField;
}

使用1字符前缀来表示作用范围,1个字符的前缀必须小写,前缀后面是由表意性强的一个单词或多个单词组成的名字,而且每个单词的首写字母大写,其它字母小写,这样保证了对变量名能够进行正确的断句。

Type:类型

考虑到Android中使用很多UI控件,为避免控件和普通成员变量混淆以及更好达意,所有用来表示控件的成员变量统一加上控件缩写作为后缀(文末附有缩写表)。

对于普通变量一般不添加类型后缀,如果统一添加类型后缀,请参考文末的缩写表。

用统一的量词通过在结尾处放置一个量词,就可创建更加统一的变量,它们更容易理解,也更容易搜索。

注意:如果项目中使用ButterKnife,则不添加m前缀,以lowerCamelCase风格命名。

例如,请使用mCustomerStrFirstmCustomerStrLast,而不要使用mFirstCustomerStrmLastCustomerStr

量词列表 量词后缀说明
First 一组变量中的第一个
Last 一组变量中的最后一个
Next 一组变量中的下一个变量
Prev 一组变量中的上一个
Cur 一组变量中的当前变量

说明:

集合添加如下后缀:List、Map、Set
数组添加如下后缀:Arr

注意:所有的VO(值对象)统一采用标准的lowerCamelCase风格编写,所有的DTO(数据传输对象)就按照接口文档中定义的字段名编写。

3.6 参数名

参数名以lowerCamelCase风格编写。
参数应该避免用单个字符命名。

3.7 局部变量名

局部变量名以lowerCamelCase风格编写,比起其它类型的名称,局部变量名可以有更为宽松的缩写。

虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。

即使局部变量是final和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。

3.8 临时变量

临时变量通常被取名为ijkmn,它们一般用于整型;cde,它们一般用于字符型。 如:for (int i = 0; i < len ; i++)

3.9 类型变量名

类型变量可用以下两种风格之一进行命名:

  1. 单个的大写字母,后面可以跟一个数字(如:E, T, X, T2)。
  2. 以类命名方式(参考3.2 类名),后面加个大写的T(如:RequestT, FooBarT)。

更多还可参考~ 阿里巴巴Java开发手册

4 资源文件规范

4.1 资源布局文件(XML文件(layout布局文件))

全部小写,采用下划线命名法

4.1.1 contentView命名

必须以全部单词小写,单词间以下划线分割,使用名词或名词词组。

所有Activity或Fragment的contentView必须与其类名对应,对应规则为:将所有字母都转为小写,将类型和功能调换(也就是后缀变前缀)。

例如:activity_main.xml

4.1.2 Dialog命名

规则:dialog_描述.xml

例如:dialog_hint.xml

4.1.3 PopupWindow命名

规则:ppw_描述.xml

例如:ppw_info.xml

4.1.4 列表项命名

规则:item_描述.xml

例如:item_city.xml

4.1.5 包含项命名

规则:模块_(位置)描述.xml

例如:activity_main_head.xmlactivity_main_bottom.xml

注意:通用的包含项命名采用:项目名称缩写_描述.xml

例如:xxxx_title.xml

4.2 资源文件(图片drawable文件夹下)

全部小写,采用下划线命名法,加前缀区分

命名模式:可加后缀 _small 表示小图, _big 表示大图,逻辑名称可由多个单词加下划线组成,采用以下规则:

  • 用途_模块名_逻辑名称
  • 用途_模块名_颜色
  • 用途_逻辑名称
  • 用途_颜色

说明:用途也指控件类型(具体见附录UI控件缩写表

例如:

名称 说明
btn_main_home.png 按键用途_模块名_逻辑名称
divider_maket_white.png 分割线用途_模块名_颜色
ic_edit.png 图标用途_逻辑名称
bg_main.png 背景用途_逻辑名称
btn_red.png 红色按键用途_颜色
btn_red_big.png 红色大按键用途_颜色
ic_head_small.png 小头像用途_逻辑名称
bg_input.png 输入框背景用途_逻辑名称
divider_white.png 白色分割线用途_颜色
bg_main_head 主模块头部背景图片用途_模块名_逻辑名称
def_search_cell 默认搜索界面单元图片用途_模块名_逻辑名称
ic_more_help 更多帮助图标用途_逻辑名称
divider_list_line 列表分割线用途_逻辑名称
sel_search_ok 搜索界面确认选择器用途_模块名_逻辑名称
shape_music_ring 音乐界面环形形状用途_模块名_逻辑名称

如果有多种形态,如按钮选择器:sel_btn_xx.xml

名称 说明
sel_btn_xx 按钮图片使用btn_整体效果(selector)
btn_xx_normal 按钮图片使用btn_正常情况效果
btn_xx_pressed 按钮图片使用btn_点击时候效果
btn_xx_focused state_focused聚焦效果
btn_xx_disabled state_enabled (false)不可用效果
btn_xx_checked state_checked选中效果
btn_xx_selected state_selected选中效果
btn_xx_hovered state_hovered悬停效果
btn_xx_checkable state_checkable可选效果
btn_xx_activated state_activated激活的
btn_xx_windowfocused state_window_focused

注意:使用AndroidStudio的插件SelectorChapek可以快速生成selector,前提是命名要规范。

4.3 动画文件(anim文件夹下)

全部小写,采用下划线命名法,加前缀区分。

具体动画采用以下规则:模块名_逻辑名称

例如:refresh_progress.xmlmarket_cart_add.xmlmarket_cart_remove.xml

普通的tween动画采用如下表格中的命名方式:动画类型_方向

名称 说明
fade_in 淡入
fade_out 淡出
push_down_in 从下方推入
push_down_out 从下方推出
push_left 推向左方
slide_in_from_top 从头部滑动进入
zoom_enter 变形进入
slide_in 滑动进入
shrink_to_middle 中间缩小

4.4 values中name命名

4.4.1 colors.xml

colors.xmlname命名使用下划线命名法,在你的colors.xml文件中应该只是映射颜色的名称一个ARGB值,而没有其它的。不要使用它为不同的按钮来定义ARGB值。

不要这样做

1
2
3
4
5
6
7
8
9
<resources>
<color name="button_foreground">#FFFFFF</color>
<color name="button_background">#2A91BD</color>
<color name="comment_background_inactive">#5F5F5F</color>
<color name="comment_background_active">#939393</color>
<color name="comment_foreground">#FFFFFF</color>
<color name="comment_foreground_important">#FF9D2F</color>
...
<color name="comment_shadow">#323232</color>

使用这种格式,你会非常容易的开始重复定义ARGB值,这使如果需要改变基本色变的很复杂。同时,这些定义是跟一些环境关联起来的,如button或者comment, 应该放到一个按钮风格中,而不是在colors.xml文件中。

相反,这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resources>

<!-- grayscale -->
<color name="white" >#FFFFFF</color>
<color name="gray_light">#DBDBDB</color>
<color name="gray" >#939393</color>
<color name="gray_dark" >#5F5F5F</color>
<color name="black" >#323232</color>

<!-- basic colors -->
<color name="green">#27D34D</color>
<color name="blue">#2A91BD</color>
<color name="orange">#FF9D2F</color>
<color name="red">#FF432F</color>

</resources>

向应用设计者那里要这个调色板,名称不需要跟"green""blue"等等相同。"brand_primary""brand_secondary""brand_negative"这样的名字也是完全可以接受的。 像这样规范的颜色很容易修改或重构,会使应用一共使用了多少种不同的颜色变得非常清晰。 通常一个具有审美价值的UI来说,减少使用颜色的种类是非常重要的。

注意:如果某些颜色和主题有关,那就单独写一个colors_theme.xml

4.4.2 dimens.xml

像对待colors.xml一样对待dimens.xml文件 与定义颜色调色板一样,你同时也应该定义一个空隙间隔和字体大小的“调色板”。 一个好的例子,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<resources>

<!-- font sizes -->
<dimen name="font_22">22sp</dimen>
<dimen name="font_18">18sp</dimen>
<dimen name="font_15">15sp</dimen>
<dimen name="font_12">12sp</dimen>

<!-- typical spacing between two views -->
<dimen name="spacing_40">40dp</dimen>
<dimen name="spacing_24">24dp</dimen>
<dimen name="spacing_14">14dp</dimen>
<dimen name="spacing_10">10dp</dimen>
<dimen name="spacing_4">4dp</dimen>

<!-- typical sizes of views -->
<dimen name="button_height_60">60dp</dimen>
<dimen name="button_height_40">40dp</dimen>
<dimen name="button_height_32">32dp</dimen>

</resources>

布局时在写marginspaddings时,你应该使用spacing_xx尺寸格式来布局,而不是像对待string字符串一样直接写值,像这样规范的尺寸很容易修改或重构,会使应用所有用到的尺寸一目了然。 这样写会非常有感觉,会使组织和改变风格或布局是非常容易。

4.4.3 strings.xml

stringsname命名使用下划线命名法,采用以下规则:模块名+逻辑名称,这样方便同一个界面的所有string都放到一起,方便查找。

名称 说明
main_menu_about 主菜单按键文字
friend_title 好友模块标题栏
friend_dialog_del 好友删除提示
login_check_email 登录验证
dialog_title 弹出框标题
button_ok 确认键
loading 加载文字
4.4.4 styles.xml

stylename命名使用大驼峰命名法,几乎每个项目都需要适当的使用style文件,因为对于一个视图来说有一个重复的外观是很常见的,将所有的外观细节属性(colorspaddingfont)放在style文件中。 在应用中对于大多数文本内容,最起码你应该有一个通用的style文件,例如:

1
2
3
4
<style name="ContentText">
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:textColor">@color/basic_black</item>
</style>

应用到TextView中:

1
2
3
4
5
6
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/price"
style="@style/ContentText"
/>

你或许需要为按钮控件做同样的事情,不要停止在那里。将一组相关的和重复android:****的属性放到一个通用的style中。

将一个大的styles.xml文件分割成多个文件, 你可以有多个styles.xml 文件。Android SDK支持其它文件,styles.xml这个文件名称并没有作用,起作用的是在文件里的<style>标签。因此你可以有多个style文件,如styles.xmlstyles_home.xmlstyles_item_details.xmlstyles_forms.xml。 不同于资源文件路径需要为系统构建起的有意义,在res/values目录下的文件可以任意命名。

4.5 layout中的id命名

命名模式为:view缩写_模块名_逻辑名,比如btn_main_search
使用AndroidStudio的插件ButterKnife Zelezny,生成注解非常方便,原生的话可以使用Android Code Generator插件。

如果想对资源文件进行分包可以参考我这篇文章~ Android Studio下对资源进行分包

5 版本统一规范

Android开发存在着众多版本的不同,比如compileSdkVersionminSdkVersiontargetSdkVersion以及项目中依赖第三方库的版本,不同的module及不同的开发人员都有不同的版本,所以需要一个统一版本规范的文件。

具体可以参考我写的这篇博文~ Android开发之版本统一规范

6 第三方库规范

别再闭门造车了,用用最新最火的技术吧,安利一波~ Android 流行框架查速表,顺便带上自己的干货~ Android开发人员不得不收集的代码

希望Team能用时下较新的技术,对开源库的选取,一般都需要选择比较稳定的版本,作者在维护的项目,要考虑作者对issue的解决,以及开发者的知名度等各方面。选取之后,一定的封装是必要的。

个人推荐Team可使用如下优秀轮子:

7 注释规范

为了减少他人阅读你代码的痛苦值,请在关键地方做好注释。

7.1 类注释

每个类完成后应该有作者姓名和联系方式的注释,对自己的代码负责。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* <pre>
* author : Blankj
* e-mail : xxx@xx
* time : 2017/03/07
* desc : xxxx描述
* version: 1.0
* </pre>
*/

public class WelcomeActivity {
...
}

具体可以在AS中自己配制,Settings → Editor → File and Code Templates → Includes → File Header,输入

1
2
3
4
5
6
7
8
9
/**
* <pre>
* author : ${USER}
* e-mail : xxx@xx
* time : ${YEAR}/${MONTH}/${DAY}
* desc :
* version: 1.0
* </pre>
*/

这样便可在每次新建类的时候自动加上该头注释。

7.2 方法注释

每一个成员方法(包括自定义成员方法、覆盖方法、属性方法)的方法头都必须做方法头注释,在方法前一行输入/** + 回车或者设置Fix doc comment(Settings → Keymap → Fix doc comment)快捷键,AS便会帮你生成模板,我们只需要补全参数即可,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* bitmap转byteArr
*
* @param bitmap bitmap对象
* @param format 格式
* @return 字节数组
*/

public static byte[] bitmap2Bytes(Bitmap bitmap, CompressFormat format) {
if (bitmap == null) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(format, 100, baos);
return baos.toByteArray();
}

7.3 块注释

块注释与其周围的代码在同一缩进级别。它们可以是/* ... */风格,也可以是// ...风格(//后最好带一个空格)。对于多行的/* ... */注释,后续行必须从*开始, 并且与前一行的*对齐。以下示例注释都是OK的。

1
2
3
4
/*
* This is // And so /* Or you can
* okay. // is this. * even do this. */

*/

注释不要封闭在由星号或其它字符绘制的框架里。

Tip:在写多行注释时,如果你希望在必要时能重新换行(即注释像段落风格一样),那么使用/* ... */

7.4 其他一些注释

AS已帮你集成了一些注释模板,我们只需要直接使用即可,在代码中输入todofixme等这些注释模板,回车后便会出现如下注释

1
2
// TODO: 17/3/14 需要实现,但目前还未实现的功能的说明
// FIXME: 17/3/14 需要修正,甚至代码是错误的,不能工作,需要修复的说明

8 测试规范

业务开发完成之后,开发人员做单元测试,单元测试完成之后,保证单元测试全部通过同时单元测试代码覆盖率达到一定程度(这个需要开发和测试约定,理论上越高越好),开发提测。

// TODO…

9 RN规范

// TODO…

10 其他的一些规范

  1. 合理布局,有效运用<merge><ViewStub><include>标签;
  2. ActivityFragment里面有许多重复的操作以及操作步骤,所以我们都需要提供一个BaseActivityBaseFragment,让所有的ActivityFragment都继承这个基类。
  3. 方法基本上都按照调用的先后顺序在各自区块中排列;
  4. 相关功能作为小区块放在一起(或者封装掉);
  5. 当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函数/方法;
  6. 数据提供统一的入口。无论是在 MVP、MVC 还是 MVVM 中,提供一个统一的数据入口,都可以让代码变得更加易于维护。比如可使用一个DataManager,把 httppreferenceeventpostdatabase 都放在DataManger里面进行操作,我们只需要与DataManger打交道;
  7. 多用组合, 少用继承;
  8. 提取方法, 去除重复代码。对于必要的工具类抽取也很重要,这在以后的项目中是可以重用的。
  9. 可引入 Dagger2 减少模块之间的耦合性。Dagger2 是一个依赖注入框架,使用代码自动生成创建依赖关系需要的代码。减少很多模板化的代码,更易于测试,降低耦合,创建可复用可互换的模块;
  10. 项目引入RxJava + RxAndroid这些响应式编程,可以极大的减少逻辑代码;
  11. 通过引入事件总线,如:EventBusAndroidEventBusRxBus,它允许我们在DataLayer中发送事件,以便ViewLayer中的多个组件都能够订阅到这些事件,减少回调;
  12. 尽可能使用局部变量;
  13. 及时关闭流;
  14. 尽量减少对变量的重复计算;

    如下面的操作:

    1
    2
    3
    for (int i = 0; i < list.size(); i++) {
    ...
    }

    建议替换为:

    1
    2
    3
    for (int i = 0, int length = list.size(); i < length; i++) {
    ...
    }
  15. 尽量采用懒加载的策略,即在需要的时候才创建;

    例如:

    1
    2
    3
    4
    String str = "aaa";
    if (i == 1) {
    list.add(str);
    }

    建议替换为:

    1
    2
    3
    4
    if (i == 1) {
    String str = "aaa";
    list.add(str);
    }
  16. 不要在循环中使用try…catch…,应该把其放在最外层;

  17. 使用带缓冲的输入输出流进行IO操作;
  18. 尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销;
  19. 尽量在合适的场合使用单例;

    使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

    (1)控制资源的使用,通过线程同步来控制资源的并发访问

    (2)控制实例的产生,以达到节约资源的目的

    (3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信

  20. 把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据 + ""最慢;

  21. 使用AS自带的Lint来优化代码结构(什么,你不会?右键module、目录或者文件,选择Analyze → Inspect Code);
  22. 最后不要忘了内存泄漏的检测;

最后啰嗦几句:

  • 好的命名规则能够提高代码质量,使得新人加入项目的时候降低理解代码的难度;
  • 规矩终究是死的,适合团队的才是最好的;
  • 命名规范需要团队一起齐心协力来维护执行,在团队生活里,谁都不可能独善其身;
  • 一开始可能会有些不习惯,持之以恒,总会成功的。

附录

UI控件缩写表

名称 缩写
TextView tv
EditText et
ImageButton ib
Button btn
ImageView iv
ListView lv
GridView gv
ProgressBar pb
SeekBar sb
RadioButtion rb
CheckBox cb
ScrollView sv
LinearLayout ll
FrameLayout fl
RelativeLayout rl
RecyclerView rv
WebView wv
VideoView vv
Spinner spn
ToggleButton tb

常见的英文单词缩写表

名称 缩写
icon ic (主要用在app的图标)
color cl(主要用于颜色值)
average avg
background bg(主要用于布局和子布局的背景)
selector sel主要用于某一view多种状态,不仅包括Listview中的selector,还包括按钮的selector)
buffer buf
control ctrl
default def
delete del
document doc
error err
escape esc
increment inc
infomation info
initial init
image img
Internationalization I18N
length len
library lib
message msg
password pwd
position pos
server srv
string str
temp tmp
window wnd(win)

程序中使用单词缩写原则:不要用缩写,除非该缩写是约定俗成的。

参考

Android包命名规范

Android 开发最佳实践

Android 编码规范

阿里巴巴Java开发手册

Google Java编程风格指南

小细节,大用途,35 个 Java 代码性能优化总结!

版本日志

  • 17/03/06: 发布初版 ;
  • 17/03/07: 修订目录排版,完善某些细节;
  • 17/03/08: 规范排版,修复typor及新增一些规范;
  • 17/03/13: 新增其他注释;
  • 17/03/14: 包名划分为按功能划分;

               ——powered by Blankj(蔡梦杰)

关于gson 解析不规范json

{“data”:{“sign”:”2113fe846f348d1b07263573eaa1c530”,”result”:”{\”eaccountId\”:41,\”sex\”:\”1\”,\”contactTel\”:\”16585854282\”,\”merchantProvince\”:\”福建省\”,\”city\”:null,\”bankCount\”:1,\”balance\”:113,\”area\”:null,\”is_finger_pay\”:\”0\”,\”token\”:\”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0SWQiOiI0MSIsInVzZXJJZCI6IjQxIiwiaWF0IjoxNTAwOTY4OTA2NDU4LCJjdXN0VHlwZSI6IjAifQ.HXkuHO_OrG4LbOAILFv6eJA8T4VepKJCeqT6YtRiwbE\”,\”nickName\”:\”守望\”,\”merchantType\”:\”0\”,\”province\”:\”北京 北京市 西城区\”,\”merchantName\”:\”莆田卤面\”,\”alipayAccount\”:\”18060031709\”,\”custId\”:41,\”merchantLogo\”:\”header/758f2ffa-f057-474f-b0c4-57a703a8dfa0.jpg\”,\”is_open_pay_code\”:\”0\”,\”avatar\”:\”header/e5be6d17-c27d-4db5-8fe6-d20cf89e5598.jpg\”,\”isOpenEcash\”:\”1\”,\”merchantArea\”:\”思明区\”,\”isAuth\”:\”1\”,\”email\”:\”1432967264@qq.com\”,\”contactName\”:\”林国森\”,\”contactType\”:\”00\”,\”is_free_pay\”:0,\”realName\”:\”林国森\”,\”merchantCity\”:\”厦门市\”,\”is_set_pay_pwd\”:1,\”mobile\”:18060031709}”},”description”:”服务处理成功”,”status”:”success

如上json
应该说对于对象必须是不加双引号的,如果加上双引号会识别为字符串。
那也只能按照字符串去解析
``java
package com.npay.merchant.retrofit.response;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.npay.merchant.bean.User;

import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Type;

/**

  • Created by guosenlin on 2017/7/25.
    */
    public class UserData {
    String sign;
    String result;

    public T getResult(Class clazz){

    try {
        JSONObject jsonObject = new JSONObject(result);
        Type fooType = new TypeToken< T>() {}.getType();
         return new Gson().fromJson(result, clazz);
    } catch (JSONException e) {
        e.printStackTrace();
    }
    return null;
    

    }

    //一般来说没必要在这边再继续序列化一次 但是由于 这个后端的json格式有问题多了””(嵌套对象)

}

``

``java
package com.npay.merchant.retrofit.response;

import com.google.gson.Gson;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**

  • Created by guosenlin on 2017/6/23.
    */

public class HttpResponse {

public UserData<T> data;

public String description;

public String status;


public static HttpResponse fromJson(String json, Class clazz) {
    Gson gson = new Gson();
    Type objectType = type(HttpResponse.class, clazz);
    return gson.fromJson(json, objectType);
}

public String toJson(Class<T> clazz) {
    Gson gson = new Gson();
    Type objectType = type(HttpResponse.class, clazz);
    return gson.toJson(this, objectType);
}

static ParameterizedType type(final Class raw, final Type... args) {
    return new ParameterizedType() {
        public Type getRawType() {
            return raw;
        }

        public Type[] getActualTypeArguments() {
            return args;
        }

        public Type getOwnerType() {
            return null;
        }
    };
}

}

private class HttpResultFunc<T> implements Func1<T, T> {

    @Override
    public T call(T httpResult) {
        if (httpResult  == null) {
            throw new ApiException(100);
        }
        return httpResult;
    }
}

/***
 * 用户登录
 * @param subscriber
 * @param phone
 * @param pwd
 * @param custType
 */
public void accountLogin(Subscriber<HttpResponse<User>> subscriber, String phone, String pwd, int custType ){
    Observable observable = mApiService.login(phone,pwd,custType)
            .map(new HttpResultFunc<HttpResponse<User>>());
    toSubscribe(observable, subscriber);
}

``

采用Hexo+github搭建属于自己的博客

1.Hexo介绍

Hexo是一个简单的静态博客框架。采用命令的方式,我们可以简单的创建属于我们自己的博客。

2.Hexo安装

这边主要介绍在Mac环境下的安装和配置
采用命令 npm install -g hexo
# 3.创建项目

hexo init nodejs-hexo 然后启动服务器是:hexo server

浏览地址是:http://localhost:4000/

生成静态页面cd 到你的init目录,执行如下命令,生成静态页面至hexo\public\目录。hexo generate (hexo g 也可以)

# 配置Github

建立与用户名对应的仓库必须是[your_user_name.git.io],

修改根目录下的_config.yml文件最底下的deploy: type: git repository:https://github.com/guosen/guosen.github.io.git
branch:master 注意:1、冒号跟后面的内容要有空格 2、第二行的type跟左边也要有空格否则出现YMLException.

部署

就是部署到Github的相应分支,先hexo clean 再生成静态网页hexo generate,然后hexo deploy部署。

更换主题

替换掉默认主题,首先下载一个主题采用命令:git clone https://github.com/daisygao/hexo-themes-cover.git themes/cove

然后修改 _config.yml文件。再部署OK!

遇到的问题:fatal:multiple stage entries for merged file处理办法
删掉git下的index文件。

参考链接:https://hexo.io/docs/troubleshooting.html
http://blog.fens.me/hexo-blog-github/

为什么写博客 ?

原因

作为一个技术人员,技术的成长是非常重要的,然而自己每天在工作忙碌着而没有去注意到自己是否在上升,自己所到达的一个层次,如果能把一些遇到的问题和自己解决的办法等等清晰的记录下来,一个是留到以后回过来看更佳方便;其次,可以提高自己的表达能力,还有就是能把一些东西用文章的形式表达下来,说明自己已经对这个已经比较清楚的理解。

写什么呢

主要就是技术文章,然后随想,吐槽等等。

Android Glide/Fresco/Picasso

#基本信息
Picasso 是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知。
Glide 是 Google 员工的开源项目,被一些 Google App 使用,在去年的 Google I/O 上被推荐,不过目前国内资料不多。

Fresco 是 Facebook 在今年上半年开源的图片缓存,主要特点包括:
(1) 两个内存缓存加上 Native 缓存构成了三级缓存
(2) 支持流式,可以类似网页上模糊渐进式显示图片
(3) 对多帧动画图片支持更好,如 Gif、WebP

#基本概念
在正式对比前,先了解几个图片缓存通用的概念:
(1) RequestManager:请求生成和管理模块
(2) Engine:引擎部分,负责创建任务(获取数据),并调度执行
(3) GetDataInterface:数据获取接口,负责从各个数据源获取数据。
比如 MemoryCache 从内存缓存获取数据、DiskCache 从本地缓存获取数据,下载器从网络获取数据等。
(4) Displayer:资源(图片)显示器,用于显示或操作资源。
比如 ImageView,这几个图片缓存都不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。
(5) Processor 资源(图片)处理器
负责处理资源,比如旋转、压缩、截取等。
以上概念的称呼在不同图片缓存中可能不同,比如 Displayer 在 ImageLoader 中叫做 ImageAware,在 Picasso 和 Glide 中叫做 Target。

#共同优点

  1. 使用简单
    都可以通过一句代码可实现图片获取和显示。
  2. 可配置度高,自适应程度高
    图片缓存的下载器(重试机制)、解码器、显示器、处理器、内存缓存、本地缓存、线程池、缓存算法等大都可轻松配置。
    自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。
    比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。
  3. 多级缓存
    都至少有两级缓存、提高图片加载速度。
  4. 支持多种数据源
    支持多种数据源,网络、本地、资源、Assets 等
  5. 支持多种 Displayer
    不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。
    其他小的共同点包括支持动画、支持 transform 处理、获取 EXIF 信息等。

#Picasso 设计及优点

Dispatcher 负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。
简单的讲就是 Picasso 收到加载及显示图片的任务,创建 Request 并将它交给 Dispatcher,Dispatcher 分发任务到具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中。
需要注意的是上面 Data 的 File system 部分,Picasso 没有自定义本地缓存的接口,默认使用 http 的本地缓存,API 9 以上使用 okhttp,以下使用 Urlconnection,所以如果需要自定义本地缓存就需要重定义 Downloader。

  1. Picasso 优点
    (1) 自带统计监控功能
    支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
    (2) 支持优先级处理
    每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
    (3) 支持延迟到图片尺寸计算完成加载
    (4) 支持飞行模式、并发线程数根据网络类型而变
    手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2。
    这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。
    (5) “无”本地缓存
    无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。

六、Glide 设计及优点

  1. 总体设计及流程
    上面是 Glide 的总体设计图。整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、 Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。
    简单的讲就是 Glide 收到加载及显示资源的任务,创建 Request 并将它交给 RequestManager,Request 启动 Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target。
    Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作。

  2. Glide 优点
    (1) 图片缓存->媒体缓存
    Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。
    (2) 支持优先级处理
    (3) 与 Activity/Fragment 生命周期一致,支持 trimMemory
    Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
    (4) 支持 okhttp、Volley
    Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
    (5) 内存友好
    ① Glide 的内存缓存有个 active 的设计
    从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。
    ② 内存缓存更小图片
    Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小
    ③ 与 Activity/Fragment 生命周期一致,支持 trimMemory
    ④ 图片默认使用默认 RGB_565 而不是 ARGB_888
    虽然清晰度差些,但图片更小,也可配置到 ARGB_888。
    其他:Glide 可以通过 signature 或不使用本地缓存支持 url 过期

Android 手势监听

#ScaleGestureDetector
缩放事件,

@Override
1
2
3
4
5
6
7
public boolean onTouchEvent(MotionEvent event) {
boolean scaleEvent = mScaleGestureDetector.onTouchEvent(event);//缩放事件
boolean gestureEvent = mGestureDetector.onTouchEvent(event);//处理一般事件
//如果没有焦点的状态应该交给父类,缩放背景。
//二维码被选中时缩放就缩放二维码 背景不改变。
return !isFocusState()?false:scaleEvent || gestureEvent;
}

对于手势监听,只有当onTouch 返回为True的情况下,才会执行,(也就是说event.getPonterCount()返回才会大于1)

#关于这个类的用法:

mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    @Override
public boolean onScale(ScaleGestureDetector detector) {
if (isFocusState()) {
float scaleFactor = mScaleFactor * detector.getScaleFactor();
float distance = calculateDistance(mImageCenterCorners[0], mImageCenterCorners[1], detector.getFocusX(), detector.getFocusY());
float scale = distance / mOldDistance;
float[] currentCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);
mTempMatrix.reset();
mTempMatrix.postScale(scaleFactor, scaleFactor, mImageCenterCorners[0], mImageCenterCorners[1]);
mTempMatrix.mapPoints(currentCorners); //限制缩放不能小于原始尺寸四分之一//
float currentSize = calculateDistance(currentCorners[0], currentCorners[1], currentCorners[2], currentCorners[3]);
if (currentSize >= mMinWidth) {
mCurrentImageCorners = currentCorners;
mOldScale = scale;
}
invalidate();
return true;
}
return false;
}
});

#GestureDetector
一般手势监听(包括单击、滑动、触摸)

#关于这类的用法

mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
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
        @Override
public boolean onDown(MotionEvent event) {
float x = event.getX();
float y = event.getY();
mTouchDownX = x;
mTouchDownY = y;
if (isFocusState()) {
if (mBtnEditDrawable != null && mBtnEditDrawable.getBounds().contains((int) x, (int) y)) {
mTouchBtnType = TOUCH_BTN_EDIT;
return true;
} else if (mBtnRotationDrawable != null && mBtnRotationDrawable.getBounds().contains((int) x, (int) y)) {
mTouchBtnType = TOUCH_BTN_CONTROLL;
mOldDistance = calculateDistance(mImageCenterCorners[0], mImageCenterCorners[1], x, y);
mOldScale = 1;
return true;
} else if (mBtnDelDrawable != null && mBtnDelDrawable.getBounds().contains((int) x, (int) y)) {
mTouchBtnType = TOUCH_BTN_DELETE;
return true;
}
}
mTouchBtnType = TOUCH_NO_BTN;
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//放大//拖动
if (mTouchBtnType == StickerView.TOUCH_BTN_CONTROLL) {
Log.d(TAG, "processScale");
processScale(e2.getX(), e2.getY());
invalidate();
return true;
}
return ((MarkLayout) getParent()).processScrollEvent(e1, e2, distanceX, distanceY);
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
//触摸
//删除
if (mTouchBtnType == StickerView.TOUCH_BTN_DELETE
&& (mTouchDownX == event.getX() && mTouchDownY == event.getY())) {
delete();
return true;
}
//编辑
if (mTouchBtnType == TOUCH_BTN_EDIT
&& (mTouchDownX == event.getX() && mTouchDownY == event.getY())) {
editTDCode();
return true;
}
return ((MarkLayout) getParent()).processSingleTapUpEvent(event);
}
});
}