周精益分享 - 英语

参与开源提高英语读写 - 王胜

阅读能力

  • 泡开源论坛,查看讨论
  • 阅读源代码包括注解、代码提交说明

前期泡论坛,看注解、提交说明时需耐下性子,借助翻译工具。时间长了,久而久之,词汇量就上升了。

书写能力

  • 试图修复bug或者提交建议
  • 尝试翻译开源软件的使用手册

经历了阅读阶段的词汇量的积累,可以再通过书写来巩固词汇量的运用。此阶段要拥有儿童学语言的勇气,不要担心自己写错,被其他参与开源的人看到笑话。

MOOC 上手 - 曾铭

Massive Open Online Courses 大型开放式网络课程

  • Coursera
  • Udacity
  • edX
  • 中国大学MOOC

上手感受

  • 视频教程 vs 正规课程
  • 视频教程:目标不清晰、散漫不专注、孤单、没有回顾总结,易放弃(遗忘、注意力转移)
  • 正规课程:目标清晰(结课证)、专注(每周测验)、作业互评、论坛讨论,易放弃(压力)
  • 专业教育的未来(非基本教育)

建议

  • 根据兴趣上手,不必贪多,先坚持完成 1 门课(4-6h/w 并不容易)
  • 只对视频教程感兴趣?为网易公开课点个赞

dispatch_sync的坑 - 潘君

  • 官方文档

    1
    2
    3
    4
    5
    6
    dispatch_sync
    Submits a block object for execution on a dispatch queue and waits until that block completes.

    。。。。。

    As an optimization, this function invokes the block on the current thread when possible.
  • dispatch_sync 卡页面, 原因见官方说明最后一句,优化搞的鬼

    1
    (删除操作) dispatch_sync
  • 队列分为两种,一种是Serial Dispatch Queue,还有一种是Concurrent Dispatch Queue。

  • xun核心模块决定线程数

参考资料:

英语,程序员

网站

  • iTalki

    语言交换及专业老师订课辅导的网站

  • YouTube一个订阅号

    比较有吸引力的老师,他的同事频道也很不错

  • rayWenderlich

    主要是iOS方面的,安卓覆盖一些。涵盖文字、视频、播客三方面

APP

  • 飞鱼口语

    国内开发的一个及时练习口语的APP,主要是方便

  • 每日英语听力(欧陆词典推出的)

    radio 模块很不错,资源不算多但比较有质量。

  • Podcast

    • 圆桌会议(RoundTable)
    • rayWenderlich (程序员职业发展及新技术点探讨,英式口语)

工具

  • Skype 和志同道合的人交流,练习口语

EF Education

  • 网站地址

    想小试牛刀的记得想我拿一下账号密码

周精益分享-谈谈未来、梦想、职业规划

实现时间自由 - 王胜

  • 近期技术打工、为公司技术出一份力
  • 伺机合伙创业
    • 自己作为技术的Partner
    • 需求靠谱的产品经理合伙创业
    • 如果有靠谱的市场/资方人事的合伙人就更完美了
  • 财富自由和时间自由后
    • 周游开拓视野
    • 做一些自己感兴趣的东西,不求有市场,只求自己感觉有价值,好玩就行

品质生活 - 吴明

  • 短期目标
    • 学习和开发使用Material Design
  • 长期目标:以下目标之一
    • 技术专家
    • 管理和技术
    • 换行业
  • 第二职业
    • 除工作之外培养第二职业
    • 第二职业主要兴趣爱好方向
    • 不求工资和市场价值高低,只追求兴趣快乐

兴趣主导,快乐很重要 - 杨志平

  • 兴趣

    • 在失去当前工作兴趣之前,不会转行
    • 职业规划随兴趣走
  • 肉身翻墙

    • 出去走一遭,趁年轻(工作或打工旅行)
    • 去牛逼公司镀金
  • 终极目标

    • 年轻时,向往自由工作者的工作方式,能够养活自己的游行工作方式
    • 适当年纪收心回归平常心(保持感兴趣的工作即可)

一步一个脚印,顺其自然-王进

  • 近期目标,一步一个脚印,不断提高自己

    • 做好现在工作,打下坚实的基础
    • 专注于技术,近期不考虑创业,转行等
    • 顺其自然,当未来出现创业等机会,自己也准备好了,也不错过
  • 未来愿景:

    • 看好移动端的未来发展,以会后向智能家居,物联网方向发展
    • 最终希望能在未来能有时间与机会出去走走

周精益分享-设计规范交流

Label的一些使用规范 - 杨志平

动态适应字体大小

iOS系统自带的常见字体样式(字号及粗细等等)

  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

效果如下:

Font

代码

1
2
3
4
5
6
7
8
// 字体初始化
UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

// 通知需要刷新的文本字体
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(userTextSizeDidChange)
name:UIContentSizeCategoryDidChangeNotification
object:nil];

国际化支持(适配注意点)

工具:genstrings

参考:博客链接

1
2
3
先看看它的宏定义:
#define NSLocalizedString(key, comment)
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

本地化对象:标签和按钮上的文本,或者在运行时获取的字符串和数据动态生成的字符串。

难点

不同语言的语法问题

语序,人称不一致:
「Paul invited you」和「You invited Paul」 -> 「%@ invited %@」,看似可以
以德语为例,「Paul invited you」译为「Paul hat dich eingeladen」,
而「You invited Paul」则译为「Du hast Paul eingeladen」。
正确处理其他语言的特殊语法方案:「%@ invited you」和「You invited %@」。

一词多意

1
2
3
4
5
6
7
8
run -> 
vt. & vi. 跑

移动
(使)流动
n. 跑, 奔跑
旅行, 旅程
行驶路线
时期; 一段时间

正确做法:

1
2
NSLocalizedString(@"activity-profile.title.the-run", nil)  
NSLocalizedString(@"home.button.start-run", nil)

或者:(完全没有试过)

NSLocalizedString 有一些变体能够提供更多字符串本地化的操作方式。NSLocalizedStringFromTable 接收 key、table 和 comment 这三个参数,其中 table 参数表示该字符串对应的一个表格,genstrings 会为表中的每一个条目生成一个以条目名称(假设为 table-item)命名的独立字符串文件 table-item.strings。
这样你就可以把字符串文件分割成几个小一些的文件。在一个庞大的项目或者团队中工作时,这一点显得尤为重要。同时这也让合并原有的和重新生成的字符串文件变得容易一些。

1
NSLocalizedStringFromTable(@"home.button.start-run", @"ActivityTracker", @"some comment..")

调试本地化字符串(拓展)

应用支持的语言版本越多,确保所有元素都正确显示就越难。但是这里有一些默认的用户选项和工具可以减轻你的负担。

NSDoubleLocalizedStrings
AppleTextDirection
NSForceRightToLeftWritingDirection
选项保证你的布局不会因为长字符串或者从右往左读的语言而混乱。

NSShowNonLocalizedStrings
NSShowNonLocalizableStrings
则可以帮助你找到没有翻译的字符串和根本没有制定字符串本地化宏的字符串。

单位和度量 -吴明

Android基本单位规范 -吴明

单位和度量
  • 像素密度
    • 每英寸的像素数被称为“像素密度”。
    • DPI =屏幕宽度(或高度)像素/屏幕宽度(或高度)英寸
    • mdpi-160dpi,
      hdpi-480x800-240dpi,
      xhdpi-720x1280-320dpi,
      xxhdpi-1080x1920-480dpi
    • mipmap-hdpi,
      drawable-hdpi区别:用mipmap会缩放图片并优化性能。
    • MacDown Screenshot
  • 密度独立像素
    • “密度独立”是指在屏幕上用不同的密度来统一的显示用户界面元素
    • MacDown Screenshot
    • 1dp 和 160 dpi 屏幕的一个物理像素相等。计算 dp 的方法:
      dp =(宽度像素160)/ dpi**
  • 可扩展像素(sp)

    • 在为安卓系统开发程序时,可扩展的像素(SP)提供和DP一样的功能,但只是在字体上。一个 SP 的默认值和 DP 上的默认值一样。
    • density==scaledDensity,即1sp=1dp,
    • 还是采用dp替代sp,为什么

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      	public static float applyDimension(int unit, float value,DisplayMetrics metrics)
      {

      switch (unit) {
      case COMPLEX_UNIT_PX:
      return value;
      case COMPLEX_UNIT_DIP:
      return value * metrics.density;
      case COMPLEX_UNIT_SP:
      return value * metrics.scaledDensity;
      case COMPLEX_UNIT_PT:
      return value * metrics.xdpi * (1.0f/72);
      case COMPLEX_UNIT_IN:
      return value * metrics.xdpi;
      case COMPLEX_UNIT_MM:
      return value * metrics.xdpi * (1.0f/25.4f);
      }
      return 0;
      }
  • pt(磅),标准的长度单位,1pt=1/72英寸,用于印刷业,买蛋糕的时候挺说最多

  • em,em指字体高,1em=16px,任意浏览器的默认字体高都是16px.
字体排版
  • 字体排版的缩放和基本样式
    • 同时使用过多的字体尺寸和样式可以很轻易的毁掉布局,最基本的样式集合就是基于 12、14、16、20 和 34 号的字体排版缩放,以下Google建议字体尺寸:
      MacDown Screenshot
  • 基本色/色彩对比度
    • 最基本的常识是,相同颜色的背景和文字是很难阅读的。但有些人不知道的是,带有过强对比度的文本会有些炫目,同样难以阅读。
    • 文本应当满足对比度7:1的是最适合阅读,最低的对比度4.5:1(依据明度计算),
    • Google对比度建议:MacDown Screenshot
  • 行高
    • 只有“主体”、“次要标题”、“大纲”等类似的样式中才允许使用自动换行。其它所有样式应当以单行形式出现。
    • MacDown Screenshot
    • For all styles, line height is 0.1em larger than the English-like languages. English and English-like languages mostly use a portion of the em box, often the lower portion below the x-height. Chinese, Japanese, and Korean (CJK) ideographic characters use the entire em box. Characters in tall languages often have long descenders and/or ascenders. To achieve the same design intention as English for CJK and to avoid potential text clipping between two lines next to each other for tall languages, the line height needs to be larger than in English for tall and dense languages.
  • 换行规则/连字符
    • 正确:
      MacDown Screenshot
    • 错误
      MacDown Screenshot
  • 每行字符

    • 每行应当保持在50-60个字符左右
    • 太长,用户的眼睛将难以找到在文本上对焦。这是因为过长的文字导致用户难以判断一行的起始点,甚至在大段文字中出现读错行的现象
    • 太短,会导致眼睛来回扫视过于频繁,破坏阅读的节奏。过短的内容还会给人压力,导致用户完成本行阅读前过早跳转到下一行阅读(因此会错过潜在的重要信息)
    • MacDown Screenshot

      • MacDown Screenshot
  • 字间距
    • MacDown Screenshot

设计规范之我见 - 张超耀

设计规范阅读心得 - 潘君

官方设计文档

参考资料:[ISUX转译]iOS 8人机界面指南(一):UI设计基础

  • 突出内容
  • 统一
  • 为变化做好准备
    • 压缩
    • 拉伸
  • 充足的互动空间
  • 路径合理
  • 注意层级
  • 使用标准的手势
  • 页面切换过渡需要有焦点
  • 反馈要准确

案例

参考资料:向用户征询iOS授权的五种常见设计模式

设计规范之奇妙的Z轴- 王进

Z轴组成:

z=elevation+translationZ;

  • eleavation是静态的成员,静止高度,是不会变化的。当一个对象的高度产生变化时,它将会尽快恢复到自身的静止高度。

translationZ是用来做动画。动态高度偏移

  • 在layout中使用 android:elevation属性去定义

在代码中使用 View.setElevation 方法去定义

  • 在layout中使用 android:translationz属性去定义设置视图的translation,可以使用View.setTranslationZ方法

  • 新的属性值:translationZ允许你创建一个动画暂时的反应出View的高度值(elevation)变化。

这对于响应触摸手势很有用处:

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

int action = motionEvent.getActionMasked();

/* Raise view on ACTION_DOWN and lower it on ACTION_UP. */

switch (action) {

case MotionEvent.ACTION_DOWN:

Log.d(TAG, "ACTION_DOWN on view.");

view.setTranslationZ(120);

break;

case MotionEvent.ACTION_UP:

Log.d(TAG, "ACTION_UP on view.");

view.setTranslationZ(0);

break;

default:

return false;

}

系统高度参考

高度特性:

  • “高度”的度量单位与 XY 轴的度量单位相同,主要是 DP。由于所有 Material 元素都具有 1 单位 DP 的厚度,所以“高度”度量的是从一个平面顶部到另一个平面顶部的距离。

  • 一个子对象的高度与其父对象的高度相关

阴影

“阴影”提供了对象深度和方向性移动的重要视觉线索。它们是唯一一种标示不同平面之间分离程度的视觉线索。某一对象的“高度”决定了其具体“阴影”的表现形式。

  • Z 轴影响阴影效果

z轴越大,阴影越明显,

  • Z轴影响View 的绘制顺序:

在同一个父view内部,z轴越小,绘制越早,z轴后绘制的会覆盖先前绘制的,只有Z轴相同,才按照添加循序绘制

  • Z轴属性会扩大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
36
37
38
39
40
41
42
43
44

<TextView

android:layout_marginTop="20dp"

android:layout_marginBottom="20dp"

android:gravity="center"

android:text="2dp"

android:textColor="#88000000"

android:layout_width="150dp"

android:layout_height="100dp"

android:elevation="2dp"

android:background="#ffffff"/>



<TextView

android:layout_marginTop="20dp"

android:layout_marginBottom="20dp"

android:layout_marginLeft="20dp"

android:gravity="center"

android:text="4dp"

android:textColor="#88000000"

android:layout_width="150dp"

android:layout_height="100dp"

android:elevation="4dp"

android:background="#ffffff"/>
  • 使用直接使用图片(除非shape),设置z轴,阴影效果不会显示,必须通过代码viewOutlineProvider指明轮廓

    • 在layout中使用android:outlineProvider属性

    none 即使设置里z轴也不会显示阴影

    background 按照background 设置背景

    bounds 按照view 大小设置背景

    paddedbounds 与bounds 类似,不过背景向右偏些

  • 代码设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {

@Override

public void getOutline(View view, Outline outline)

//可以指定圆形,矩形,圆角矩形,path

outline.setOval(0, 0, view.getWidth(), view.getHeight());

}

};

fab.setOutlineProvider(viewOutlineProvider);?

两层阴影

  • 这两层阴影, 一层是环境光投射的阴影, 另一层是关键光照射在 material 上投射的阴影.这两种光线叠加在一起, 才能构成 material design 中的阴影.

  • 而理论上, 这两种阴影都应该是有平滑曲线的, 但是由于手机处理器性能问题, 实时计算平滑后的阴影会导致可怕的耗电与发热, 所以 Google 只能在这方面妥协, 将阴影曲线拉直, 直接导致某些阴影效果变差.通过叠加可以模拟真实的曲线阴影。

链接:http://www.zhihu.com/question/28865209/answer/42749018

周精益分享-自由主题

51offer-api 项目上手总结 - 曾铭

项目结构

  • 目录
  • filter
  • controller
  • service interface&implement 注意区分接口与对应实现
  • dao interface&implement data access object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-------------------------------------
HTTP
-------------------------------------

Filter1
-------------------------------------

Filter2
-------------------------------------

Controller | UserController | PickController
-------------------------------------


-------------------------------------
Service | UserService | XXService |
-------------------------------------

DAO
-------------------------------------

maven

  • cocoapods + xctool
  • 同 gradle

spring

  • IOC,DI
  • 版本区分 service 现有方式及问题

svn

  • 难用的 branch,基于 copy,新路径
  • merge,注意主从,merge from server

Better遭遇白帽子袭击 - 王胜

乌云链接

被狙击的点

  • 短信验证码校验
  • 敏感信息泄露
  • 越权操作

总结

  • 不能太依赖移动端进行拦截,API必需做后端拦截
  • API协议制定后,应该严格执行,无需的字段不要返回,敏感的字段处理后再返回
  • API对于入参要进行严格的协议校验和业务合法性校验
  • 移动安全应该提上日程了

Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错) - 陈奎

  • 第一:介绍Dubbo背景
  • 第二:Dubbo的简介
  • 第三:Dubbo与Zookeeper、SpringMVC整合使用

Sonar 的搭建小结 - 杨俊构

Sonar 的搭建

  • Ubuntu 虚拟机
  • Sonar 配置

sonar-runner 与Gradle配置

周精益分享-自由主题

浅拷贝与深拷贝 - 杨志平

程序中经常会遇到集合类的传值

坑: 数组操作时对于数组中的对象拷贝

目的:

观察array1、mArrayCopy、mArrayCopy2 三者区别

1
2
3
NSArray *array1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *mArrayCopy = [[NSArray alloc] initWithArray:array1 copyItems:YES];
NSArray* mArrayCopy2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array1]];

首先了解copy与retain的区别

OC对象引用计数器:屋里开灯规则

copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。

retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝。

主角:copy、mutableCopy

注意:可变和不可变对象使用copy、mutableCopy的区别

遵守NSCopying 协议的类可以发送copy消息,
遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。

默认的ios类并没有遵守这两个协议
自定义copy 必须遵守NSCopying,并且实现 copyWithZone: 方法
自定义mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法

实例

系统的非容器类对象

这里指的是NSString,NSNumber等等一类的对象。

示例1:
不可变string

1
2
3
4
5
6
7
8
9
10
NSString *string = @"origionStr";
NSString *string2 = string;
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
[stringMCopy appendString:@"!!!!"];
// log
NSLog(@"string = %p %p %@ ",string,&string,string);
NSLog(@"string2 = %p %p %@ ",string2,&string2,string2);
NSLog(@"stringCopy = %p %p %@ ",stringCopy,&stringCopy,stringCopy);
NSLog(@"stringMCopy = %p %p %@ ",stringMCopy,&stringMCopy,stringMCopy);

结果:

1
2
3
4
string      = 0x1064e7088		0x7fff597190a0 origionStr 
string2 = 0x1064e7088 0x7fff59719098 origionStr
stringCopy = 0x1064e7088 0x7fff59719090 origionStr
stringMCopy = 0x7fafebdbb4b0 0x7fff59719088 origionStr!!!!

示例2:
可变string

1
2
3
4
5
6
7
8
9
10
NSMutableString *string = [NSMutableString stringWithString: @"origion"];
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"];
NSLog(@"string = %p %@",string,string);
NSLog(@"stringCopy = %p %@",stringCopy,stringCopy);
NSLog(@"stringMCopy = %p %@",mStringCopy,mStringCopy);
NSLog(@"stringMCopy = %p %@",stringMCopy,stringMCopy);

结果:

1
2
3
4
string      = 0x7fafebdec820 origion origion!
stringCopy = 0x7fafebdcb8c0 origion
stringMCopy = 0x7fafebda4320 origion
stringMCopy = 0x7fafebd32890 origion!!

对于系统的非容器类对象,我们可以认为,如果对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。

系统的容器类对象

指NSArray,NSSet,NSDictionary等。对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。

示例1:
不可变数组

1
2
3
4
5
6
7
8
9
10
//copy返回不可变对象,mutablecopy返回可变对象
NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray *arrayCopy1 = [array1 copy];
NSMutableArray *mArrayCopy1 = [array1 mutableCopy];
[mArrayCopy1 addObject:@"de"];
[mArrayCopy1 removeObjectAtIndex:0];
// log
NSLog(@"array1 = %p %@ %p %@",array1,array1,array1[1],array1[1]);
NSLog(@"arrayCopy1 = %p %@ %p %@",arrayCopy1,arrayCopy1,arrayCopy1[1],arrayCopy1[1]);
NSLog(@"mArrayCopy1 = %p %@ %p %@",mArrayCopy1,mArrayCopy1,mArrayCopy1[0],mArrayCopy1[0]);

结果:

1
2
3
array1      = 0x7fafebdeaed0 (a,b,c) 	0x1064e7228 b
arrayCopy1 = 0x7fafebdeaed0 (a,b,c) 0x1064e7228 b
mArrayCopy1 = 0x7fafebdfc010 (b,c,de) 0x1064e7228 b

示例:
可变数组

1
2
3
4
NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSMutableArray *mArrayCopy = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSLog(@"array1 = %p %@ %p %@ %p %@",array1, array1, array1[0], array1[0], array1[1], array1[1]);
NSLog(@"arrayCopy1 = %p %@ %p %@ %p %@",mArrayCopy,mArrayCopy, mArrayCopy[0], mArrayCopy[0], mArrayCopy[1], mArrayCopy[1]);

结果:

1
2
array1      = 0x7fafebcb5ae0 (a,b,c)	 0x10afe1208 a 0x10afe1228 b
arrayCopy1 = 0x7fafebcb5b10 (a,b,c) 0x10afe1208 a 0x10afe1228 b

示例2:

1
2
3
NSArray *array1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSMutableArray *mArrayCopy = [[NSMutableArray alloc] initWithArray:array1 copyItems:YES];
NSArray* mArrayCopy2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array1]];

mArrayCopy2是完全意义上的深拷贝,而mArrayCopy则不是

对于mArrayCopy内的不可变元素其还是指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。

APP标注工具 - 王进

tracking-sdk之FMDB & common字段简单分享 - 张超耀

FMDB

  • cocoapods管理(优先考虑)
  • libsqlite3.dylib函数库支持
  • 怎删改 & 查简单实用
  • object转string时有转义字符,需要客户端进一步处理

common字段

  • IP & 经纬度获取(不经意间的惊喜)IP查询
  • 其他字段(略)

获取经纬度遇到的问题

  • 经纬度获取是异步,可能在实时数据上传前还没有获取到,所以暂时取巧用IP解析的地址作为经纬度处理。

tracking-sdk 分享 - 潘君

正则表达式入门 - 曾铭

目标

  • 原理我也不懂,但会用(知道能解决什么问题)能看懂(知道在解决什么问题)很重要
  • 高效
  • 碰到问题多”玩”, 玩玩就会了

罗列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
\b 单词分割
\d 数字
\s 空白符
\w 字母、数字、下划线、汉字
\B \D \S \W
[^xyz] 非 xyz
[abc0-9] 字符范围
^ 起始
$ 结束
{n} 重复 n
{n,} 重复 n 到无穷次
{n,m} 重复 nm
* 重复 0 到无穷次
+ 重复 1 到无穷次
? 重复 0 或 1 次
| 或
\\ 字符转义
(exp) 捕获
(?<name>exp) 捕获并命名
(?:exp) 不捕获
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置

推荐工具

参考

那些年我们用过的第三方库

SnapKit(Masonry) 的使用 - 杨志平

这两个库的用法都是差不多的,只是由两个不同的人来主导开源

SnapKit是Swift版

Masonry是OC版

自动布局及交互式编程是iOS开发的趋势,同时Swift也会在不久将来替换OC语言。所以现在的iOS开发者可以开始学习Swift2.0 以及应用 Autolayout 来编程

代码对比(概况了解)

开始前OC原生布局代码
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

UIView *superview = self;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[

//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
使用Masonry

精简

1
2
3
4
5
6
7
8
9

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(- padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

更加精简

1
2
3
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}]
;

同理使用SnapKit

精简

1
2
3
4
5
6
7
8
9
10

let box = UIView()
superview.addSubview(box)

box.snp_makeConstraints { (make) -> Void in
make.top.equalTo(superview).offset(20)
make.left.equalTo(superview).offset(20)
make.bottom.equalTo(superview).offset(-20)
make.right.equalTo(superview).offset(-20)
}

更加精简

1
2
3
box.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(superview).inset(UIEdgeInsetsMake(20, 20, 20, 20))
}

如何使用 && 原理

常见的约束类型对比

ViewAttribute NSLayoutAttribute
view.snp_left NSLayoutAttribute.Left
view.snp_right NSLayoutAttribute.Right
view.snp_top NSLayoutAttribute.Top
view.snp_bottom NSLayoutAttribute.Bottom
view.snp_leading NSLayoutAttribute.Leading
view.snp_trailing NSLayoutAttribute.Trailing
view.snp_width NSLayoutAttribute.Width
view.snp_height NSLayoutAttribute.Height
view.snp_centerX NSLayoutAttribute.CenterX
view.snp_centerY NSLayoutAttribute.CenterY
view.snp_baseline NSLayoutAttribute.Baseline

常见的用法

1
2
3
4
5
6
7
8
9
10
make.top.equalTo(42)
make.lessThanOrEqualTo.equalTo(SuperView)
make.top.equalTo(SuperView)
make.size.equalTo(CGSizeMake(50, 100))
make.edges.equalTo(UIEdgeInsetsMake(10, 0, 10, 0))
make.left.equalTo(view).offset(UIEdgeInsetsMake(10, 0, 10, 0))

make.height.equalTo(OtherView).offset(10)
make.trailing.equalTo(OtherView.snp_trailing).offset(10)
make.bottom.equalTo(-20).priority(250)

对比交互式编程的约束布局

image


JSONModel for swift 的探索 - 曾铭

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
// 可在 Playground 中尝试
import Foundation
import UIKit


// 基本的类型转换
let a:String = ""
let b = ""
var c:String? = nil
c = "test"

let d:String = c as String!
var e = Int(c!)
e = Int("123")

var f = Int("123")!

print("a type:\(a.dynamicType)")
print("b type:\(b.dynamicType)")
print("c type:\(c.dynamicType)")
print("d type:\(d.dynamicType)")
print("e type:\(e.dynamicType)")
print("f type:\(f.dynamicType)\n")


// KVC for NSObject

class FooKVC : NSObject {
var p1 : String = "s"
}

let fk1 = FooKVC()

fk1.p1

fk1.setValue("y", forKey: "p1")

fk1.p1

// reflect for swift2.0

class A
{

var name = "namevalue"
var age = 123
var some:(String, Int) = ("ming", 2)
}

let a1 = A()

a1.age = 456

a1.age


let r = Mirror(reflecting: a1)
for c in r.children {
print(c.label.dynamicType)
print(c.label)
print(c.value.dynamicType)
print(c.value)
print("===")
}
  • KVO for NSObject 略, 注意监听者与被监听者都要是 NSObject 子类

JSONModel for swift?

>
NB: Swift works in a different way under the hood than Objective-C. Therefore I can’t find a way to re-create JSONModel in Swift. JSONModel in Objective-C works in Swift apps through CocoaPods or as an imported Objective-C library.

JSONModel 做的事情

  • String(NSData) -> Dictionary(Array)
  • Dictionary(Array) -> Model-Object

对应实现

参考链接


RATreeView的简单使用 - 张超耀

RATreeView:实现树形结构的TableView;点击每个cell,都可以展开出现新的cell;可以自定义树的层数

主要方法介绍

  • cell的高度
    -(CGFloat)treeView:(RATreeView *)treeView heightForRowForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 这个函数决定是否可以展开,通过设定我们可以设置哪些单元格可以展开到下一层,哪些不可以展开
    - (BOOL)treeView:(RATreeView *)treeView shouldExpandItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 这个看字面意识就理解了,在单元格显示之前(或者说将要显示时)我们可以做些设置,这里是设置相应深度的颜色背景
    - (void)treeView:(RATreeView *)treeView willDisplayCell:(UITableViewCell *)cell forItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo

  • 这里就是我们最熟悉的点击cell处理函数,看代码慢慢体会treeNodeInfo的使用,不想多说了
    -(void)treeView:(RATreeView *)treeView didSelectRowForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo

  • 数据源处理,相当于UITableViewCell处理,关键还是理解treeNodeInfo概念
    - (UITableViewCell *)treeView:(RATreeView *)treeView cellForItem:(id)item treeNodeInfo:(RATreeNodeInfo *)treeNodeInfo
  • 返回每一层包含成员的个数,来制表
    - (NSInteger)treeView:(RATreeView *)treeView numberOfChildrenOfItem:(id)item
  • 返回cell对象
    - (id)treeView:(RATreeView *)treeView child:(NSInteger)index ofItem:(id)item

    RATreeView - github


Facebook 开源的图片加载库Fresco - 王胜

Fresco诞生背景

为提高Android中图片的加载速度,一般的图片库都采用了三级缓存:Memory Cache、Disk Cache和Network。但是,Android的系统层是将物理内存平均分配给每一个App。这样每个App所分配的空间都是有限的,早起的android设备,每个App只被分配16MB空间,这样,如果App中使用大量的图片,那么很容易因OOM而Crashes掉。Facebook App正是大量使用图片的App,面临这个问题刻不容缓,所以他们历尽艰难,开发了Fresco图片加载库。

Fresco诞生过程

内存区域分析:

  • Java heap

    每个厂商会为App分配一个固定尺寸的运行空间。所有的申请通过Java的new操作申请,操作相对安全,通过GC内存自动回收保证内存不被泄露。但不幸的时,GC不够精确化,回收得不够及时。因此还是会存在OOM。

  • Native heap

    通过C或者C++可绕过Java虚拟机直接操作物理内存,但Java程序员习惯了GC的自动回收,很难操作C++的手动操作内存。

  • Ashmen

    Android还有一块内存区域,叫Ashmen。这里的操作很像Nativew heap,但是这里是系统调用的。Java 应用程序是不能直接访问Ashmen的,但是一些例外的情况可以操作,图片就是一种例外。

    1
    2
    3
    BitmapFactory.Options = new BitmapFactory.Options();
    options.inPurgeable = true;
    Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

难点突破:

尽管发现了Purgeable bitmaps,但是这个解码的过程是在UI线程操作的,因此他们又采用了异步实现,并保证了UI线程不引用时,unpin的区域不会被释放。

上层构建

提供给上层调用时,采用了MVC的架构:

  • Model:DraweeHierarchy
  • Control:DraweeControllers
  • View:DraweeViews

使用示例

  1. gradle配置中添加库引用

    1
    compile 'com.facebook.fresco:fresco:0.6.1+'
  2. xml中添加组件

    1
    2
    3
    4
    5
    6
    7
    <com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/sdv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    fresco:roundAsCircle="true"
    fresco:roundingBorderWidth="1dp"
    fresco:roundingBorderColor="#00ff00"

  3. 代码中指定图片地址

    1
    2
    SimpleDraweeView sdv = (SimpleDraweeView) findViewById(R.id.sdv);
    sdv.setImageURI(Uri.parse("http://g.hiphotos.baidu.com/image/pic/item/2e2eb9389b504fc2b351980be7dde71190ef6db5.jpg"));

参考资料:


SIAlertView 阅读 - 潘君

  • 创建自定义alert view的流程

自定义window->添加自定义view controller->定制view->view引用window

  • @class
    1
    @class SIAlertView;

能不使用import的就不使用
用@class代替

  • 通知 和 Block

  • 层级

    1
    2
    const UIWindowLevel UIWindowLevelSIAlert = 1996.0;  // don't overlap system's alert
    const UIWindowLevel UIWindowLevelSIAlertBackground = 1985.0; // below the alert window
  • UIViewTintAdjustmentMode
    通过获取keyWindow的UIViewTintAdjustmentMode来设置alertWindow的
    该属性能够设置tint的调整模式

    1
    2
    3
    4
    5
    6
    7
    typedef enum {
    // 和父视图的一样
    UIViewTintAdjustmentModeAutomatic,
    // 不对tintColor做任何修改
    UIViewTintAdjustmentModeNormal,
    // 在原有tintColor基础上变暗
    UIViewTintAdjustmentModeDimmed,}UIViewTintAdjustmentMode;
  • iOS特有版本代码

    1
    2
    3
    4
    #ifdef __IPHONE_7_0
    //some code
    #endif
    此处填写iOS7才能运行的代码
  • initialize

    1
    2
    3
    4
    5
    6
    7
    8
    9
    + (void)initialize
    {
    if (self != [SIAlertView class])
    return;

    // 默认值赋值
    }

    一些值放在+(void)initialize;中赋值为默认值,这样不管通过何种方式初始化
  • UIAppearance
    SIAlertView *appearance = [self appearance];
    UIView符合UIAppearence协议,能够全局修改所有实例的UI

  • 调用Bundle中资源
    [UIImage imageNamed:@”SIAlertView.bundle/button-default”]
    bundle中资源的调用方法

  • _cmd
    oc特有的方法,无法通过c语言获取

  • [self invalidateLayout]

  • 合理利用循环引用

1
2
3
4
5
6
7
8
if (!self.alertWindow) {
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
window.opaque = NO;
window.windowLevel = UIWindowLevelSIAlert;
window.rootViewController = viewController;
self.alertWindow = window;
}
  • oldKeyWindow
    通知alert后面的视图转变方向
    取用一些oldKeyWindow的值

android饼图库 -吴明


HeaderFooterRecyclerViewAdapter——李仙鹏

HeaderFooterRecyclerViewAdapter,用于RecyclerView。可方便的添加header或者footer。使用非常简单,只需要按照实际需求,在对应的header、content、footer相关方法进行重写即可。

附上源码

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
public abstract class HeaderFooterRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int VIEW_TYPE_MAX_COUNT = 1000;
private static final int HEADER_VIEW_TYPE_OFFSET = 0;
private static final int FOOTER_VIEW_TYPE_OFFSET = HEADER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT;
private static final int CONTENT_VIEW_TYPE_OFFSET = FOOTER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT;

private int headerItemCount;
private int contentItemCount;
private int footerItemCount;

/**
* {@inheritDoc}
*/

@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

// Delegate to proper methods based on the viewType ranges.
if (viewType >= HEADER_VIEW_TYPE_OFFSET && viewType < HEADER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateHeaderItemViewHolder(parent, viewType - HEADER_VIEW_TYPE_OFFSET);
} else if (viewType >= FOOTER_VIEW_TYPE_OFFSET && viewType < FOOTER_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateFooterItemViewHolder(parent, viewType - FOOTER_VIEW_TYPE_OFFSET);
} else if (viewType >= CONTENT_VIEW_TYPE_OFFSET && viewType < CONTENT_VIEW_TYPE_OFFSET + VIEW_TYPE_MAX_COUNT) {
return onCreateContentItemViewHolder(parent, viewType - CONTENT_VIEW_TYPE_OFFSET);
} else {
// This shouldn't happen as we check that the viewType provided by the client is valid.
throw new IllegalStateException();
}
}

/**
* {@inheritDoc}
*/

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
// Delegate to proper methods based on the viewType ranges.
if (headerItemCount > 0 && position < headerItemCount) {
onBindHeaderItemViewHolder(viewHolder, position);
} else if (contentItemCount > 0 && position - headerItemCount < contentItemCount) {
onBindContentItemViewHolder(viewHolder, position - headerItemCount);
} else {
onBindFooterItemViewHolder(viewHolder, position - headerItemCount - contentItemCount);
}
}

/**
* {@inheritDoc}
*/

@Override
public final int getItemCount() {
// Cache the counts and return the sum of them.
headerItemCount = getHeaderItemCount();
contentItemCount = getContentItemCount();
footerItemCount = getFooterItemCount();
return headerItemCount + contentItemCount + footerItemCount;
}

/**
* {@inheritDoc}
*/

@Override
public final int getItemViewType(int position) {
// Delegate to proper methods based on the position, but validate first.
if (headerItemCount > 0 && position < headerItemCount) {
return validateViewType(getHeaderItemViewType(position)) + HEADER_VIEW_TYPE_OFFSET;
} else if (contentItemCount > 0 && position - headerItemCount < contentItemCount) {
return validateViewType(getContentItemViewType(position - headerItemCount)) + CONTENT_VIEW_TYPE_OFFSET;
} else {
return validateViewType(getFooterItemViewType(position - headerItemCount - contentItemCount)) + FOOTER_VIEW_TYPE_OFFSET;
}
}

/**
* Validates that the view type is within the valid range.
*
* @param viewType the view type.
* @return the given view type.
*/

private int validateViewType(int viewType) {
if (viewType < 0 || viewType >= VIEW_TYPE_MAX_COUNT) {
throw new IllegalStateException("viewType must be between 0 and " + VIEW_TYPE_MAX_COUNT);
}
return viewType;
}

/**
* Notifies that a header item is inserted.
*
* @param position the position of the header item.
*/

public final void notifyHeaderItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
if (position < 0 || position >= newHeaderItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (newHeaderItemCount - 1) + "].");
}
notifyItemInserted(position);
}

/**
* Notifies that multiple header items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyHeaderItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newHeaderItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (newHeaderItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart, itemCount);
}

/**
* Notifies that a header item is changed.
*
* @param position the position.
*/

public final void notifyHeaderItemChanged(int position) {
if (position < 0 || position >= headerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemChanged(position);
}

/**
* Notifies that multiple header items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyHeaderItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount >= headerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart, itemCount);
}


/**
* Notifies that an existing header item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public void notifyHeaderItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= headerItemCount || toPosition >= headerItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemMoved(fromPosition, toPosition);
}

/**
* Notifies that a header item is removed.
*
* @param position the position.
*/

public void notifyHeaderItemRemoved(int position) {
if (position < 0 || position >= headerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRemoved(position);
}

/**
* Notifies that multiple header items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public void notifyHeaderItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > headerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for header items [0 - " + (headerItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart, itemCount);
}

/**
* Notifies that a content item is inserted.
*
* @param position the position of the content item.
*/

public final void notifyContentItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
if (position < 0 || position >= newContentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (newContentItemCount - 1) + "].");
}
notifyItemInserted(position + newHeaderItemCount);
}

/**
* Notifies that multiple content items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newContentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (newContentItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart + newHeaderItemCount, itemCount);
}

/**
* Notifies that a content item is changed.
*
* @param position the position.
*/

public final void notifyContentItemChanged(int position) {
if (position < 0 || position >= contentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemChanged(position + headerItemCount);
}

/**
* Notifies that multiple content items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > contentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart + headerItemCount, itemCount);
}

/**
* Notifies that an existing content item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public final void notifyContentItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= contentItemCount || toPosition >= contentItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemMoved(fromPosition + headerItemCount, toPosition + headerItemCount);
}

/**
* Notifies that a content item is removed.
*
* @param position the position.
*/

public final void notifyContentItemRemoved(int position) {
if (position < 0 || position >= contentItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRemoved(position + headerItemCount);
}

/**
* Notifies that multiple content items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyContentItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > contentItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for content items [0 - " + (contentItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart + headerItemCount, itemCount);
}

/**
* Notifies that a footer item is inserted.
*
* @param position the position of the content item.
*/

public final void notifyFooterItemInserted(int position) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
int newFooterItemCount = getFooterItemCount();
if (position < 0 || position >= newFooterItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (newFooterItemCount - 1) + "].");
}
notifyItemInserted(position + newHeaderItemCount + newContentItemCount);
}

/**
* Notifies that multiple footer items are inserted.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeInserted(int positionStart, int itemCount) {
int newHeaderItemCount = getHeaderItemCount();
int newContentItemCount = getContentItemCount();
int newFooterItemCount = getFooterItemCount();
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > newFooterItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (newFooterItemCount - 1) + "].");
}
notifyItemRangeInserted(positionStart + newHeaderItemCount + newContentItemCount, itemCount);
}

/**
* Notifies that a footer item is changed.
*
* @param position the position.
*/

public final void notifyFooterItemChanged(int position) {
if (position < 0 || position >= footerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemChanged(position + headerItemCount + contentItemCount);
}

/**
* Notifies that multiple footer items are changed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeChanged(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > footerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRangeChanged(positionStart + headerItemCount + contentItemCount, itemCount);
}

/**
* Notifies that an existing footer item is moved to another position.
*
* @param fromPosition the original position.
* @param toPosition the new position.
*/

public final void notifyFooterItemMoved(int fromPosition, int toPosition) {
if (fromPosition < 0 || toPosition < 0 || fromPosition >= footerItemCount || toPosition >= footerItemCount) {
throw new IndexOutOfBoundsException("The given fromPosition " + fromPosition + " or toPosition " + toPosition + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemMoved(fromPosition + headerItemCount + contentItemCount, toPosition + headerItemCount + contentItemCount);
}

/**
* Notifies that a footer item is removed.
*
* @param position the position.
*/

public final void notifyFooterItemRemoved(int position) {
if (position < 0 || position >= footerItemCount) {
throw new IndexOutOfBoundsException("The given position " + position + " is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRemoved(position + headerItemCount + contentItemCount);
}

/**
* Notifies that multiple footer items are removed.
*
* @param positionStart the position.
* @param itemCount the item count.
*/

public final void notifyFooterItemRangeRemoved(int positionStart, int itemCount) {
if (positionStart < 0 || itemCount < 0 || positionStart + itemCount > footerItemCount) {
throw new IndexOutOfBoundsException("The given range [" + positionStart + " - " + (positionStart + itemCount - 1) + "] is not within the position bounds for footer items [0 - " + (footerItemCount - 1) + "].");
}
notifyItemRangeRemoved(positionStart + headerItemCount + contentItemCount, itemCount);
}

/**
* Gets the header item view type. By default, this method returns 0.
*
* @param position the position.
* @return the header item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getHeaderItemViewType(int position) {
return 0;
}

/**
* Gets the footer item view type. By default, this method returns 0.
*
* @param position the position.
* @return the footer item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getFooterItemViewType(int position) {
return 0;
}

/**
* Gets the content item view type. By default, this method returns 0.
*
* @param position the position.
* @return the content item view type (within the range [0 - VIEW_TYPE_MAX_COUNT-1]).
*/

protected int getContentItemViewType(int position) {
return 0;
}

/**
* Gets the header item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the header item count.
*/

protected abstract int getHeaderItemCount();

/**
* Gets the footer item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the footer item count.
*/

protected abstract int getFooterItemCount();

/**
* Gets the content item count. This method can be called several times, so it should not calculate the count every time.
*
* @return the content item count.
*/

protected abstract int getContentItemCount();

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for header items.
*
* @param parent the parent view.
* @param headerViewType the view type for the header.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateHeaderItemViewHolder(ViewGroup parent, int headerViewType);

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for footer items.
*
* @param parent the parent view.
* @param footerViewType the view type for the footer.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateFooterItemViewHolder(ViewGroup parent, int footerViewType);

/**
* This method works exactly the same as {@link #onCreateViewHolder(android.view.ViewGroup, int)}, but for content items.
*
* @param parent the parent view.
* @param contentViewType the view type for the content.
* @return the view holder.
*/

protected abstract RecyclerView.ViewHolder onCreateContentItemViewHolder(ViewGroup parent, int contentViewType);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for header items.
*
* @param headerViewHolder the view holder for the header item.
* @param position the position.
*/

protected abstract void onBindHeaderItemViewHolder(RecyclerView.ViewHolder headerViewHolder, int position);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for footer items.
*
* @param footerViewHolder the view holder for the footer item.
* @param position the position.
*/

protected abstract void onBindFooterItemViewHolder(RecyclerView.ViewHolder footerViewHolder, int position);

/**
* This method works exactly the same as {@link #onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int)}, but for content items.
*
* @param contentViewHolder the view holder for the content item.
* @param position the position.
*/

protected abstract void onBindContentItemViewHolder(RecyclerView.ViewHolder contentViewHolder, int position);

}

聊聊提高代码质量

什么样的代码才是好的——李仙鹏

我所理解的好代码

  • 代码规范——可读性

  • 代码耦合低——可扩展性、可移植性

    • 尽量采用接口实现,减少继承
    • 通用功能尽量抽取作为一个独立的方法,避免重复造轮子
    • 设计模式并不能提高代码执行效率,但容易对代码进行模块切分,从而进行代码的解耦合。设计模式也可以算是程序员的一种通用“语言“,方便程序员之间的沟通.
      • 根据不同OS或者语言特性,从大的架构上遵循MVC或者MVP或者其它类似的层次分明的设计模式。
  • 严格的code review

    • 能够发现潜在的bug、不合理的实现和是否遵从代码规范
    • 有助于代码提交人员对自身代码质量的要求——面子问题
  • 性能

以上这些点都是为了程序的健壮性、可扩展性、可移植性。最终目标,在核心开发人员变动或者产品需求变动后,都不会对代码维护、版本迭代和程序稳定性造成重大影响。

51offer-v2.5.0重写中,我们是如何做的

  • 整体架构上采用MVP模式

    • V和P通过接口实现交互,M只被P处理,P处理完后通过接口反馈给V
      MVP
  • 代码可移植性和可扩展性

    • 按照是否通用原则,命名包名——非通用包名,放在offer包下;通用功能模块,放在非offer包下。这样有利于快速移植,或者打成JAR包
    • 接口实现,减少继承——在类的继承中,减少继承体系中每层父类的职责范围
  • HTTP请求

    • 底层请求采用OkHttpOkHttp默认实现的功能为:
      • 支持协议——HTTP/2SPDY, 可以合并多个到同一个主机的请求
        • 使用连接池技术减少请求的延迟
        • 使用GZIP压缩减少传输的数据量
        • 报文缓存响应,避免重复的网络请求
  • json解析

  • 图片加载

    • 采用Android-Universal-Image-Loader

      • 多线程图片加载
      • 可自定义的加载器
      • 可自定义的图片显示回调
      • 图片三级缓存——内存和硬盘缓存的图片通过多种数据结构管理
      • 监听加载过程
    • bitmap显示质量设置为RGB_565——一个像素需要16位表示,Android中默认为ARGB_8888——一个像素需要32位表示。这种方式在基本保障图片质量要求的同时,还能够大大减少手机的内存占用

  • 数据存储

    • 由于目前客户端还未涉及到大量数据和离线加载模式,所以我们暂不使用数据库(以后在做IM或者离线加载时,会考虑使用ORM来访问数据库),综合速度考虑使用Android的SharedPreference

组件通信库EventBus - 吴强

EventBus是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间的通信。

三个主要元素:

  • Event:事件

    Event可以是任意类型的对象

  • Subscriber:事件订阅者,接收特定的事件

    在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个

  • Publisher:事件发布者,用于通知Subscriber有事件发生

    可以在任意线程任意位置发送事件,直接调用EventBus的post(Object)方法,可以自己实例化EventBus对象,但一般使用默认的单例就好了:EventBus.getDefault(),根据post函数参数的类型,会自动调用订阅相应类型事件的函数。

ThreadMode

Subscriber函数的名字只能是那4个,因为每个事件订阅函数都是和一个ThreadMode相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode:

  • PostThread:
    事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。
  • MainThread:
    事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。
  • BackgroundThread:
    事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。
  • Async:
    事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。

注册事件与解除注册

  • 通过EventBus.getDefault().register方法可以向EventBus注册来订阅事件
  • 通过registerSticky可以注册Stick事件处理函数
  • 通过EventBus.getDefault().unregister方法解除EventBus事件订阅

Post事件

  • 直接调用EventBus.getDefault().post(Event)就可以发送事件,根据Event的类型就可以发送到相应事件的订阅者。
  • 当通过postSticky发送一个事件时,这个类型的事件的最后一次事件会被缓存起来,当有订阅者通过registerSticky注册时,会把之前缓存起来的这个事件直接发送给它

缺点

无法进程间通信,如果一个应用内有多个进程的话就没办法了

注意事项及要点

  • 同一个onEvent函数不能被注册两次,所以不能在一个类中注册同时还在父类中注册
  • 当Post一个事件时,这个事件类的父类的事件也会被Post。
  • Post的事件无Subscriber处理时会Post NoSubscriberEvent事件,当调用Subscriber失败时会Post SubscriberExceptionEvent事件。

其他

EventBus中还有个Util包,主要作用是可以通过AsyncExecutor执行一个Runnable,通过内部的RunnableEx(可以搜索异常的Runnable)当Runnable抛出异常时通过EventBus发消息显示错误对话框。

参考资料:快速Android开发系列通信篇之EventBus

如何提高代码质量——吴明

Alt text

  • code review
  • 命名规范
    • java命名规范:
      • java类:m+模块+功能(如:mLoginRegester)
      • 控件类:m+模块+功能+控件缩写(如:mLoginRegesterBtn)
      • 常量:模块+功能(如:SCHOOL_COUNT_MAX)
    • xml命名规范
      • id:模块+功能+控件缩写
    • color,string,dimen,drawable
    • 图片:模块功能备注_状态
    • 注释
  • 好的框架
    • okhttp
    • Gson
  • 设计模式:
  • 处理异常
  • 优化性能

  • 代码测试工具:静态代码分析

如何写一份好的代码 - 张超耀

数据结构和核心算法

  • 数据结构的重要性:低水平程序员总在考虑代码,高水平程序员总在考虑数据结构及其之间的关系

  • 数据结构决定算法,数据结构考虑清楚了,核心的算法自然就出来了,这就是关于每个类的每个方法如何实现的问题

功能实现

  • 思路确定后,实现过程也需要大量的构思活动。碰到比较熟悉有经验的领域,自然可以轻车熟路,但难免会有一些你不太熟悉的技术需要尝试。作为一个程序员,最大的挑战也是最大的乐趣所在,就是不断学习新的技术,没有这样的心态,很快就会落后。

  • 那么遇到不熟悉的技术怎么办?Demo先行,这样做的好处是把单个技术问题和其他潜在的bug隔离开来,便于快速学习新技术。否则,直接在项目里写代码出错以后,要判断问题的源头都要多费好几倍的精力。

测试

  • 测试很重要,设计测试用例就像开发时设计数据结构一样,也是很关键的。

代码可读性

  • 要想自己满意,代码的可读性一定要好。要做到一年后甚至几年后你拿到自己写的代码,还能很容易看明白当时的思路和实现。这就涉及到命名和注释的问题

  • 命名就像超市里的商品标签一样,要让看得人一目了然就知道这是个什么东西

  • 注释也是很重要的,它可以用来说明一段代码的作用,算法的设计思想,或者是方法调用的参数格式要求等

最后总结一下:

  • 技术水平是可以慢慢提高的,但是好的编程习惯需要从一开始就养成,它会让你在前进的道路上事半功倍,受益终生。

怎样写好的代码 - 曾铭

有两种方式构建软件设计:一种是把软件做得很简单以至于明显找不到缺陷;另一种是把它做得很复杂以至于找不到明显的缺陷
——C.A.R. Hoare

谁来写?角色的定义

  • 程序员 vs 工程师
  • 实现功能 vs 解决问题
  • 搭个帐篷 vs 照顾孩子

好的代码?

一个程序员更希望接手一个有bug但是看的懂的工程,还是一个没bug但是看不懂的工程?

  • 代码跟人聊天,解释做什么,注释解释为什么这么做,要注意什么
  • 面向接口而不仅仅面向对象

举例:在 APP 开发和 API 开发之间,面向 API 文档做开发

写的过程?(推荐开发过程)

  • 需求明确(理解来源及演进)
  • 整体设计(外在联系,临界条件,错误处理)
  • 实现(验证思路,解决问题及优化)切记不要拿到需求直接跳到这一步

好?目标

  • 可运行,可读,可维护,可测试
  • 参见 TDD,BDD

无主题

Android中ListView的垃圾回收 - 王胜

ListView中ItemView的复用

为了提高ListView的加载速度和用户操作的流畅度,ListView底层做了ItemView的复用,避免重复地为每一个ItemView开辟空间。

  • ActiveView

    激活view,当期显示在屏幕上的激活的view

  • ScrapView
    废弃view,被删除的ActiveView会被自动加入ScrapView

ListView的ItemView中大图片的覆盖显示

由于复用机制,当Item中包含大图时,由于Java GC的回收机制不够及时,造成ScrapView中图片资源不能立即释放,当用户上拉加载新的item内容时,却看到了之前加载的图片,直到新图从网络下载完毕,才会刷新view正确显示图片。

考虑到这种情况,Android 在API Level1中已经为ListView和GridView添加了RecyclerListener回调接口。开发者只需为ListView注册回调,重写onMovedToScrapHeap方法即可。

示例:

1
2
3
4
5
6
7
8
mListView.setRecyclerListener(new RecyclerListener() {
@Override
public void onMovedToScrapHeap(View view) {
// Release strong reference when a view is recycled
final ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
imageView.setImageBitmap(null);
}
});

参考资料:

提升用户体验的7大微交互 - 张超耀


众所周知,我们总是依据封面来判断书的好坏,聪明的设计师会创造实用有吸引力的界面。潜在用户可能会被吸引,但如何一直黏住他们呢?

要试着回答这个问题,所有一切都指向人本设计,其中用户是最主要的考量。以人为本:你的应用应该使用日常用语,包括情绪、口语,外观还要有一丝“诱惑力”。界面应当成为你的好朋友,时刻准备给出建议提升你的体验,让你会心一笑。

  • 是微交互在起作用。精确的说,这主要是界面附带的交互动画,使它更具表现力。优秀的动画能够:

    • 表达状态并提供反馈
    • 提升直接的操纵感
    • 将操作结果可视化
  • 在用户体验中,关键是你如何对待用户,还有他们使用产品时的感受。极小的细节都值得加倍留心。微交互提供了用户所需的反馈,表达了当前运行状况。无论背后逻辑有多么复杂,都能使界面更亲切。

显示系统状态

Jakob Nielsen在可用性原则启示第一条中描述:让用户始终知晓当前在发生什么。用户希望立马得到回应,但总有些情况下,网站需要一点时间等待操作完成。

那么,界面就应当在背景处显示图形,反映完成百分比。或是播放声音,让用户了解当前发生的事情。这个原则也关系到文件传输:不要让用户觉得无聊,给他们看进度条。即使是不太令人愉快的通知,比如传输失败,也应该以令人喜爱的方式展现。让你的用户微笑!

突出显示变化

通常为了节省空间,应用会在需要时把某个按钮替换掉。有时我们需要展现通知,确保用户注意到了。动画可以吸引他们注意,不至于忽略你认为重要的信息。


保持前后关联

在这个智能手机和小屏幕智能手表的时代,难以在屏幕上展现大量信息。有一种处理方式,是在不同页面之间保持清晰的导航。让用户理解什么东西来自何处,便能轻易回溯。有多种方式可以实现:

非标准布局

继续之前的例子,微交互应当帮助用户理解如何操作非标准的布局,去除不必要的疑惑。照片前后滑动、滚动式图表和旋转角色都是很棒的选择:


行动号召

除了帮助用户有效地操作应用,微交互也有鼓励用户操作的能力:持续浏览、点赞或分享内容,只因为这很有吸引力,用户不舍离去:


输入的视觉化

所有应用中,数据录入都是最重要的元素之一。数据录入决定了用户所得结果的质量。通常,这部分很无趣,但微交互可以使它与众不同:


使教程生动形象

当然,在应用发布后,动画可以教育用户。它突出一些基本功能和控件,排除用户在未来的使用中的障碍。


结论

所以,如果你重视用户体验,就应该从头到尾精细打磨界面,通过微交互与动画为它增色添彩。它会为你的项目赋予生命。

注意每个细节,是使人机交互易用的成功要诀

原文链接

曾铭

CLI 上手

CLI vs GUI

  • Command Line Interface VS Graphical User Interface
  • 分别是给工程师用的和给人(包含工程师)用的
  • 哪个方便用哪个,哪个好玩用哪个

CLI 能干什么?(for me)

  • 快速安装软件
  • CLI 开发:工具、服务,如 jenkins, sonar, ssh-key,

CLI 如何上手

  • 不要畏惧,把命令行当做大一上的 C 语言,是最简单,逻辑最清晰的。(鬼知道 GUI 背后都做了什么)
  • 理解环境变量 profile_load_path env echo,知道 shell 怎么去找一个命令即可
  • 安装软件,了解常见的包管理工具即可:homebrew(常用软件), gem(ruby 软件), npm (node.js 软件)
    • 基本都是 search install list ,会这三个参数就行了
    • 思想都是想通的,比如 pod (for iOS/OSX dev)
1
2
3
4
5
# 在终端输入试试
say "hi"
# 还有王胜推荐的 sl
brew install sl
sl

Sonar 学习

  • Sonar不叫Sonar 叫SonarQube
  • 开源的 刁刁的 项目地址
  • 管理平台代码质量
  • 支持plugin

    • 通过插件形式,可以支持包括 java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测
    • 插件库
    • 大部分免费 OC不免费 有第三方免费的
  • Sonar的3个组成部分

    • Database (支持多种类型的数据库MySQL/Oracle) 配置sonar实例 存储分析结果
    • Web Server 浏览代码质量分析结果
    • Analyzers 分析代码的分析器
  • Sonar Notes

    • SonarQube的web server和database只能有一个
    • 从性能上考虑 三个组件应该放在三个专有的电脑上,但是Database和Web Server可以安装在同一台电脑上
    • Datatbase和Analyzers不应该安装在同一台电脑上
    • Analyzers可以通过加电脑扩展
    • Database和Analyzers必须存在同一个网络内
    • 所有的机子必须时间同步
  • Analyzers

    • SonarQube Runner: recommended for all non-Maven projects
    • Maven: recommended for all projects built with Maven
    • SonarQube Ant Task: to integrate with projects built with Ant
    • Gradle: to integrate with projects built with Gradle
    • CI Engine: see Jenkins plugin or Hudson plugin or Bamboo plugin or AnthillPro plugin. Note that other CI engines can be used even if there is no SonarQube plugin available.
  • Mac下跑通sonar所有流程需要的东西

    • a Mac with Xcode
    • SonarQube
    • SonarQube Runner
    • xctool
      • xctool是facebook开源的一个命令行工具,用来替代苹果的xcodebuild工具
    • OCLint
      • OCLint是一个静态代码分析器,可以检测的Objective-C代码(同时支持Ç和c + +),发现常见的问题:例如的if / else / try / catch和/最后声明出错,未被使用的本地实例和参数,过度复杂(有效值代码行数状语从句:常循环复杂度太高),冗余代码,代码异味,以及其他不好的代码。
    • gcovr
      • 代码覆盖率分析工具

常用配置文件

配置Sonar-Runner

  • 路径 <放置sonar的目录>/sonar-runner/2.4/libexec/conf/sonar-runner.properties

  • 只摘录相关项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #----- Default SonarQube server 
    sonar.host.url=http://localhost:9000

    #----- MySQL
    sonar.jdbc.url=jdbc:mysql://192.168.22.22:3306/sonar?useUnicode=true&amp;characterEncoding=utf8

    #----- Global database settings
    sonar.jdbc.username=sonar
    sonar.jdbc.password=sonar

    #----- Security (when 'sonar.forceAuthentication' is set to 'true')
    sonar.login=admin
    sonar.password=admin

配置Database和web server

sonar.properties

  • 路径 <放置sonarqube的目录>/sonarqube-5.1.2/conf/sonar.properties
  • 只摘录相关项
    1
    2
    3
    4
    5
    6
    7
    8
    9
    sonar.jdbc.username=sonar  // 数据库账号
    sonar.jdbc.password=sonar // 数据库密码

    sonar.jdbc.url=jdbc:mysql://192.168.22.22:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance
    // MySql路径

    sonar.web.host=0.0.0.0 // web host
    sonar.web.context=/sonar // web 路径
    sonar.web.port=9000 // 端口号

配置需要分析的项目 (需放置在项目根目录下)

run-sonar.sh 分析项目的脚本

  • 未做修改 直接拿来用

sonar-project.properties 项目的必要参数
只摘录相关项
看注释

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

##########################
# Required configuration #
##########################

sonar.projectKey=51offer
sonar.projectName=51offer
sonar.projectVersion=1.9
sonar.language=objc

# Project description
sonar.projectDescription=test

# Path to source directories
sonar.sources=51offer
# Path to test directories (comment if no test)
sonar.tests=51offerTests


# Xcode project configuration (.xcodeproj or .xcworkspace)
# -> If you have a project: configure only sonar.objectivec.project
# -> If you have a workspace: configure sonar.objectivec.workspace and sonar.objectivec.project
# and use the later to specify which project(s) to include in the analysis (comma separated list)
sonar.objectivec.project=51offer.xcodeproj
sonar.objectivec.workspace=51offer.xcworkspace

# Scheme to build your application
sonar.objectivec.appScheme=51offer
# Scheme to build and run your tests (comment following line of you don't have any tests)
# sonar.objectivec.testScheme=myApplicationTests

##########################
# Optional configuration #
##########################

# Encoding of the source code
sonar.sourceEncoding=UTF-8

# JUnit report generated by run-sonar.sh is stored in sonar-reports/TEST-report.xml
# Change it only if you generate the file on your own
# The XML files have to be prefixed by TEST- otherwise they are not processed
# sonar.junit.reportsPath=sonar-reports/

# Cobertura report generated by run-sonar.sh is stored in sonar-reports/coverage.xml
# Change it only if you generate the file on your own
# sonar.objectivec.coverage.reportPattern=sonar-reports/coverage*.xml

# OCLint report generated by run-sonar.sh is stored in sonar-reports/oclint.xml
# Change it only if you generate the file on your own
# sonar.objectivec.oclint.report=sonar-reports/oclint.xml

# Paths to exclude from coverage report (tests, 3rd party libraries etc.)
# sonar.objectivec.excludedPathsFromCoverage=pattern1,pattern2
sonar.objectivec.excludedPathsFromCoverage=.*Tests.*

# Project SCM settings
# sonar.scm.enabled=true
# sonar.scm.url=scm:git:https://...

参考资料:

网络信息安全

微信支付的网络安全 - 王胜

协议规则

  • 采用HTTPS传输
  • POST提交
  • MD5签名,请求和接收数据均需要校验签名,详见
  • 调用申请退款、撤销订单接口需要商户证书

安全规范

  • 签名算法
    1. 设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
    2. 在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue
  • 生成随机数算法

    微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串

  • 商户证书

    • 获取商户证书

      微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。

    • 使用商户证书

    • 商户证书安全
  • 商户回调API安全

    在普通的网络环境下,HTTP请求存在DNS劫持、运营商插入广告、数据被窃取,正常数据被修改等安全风险。商户回调接口使用HTTPS协议可以保证数据传输的安全性。所以微信支付建议商户提供给微信支付的各种回调采用HTTPS协议。请参考:HTTPS搭建指南

参考资料:微信支付

安全『相对论』 - 曾铭

角色

介质

  • 服务器
  • API
  • 网络传输过程
  • 客户端

  • owner: 开发者 内部人员
  • 用户:消费者、商家
  • 『坏人们』:黑客(白帽)、『友商』、路过……

分清角色才能明白设定一个安全逻辑是针对谁,解决什么问题。

目标

  • 『一有适当的利润,资本就会非常胆壮起来。只要有10%的利润,它就会到处被人使用;有20%,就会活泼起来;有50%,就会引起积极的冒险;有100%,就会使人不顾一切法律;有300%,就会使人不怕犯罪,甚至不怕绞首的危险。』
  • 没有绝对的安全,只有相对的安全
  • 为了安全是否影响了普通用户?能否变通?
  • 影响的是一个用户还是全部用户?
  • 能否补救?主动?被动?
  • 你不需要完美,你只需要比友商好一些

实践

聊聊发短信接口的案例

  • get -> post
  • 限制单日单个手机号,(避免单个手机号过多骚扰,产品不完整,建议加提示)
  • 限制单个 IP 请求次数 (有必要,但要适量放宽,要跟踪结果)
  • 图形验证码 (把问题转化为计算机不擅长的问题来保障安全,有必要,用户体验有影响,可优化)
  • 一种 API 加密方式(根本问题是:API 如何判断这个请求是可信的客户端发来的)
1
2
客户端 - 网络传输 - API
key, 加密(签名)算法 sign=md5(key+url_path+time)

RSA -李仙鹏

在公钥系统中,我们采用公钥加密,私钥解密的方式,使得报文能够比较安全的传输。

假设A和B通信,但他们之间不通过对称密钥,B有一个公钥$K_B^+$和一个私钥$K_B^-$。为了与B实现通信,A首先需要获得B的公钥$K_B^+$对报文m进行加密,即$K_B^+(m)$;B收到A的加密报文后用私钥$K_B^-$进行解密,即$K_B^-(K_B^+(m))=m$。其中的加解密算法通常使用RSA(RSA取创始人Ron Rivest, Adi Shamir, Leonard Adleman的姓氏首字母)。

RSA工作方式

加密时,首先把报文m做e次的幂运算,然后做模n的算数运算,即$m^e\%n$;解密则先把上述密文值做d次幂,再做模n运算,即$(m^e)^d\%n=m^\left(ed\right)\%n$。

RSA工作原理推导

为了解开RSA工作原理的神秘面纱,需要使用数论中的一个神奇结论:如果p和q是素数,且有$n=pq$,则$x^y\%n$与$x^\left(y\%\left(p-1\right)\left(q-1\right)\right)\%n$相等。应用这个结论,那我们的私钥解密为:$$(m^e)^d\%n=m^\left(ed\%\left(p-1\right)\left(q-1\right)\right)\%n$$

注意,$m<n$,并且我们是这样选择e和d的:$ed-1$能被$\left(p-1\right)\left(q-1\right)$整除,等价地说$ed\%\left(p-1\right)\left(q-1\right)=1$,由此可得:$$(m^e)^d\%n=m^1\%n=m\qquad m<n,n=pq,ed\%\left(p-1\right)\left(q-1\right)=1,p和q是素数$$

于是乎,就得到了我们希望的结果:先对m做e次幂(加密)再做d次幂(解密),然后做模n的算数运算,就可得到原始报文m。另外通过颠倒上述公式的加解密次序,一样能得到原来的m。
$$(m^e)^d\%n=m=(m^d)^e\%n\Rightarrow K_B^-(K_B^+(m))=m=K_B^+(K_B^-(m))$$

因此B对外公开的公钥$K_B^+$为二元组$(n,e)$,私钥$K_B^-$为二元组$(n,d)$

举例验证

条件:$$m<n,\quad n=pq,\quad ed\%\left(p-1\right)\left(q-1\right)=1,\quad p和q是素数$$

取简单的值:$$p=5,\quad q=7$$

那么可以推导出:$$n=pq=5*7=35,\quad (ed-1)\%(p-1)(q-1)=0\Rightarrow (ed-1)\%(5-1)(7-1)=0\Rightarrow (ed-1)\%24=0$$

于是,我们可以简单起见,为了减少计算量,设$ed-1$为$24$的1倍,那么我们可取值$$e=5,\quad d=5$$

假设传输明文为:$$m=3$$

那么综上所述,我们可以得出:

  • 加密,$m^e\%n=3^5\%35=33$

  • 解密,$(m^e\%n)^d\%n=33^5\%35=3=m$,因此对RSA加密后的密文解密得到的就是原始的明文

iOS 越狱应用安全-潘君

  • 对ipa砸壳
  • class-dump 导出头文件
  • 头文件导入project方便查看
  • 善用Xcode的搜索找需要hook的函数
  • hook函数完成不可告人的目的

参考资料

网络安全 - 张超耀

主要特性

  • 保密性

    • 信息不泄露给非授权用户、实体或过程,或供其利用的特性。
  • 完整性

    • 数据未经授权不能进行改变的特性。即信息在存储或传输过程中保持不被修改、不被破坏和丢失的特性。
  • 可用性

    • 可被授权实体访问并按需求使用的特性。即当需要时能否存取所需的信息。例如网络环境下拒绝服务、破坏网络和有关系统的正常运行等都属于对可用性的攻击;
  • 可控性

    • 对信息的传播及内容具有控制能力。
  • 可审查性

    • 出现安全问题时提供依据与手段

主要类型

网络安全由于不同的环境和应用而产生了不同的类型。主要有以下几种:

  • 系统安全

    • 运行系统安全即保证信息处理和传输系统的安全。它侧重于保证系统正常运行。避免因为系统的崩演和损坏而对系统存储、处理和传输的消息造成破坏和损失。避免由于电磁泄翻,产生信息泄露,干扰他人或受他人干扰。
  • 网络的安全

    • 网络上系统信息的安全。包括用户口令鉴别,用户存取权限控制,数据存取权限、方式控制,安全审计。安全问题跟踩。计算机病毒防治,数据加密等。
  • 信息传播安全

    • 网络上信息传播安全,即信息传播后果的安全,包括信息过滤等。它侧重于防止和控制由非法、有害的信息进行传播所产生的后果,避免公用网络上大云自由传翰的信息失控。
  • 信息内容安全

    • 网络上信息内容的安全。它侧重于保护信息的保密性、真实性和完整性。避免攻击者利用系统的安全漏润进行窃听、冒充、诈编等有损于合法用户的行为。其本质是保护用户的利益和隐私。

HTTP加密 -吴明

  • 对称性加密
    • 加密双方使用同一个密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密
    • 常用算法:
      • DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES
  • 非对称性加密
    • 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
    • RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)
  • 对称性加密示例
    • sig计算
      sig主要用来做校验,由BaseString和SecretKey通过HMAC-SHA1算法计算得到的。
    • BaseString 组成
      BaseString由以下三部分组成:http方法名、请求url和参数,其中参数需要按照统一的规则进行编码和排序,具体规则如下:
      根据参数名称将所有请求参数按照参数名字母的先后顺序逐个比较,进行升序排序(参数名注意区分大小写),然后使用=号连接参数名和编码后的参数值,最后使用_下划线连接经过上述处理后的参数,例如:
      
      param1=encode(value1)_param2=encode(value2)_parma3=encode(value3)
      
      注:a、比如参数名:abcdef和abcDef,abc都是一样的,大写D的asi码在小写d的asi码前面,所以:abcDef排在abcdef前面 b、BaseString中的参数是指所有GET/POST的请求参数,但不包括sig参数,也不包括cookie或HTTP header字段 将http方法名、请求url和参数这三部分用”&”连接起来,然后再进行 encode编码即得到BaseString,使用伪代码描述以上过程如下:
      
      BaseString=encode(httpMethod+"&"+httpUrl+"&"+sorted_query_params.each{|p,v|p+"="+encode(v)}.join("_"))
      
      然后使用BaseString和SecretKey通过HMAC-SHA1算法计算sig,最后将计算 出的sig进行编码,使用伪代码描述签名过程如下: encode (getHmacSHA1Sig(BaseString, SecretKey)) - sig计算示例 例如某个请求的url为http://192.168.6.218:8080/test/jobplan/login,假设使用的ConsumerKey为eqsrje1tlarvvm54fwsjhcb4,使用的SecretKey为ks5t9fpiwmo3oanccu4f6eas,请求中用到的参数如下:
      
      consumer_key – eqsrje1tlarvvm54fwsjhcb4
      hrid - 00016145
      password - 7215ee9c7d9dc229d2921a40e899ec5f
      fields - dspName
      format – xml
      timestamp - 2013-11-20 09:21:17
      
      首先依据上面的算法得出BaseString为:
      
      POST%26http%3A%2F%2F192.168.6.218%3A8080%2FfuannaEP%2Fjobplan%2Flogin%26consumer_key%3Deqsrje1tlarvvm54fwsjhcb4_fields%3DdspName_format%3Djson_hrid%3D00016145_password%3D7215ee9c7d9dc229d2921a40e899ec5f_timestamp%3D2013-11-20%2B09%253A21%253A17
      
      通过BaseString和SecretKey通过HMAC-SHA1算法计算所得的签名如下: dqgTQkET7MxSShy1M4WXbWEKodA%3D 然后就可以发送请求,生成的http 请求链接如下:
      
      http://192.168.6.218:8080/test/jobplan/login?password=7215ee9c7d9dc229d2921a40e899ec5f&consumer_key=eqsrje1tlarvvm54fwsjhcb4&hrid=10057&format=xml&fields=dspName×tamp =2013-11-20 09:21:17&signature=dqgTQkET7MxSShy1M4WXbWEKodA%3D
      

[上海] Android 小伙伴一起来 51offer 玩吧

国内留学行业整体上还是一个传统中介行业,51offer 致力于让人们更方便的通过互联网留学。
留学学生和留学学校虽然五花八门,但既有的留学流程大体一致,相应的盈利模式也比较清晰。但与之对应的如何切实通过产品降低留学门槛,在更多的学生与学校之间建立便利的联系,还有很多好玩的事情可以做。
在互联网留学这一块,51offer 目前可以算国内第一,同时身为一个互联网从业者客观来说,这个行业各公司产品、技术各方面还非常烂(包括我们 51offer)。

要做的事情很多,要决定不做的事情更多,要把不好变好的事情也有不少。
如果你愿意挑战既有现状,喜欢用聪明的方式把事情做好,最好再对这个教育细分领域感兴趣,让我们一起来做些有意义的事。

本人曾铭,ID: mithvv,目前负责公司移动团队。丁香园和百姓网的工作经历让我相信小而扁平的团队组织更加高效。如果前几天耗子和玉伯你站在耗子的一方,嗯,你会喜欢我们目前的合作方式。(ಥ_ಥ,想说玉伯也是我非常尊敬的前辈,他的观点适合支付宝……)

公司状况

  • 刚过 B 轮,两年内完成 5000 万美元融资,自身盈利也一直很好,生死存亡的问题你暂时不用担心
  • 我们基本不加班甚至很难让你加班,因为我们所有的加班(以小时计)都可以调休。我个人是每周三调休带孩子玩,周日上一天班 ;)
  • 公司在上海火车站边上,地铁出站过个路口就到(下雨天忘带伞问题都不大)
  • 技术团队人数目前 50+,谨慎扩张中
  • 技术语言各端不同。后端主要是 Java(大部分),搜索 C++,爬虫 Python,前端和移动各公司应该区别不大,移动这边 Swift 正在一起学习,计划 2.0 正式版出来后用到产品中

招聘要求(不罗列每个职位的技术细节了,意义不大)

  • 招 Android、Java、前端工程师,是工程师或者攻城狮,不招码农
  • 至少作为主程参与过一个产品的完整开发过程
  • 会或者可以快速学会 git,这样我们能快速在 Github 上玩起来
  • 有经验或者了解持续集成、自动化测试及其意义,这样我们在如何快速开发上比较容易达成一致
  • 能把一个技术点用自己的语言阐述清楚,我们的周技术分享期待你的 ShowTime
  • 加分项
    • 践行开源
    • 能跟产品从产品角度阐述清楚技术逻辑
    • 有稳定维护的 Blog 或 Github 帐号
    • 能自由访问国际互联网(如果不会我们教你,这点其实也必备)
    • 对技术有热情,(比如上面一些条件你并不完全满足,但你自认思路清晰,学习能力强,就是要来面一面。我们非常非常欢迎对技术有热情的你!)

薪资待遇

  • 整体 10k - 20k 之间,具体面议,牛人没法封顶

你都看到这了,简历快发给我吧: ming.z[at]51offer.com (24 小时内必有反馈哦)
有意聊天者时间你定,地点我定(工作忙,上海火车站附近即可),咖啡我请 ;)

原始链接:51offer-blog