Title

Android Design 相关

首先看一下要怎么实现水平的虚分割线:
在 drawable 文件下建立 line_dotted_horizontal.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">


<stroke
android:color="#C7B299"
android:dashGap="6px"
android:dashWidth="6px"/>


</shape>

按如下方式引用:

1
2
3
4
5
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@drawable/line_dotted_horizontal"
android:layerType="software"/>

注意:需要添加 android:layerType=”software” 否则在有些设备上仍然显示实线

垂直的虚线

drawable 下新建 line_dotted_vertical.xml

填上参考一下

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:left="-300dp"
android:right="-300dp">

<rotate
android:drawable="@drawable/line_dotted_horizontal"
android:fromDegrees="90"
android:toDegrees="90"
android:visible="true"/>

</item>
</layer-list>

或者

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/line_dotted_horizontal"
android:fromDegrees="90"
android:toDegrees="90"/>


参考列表

Creating horizontal and vertical dotted lines in android
How to create vertical dashed line divider using xml in Android?
Pro tip: Add a dashed line in an Android layout
画个虚线箭头连接引导2个View

Title

Android 相关

CodePath Android Cliffnotes
Android开发中那些相见恨晚的方法. 类. 接口. 工具
C 算法研究学习
最佳实践之Android代码规范

Android经典面试问题
安卓面试题总结

不错的 Android 开发者博客

Java 单例真的写对了么?
Givemepass’s Android 惡補筆記
Android 经验碎片笔记(一)
避免Android中Context引起的内存泄露
Android性能调优利器StrictMode

http://landerlyoung.github.io/ - retrolambda测试报告
JeremyHe - 倍数提高工作效率的Android Studio奇技

Everything every Android Developer must know about new Android’s Runtime Permission

Android开发最佳实践
Android开发经验总结

姚贺鹏的博客 - 记录生活点滴 - RxJava入门使用教程
u3coding - Android中View的滑动冲突及其解决方式


优秀 Android 开发博客

http://www.sunmoonblog.com/
http://zmywly8866.github.io/
佐井 ~ 做有积累的事情
如何阅读Android源码


Android 不错的文章收藏

使用findbugs为自己的代码review
Android6.0 新特性详解
Android App 安全的HTTPS 通信
FlatBuffers 体验-跨平台数据解析库


Android开发 so文件知识

Android开发,不可不知的so文件知识大全


Android开发 Data Binding 数据绑定

Data Binding Library
LyndonChin/MasteringAndroidDataBinding
Android 开发有哪些新技术出现?
Equivalent of ListView.setEmptyView in RecyclerView
How to show an empty view with a RecyclerView?


Android6 权限知识

Everything every Android Developer must know about new Android’s Runtime Permission
Permissionsdispatcher 权限管理注解
Android.com - Permissions Best Practices
Android6.0权限管理-PermissionsDispatcher

聊一聊Android 6.0的运行时权限
Android M 新的运行时权限开发者需要知道的一切
Android工具箱之Android 6.0权限管理
Android 6.0 运行时权限处理

解决Android5.0应用安装失败的问题

1
Installation failed with message INSTALL_FAILED_DUPLICATE_PERMISSION

Android 电源管理

保持设备为唤醒状态
Android中的WakeLock使用



Android 的 AlarmManager

在Android中使用Alarm小结


Android 监听短信

注册信息验证时的用户体验提升
安卓监听新收到短信


Android Service

Android里Service的bindService()和startService()混合使用深入分析
Android中service的使用
Android学习笔记39:Android四大组件之Service

基础总结篇之四:Service全解析
Andorid之BINDSERVICE的使用方法总结
android startservice和bindservice的区别


Android BroadcastReceiver

基础总结篇之五:BroadcastReceiver应用详解


Android Notification

Notification使用


Android Camera

RxCamera, 一个RxJava风格的android camera封装
RxCamera



Android 动画相关

如何高效的学习Android动画
dodola/MetaballLoading
daimajia/AndroidViewAnimations 动画集合


Android Emoji

http://www.cypressnorth.com/wp-content/uploads/2013/05/Google-Hangout-Emojis.jpg
Google-Hangout-Emojis
Emoji Unicode Tables



Android Bitmap

官方文档学习 - Canvas and Drawables
Android样式的开发:drawable汇总篇


Canvas

Andriod中绘(画)图—-Canvas的使用详解


Picasso 框架

Picasso 加载图片转换为圆角或圆形
Draw bitmap on canvas with Picasso

Picasso onBitmapLoaded never called 的问题
Picasso onBitmapLoaded never called
Weak Reference in Java and Android


Android Material Design

chrisbanes/cheesesquare
学习笔记:一起来了解 Android Design Support Library


Android Toolbar

如何使用Toolbar(Actionbar)+DrawerLayout
Android Toolbar样式定制详解

搜索输入框 [Tutorial] Android ActionBar with Material Design and search field

如何将DrawerLayout显示在ActionBar/Toolbar和status bar之间
Android 4.4 实现透明状态栏Translucent system bar(Status bar)

Android使用fitsSystemWindows属性实现–状态栏【status_bar】各版本适配方案
我们为什么要用fitsSystemWindows?
对于MIUI7,网易新闻客户端和知乎客户端是如何实现透明状态栏的?
薄荷Toolbar(ActionBar)的适配方案



Android Design SearchView

Creating a SearchView that looks like the material design guidelines
slightfoot/SearchActivity.java


Android CoordinatorLayout

android CoordinatorLayout使用
Inflating AppBarLayout with Toolbar + TabLayout
How to use a TabLayout with Toolbar inside CollapsingToolbarLayout?


Android Floating Action Bar

Floating Action Buttons



Android 透明状态栏

laobie/StatusBarUtil
Android 状态栏工具类(实现沉浸式状态栏/变色状态栏)


CardView

CardView简述


Android ViewPager…

巧解ViewPager滑动冲突


Android RecyclerView…

RecyclerView使用详解
RecyclerView使用详解 三
RecyclerView的使用技巧

RecyclerView体验简介
RecyclerView使用心得之加载更多和添加头部布局


Android 触摸事件

android自定义布局中的平滑移动
滑轮控件研究四. VelocityTracker的简单研究
Android的Touch系统简介(一)
两分钟彻底让你明白Android中onInterceptTouchEvent与onTouchEvent(图文)!
Android Touch事件分发详解

Android触摸事件传递(上)
Android触摸事件传递(下)

android 事件处理机制总结,ScrollView ViewPager ListView GridView嵌套小结
Android事件传递小结
android事件拦截处理机制详解


Android 开发设计相关blog及源码

Android 开发IM聊天Android Building Group Chat App using Sockets – Part 1

Android 下拉刷新

stormzhang/SwipeRefreshLayoutDemo
Android Swipe Down to Refresh ListView Tutorial
OrangeGangsters/SwipyRefreshLayout

RecyclerView的上拉加载更多

Android程序设计-RecyclerView的使用
How to implement Load More RecyclerView with progressbar at bottom
利用SwipeRefreshLayout和LoadMoreListView实现下拉刷新和上拉加载更多


butterknife

关于开源项目ButterKnife的使用心得
ButterKnife基本使用


Android三种数据存储方式

Android三种数据存储方式



自定义 View

教你搞定Android自定义View
教你搞定Android自定义ViewGroup


Android 应用使用自定义字体

Is it possible to set font for entire Application?
GitHub chrisjenx/Calligraphy
GitHub vsvankhede/easyfonts
GitHub shellum/fontView
How to use custom fonts in Android apps (and not get fat)

使用 Calligraphy 自定义字体

引入依赖

1
2
3
dependencies {
compile 'uk.co.chrisjenx:calligraphy:2.1.0'
}

在 Application 中添加配置项

1
2
3
4
5
6
7
// Calligraphy 自定义字体
CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
.setDefaultFontPath("carbon/FZLT.ttf")
.setFontAttrId(R.attr.fontPath)
//.addCustomViewWithSetTypeface(CustomViewWithTypefaceSupport.class)
//.addCustomStyle(TextField.class, R.attr.textFieldStyle)
.build());

在需要使用自定义字体的 Activity 或自定义基类 BaseActivity 中复写以下方法:

1
2
3
4
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}

于是全局的程序应该都是使用自定义字体渲染了
注意事项:动态加载的文字没有被自动渲染成自定义字体,需要在加载后手动设置;引入自定义字体后的程序性能可能会受到影响,继续研究


Android Java8

Java 8+Android M新特性总结(简略版)
Android Studio 中使用Java 8 特性


开源表情库

rockerhieu/emojicon
arvida/emoji-cheat-sheet.com


不错的开源库

Yalantis/Phoenix
Yalantis/Taurus

其它

Android Material Design working with Tabs
Android 开发中如何信任自签名 Https 证书

专业收藏开发者相关博客

Gin’s Blog
机器学习十大算法

C4.5算法
Kmeans算法
朴素贝叶斯算法
K最近邻分类算法(KNN)
EM最大期望算法
PageRank算法
AdaBoost
Apriori算法
SVM支持向量机
CART分类与回归树

Title

Android之EventBus的使用

EventBus是一款针对Android优化的发布/订阅事件总线.

主要功能是替代(Intent,Handler,BroadCast)在(Fragment,Activity,Service)线程之间传递消息.
优点是开销小,代码更优雅.以及将发送者和接收者解耦.
当在开发一些庞大的的项目时,模块比较多,这个时候为了避免耦合度和保证 APP 的效率
会用到 EventBus 等类似的事件总线处理模型.
这样可以简化一些数据传输操作,保证APP的简洁,做到高内聚. 低耦合.

参考
Android之EventBus的使用
EventBus使用
Google-Guava-EventBus源码解读
Android开发 使用EventBus管理事件传递消息
Android学习系列(43)–使用事件总线框架EventBus和Otto
事件总线 —— otto的bus和eventbus对比分析
Android事件总线还能怎么玩?-AndroidEventBus
快速Android开发系列通信篇之EventBus
EventBus, otto, LocalBroadcast的选择

EventBus GitHub: https://github.com/greenrobot/EventBus

1
Gradle: compile 'de.greenrobot:eventbus:2.4.0'

基本用法

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
 // 分订阅,注册,发布,取消注册
EventBus.getDefault().register(this);
EventBus.getDefault().register(new MyClass());
// 三个参数分别是:消息订阅者(接收者),接收方法名,事件类
EventBus.getDefault().register(this, "setTextA", SetTextAEvent.class);
// 取消注册
EventBus.getDefault().unregister(this);
EventBus.getDefault().unregister(new MyClass());
// 订阅处理数据
public void onEventMainThread{}
public void onEvent(AnyEventType event){}
onEventPostThread, onEventBackgroundThread, onEventAsync
// 发布
EventBus.getDefault().postSticky(new SecondActivityEvent("Message From SecondActivity"));
EventBus.getDefault().post(new ChangelmgEvent(1));
Event
public class Event extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注册
EventBus.getDefault().register(this);
}
// 发送
private void postData() {
String str = "Hello World";
// 发送数据,可以是一个类型或者一个类
EventBus.getDefault().post(str);
}
// 接收
public void onEvent(String str) {}
// 接收
public void onEventMainThread(String str) {}
// 接收
public void onEventPostThread(String str) {}
// 接收
public void onEventBackgroundThread(String str) {}
// 接收
public void onEventAsync(String str) {}
@Override
protected void onDestroy() {
super.onDestroy();
// 取消注册
EventBus.getDefault().unregister(this);
}
}

使用方法

  1. onEvent
  2. onEventMainThread
  3. onEventBackgroundThread
  4. onEventAsync

这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:
告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的:

onEvent:

如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行
也就是说发布事件和接收事件线程在同一个线程.
使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟.

onEventMainThread:

如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,比如开启子线程远程加载数据后更新UI,使用其它订阅函数(onEventBackground)会报错

1
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中更新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的.

onEventBackground:

如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的
那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的
那么onEventBackground函数直接在该子线程中执行.

onEventAsync:

使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.

使用重载机制进行收发不同对象
1
2
3
4
5
6
7
8
9
public void onEventMainThread(FirstEvent event) {  
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventMainThread(SecondEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEvent(ThirdEvent event) {
Log.d("harvic", "OnEvent收到了消息:" + event.getMsg());
}

Title

使用Chrome来调试你的Android App

Github
使用Chrome来调试你的Android App
Facebook Stetho 使用学习
Android调试工具 stetho

####

添加依赖

1
2
3
4
5
6
compile 'com.facebook.stetho:stetho:1.2.0'

//调试 HttpURLConnection 建立的网络请求
compile 'com.facebook.stetho:stetho-urlconnection:1.2.0'
//调试 OKHttp 建立的网络请求
compile 'com.facebook.stetho:stetho-okhttp:1.2.0'

在Application中启动配置

1
2
3
4
Stetho.initialize(Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
.build());

使用HttpUrlConnection调试网络请求,导入官方的一个网络请求类 Networker [stetho/stetho-sample/src/main/java/com/facebook/stetho/sample/Networker.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
//Post请求
public String sendHUCTPost(Context context, String url, Map<String, String> params) {
PrintWriter out = null;
BufferedReader in = null;
String response = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
//HttpURLConnection

StringBuffer requestParams = new StringBuffer();
if (params != null && params.size() > 0) {

//conn.setDoOutput(true); // true indicates POST request

// creates the params string, encode them using URLEncoder
Iterator<String> paramIterator = params.keySet().iterator();
while (paramIterator.hasNext()) {
String key = paramIterator.next();
String value = params.get(key);
requestParams.append(URLEncoder.encode(key, "UTF-8"));
//判断一下,防止传空值 null 程序[URLEncoder.encode(value, "UTF-8"]意外崩溃,不能用 value.isEmpty() 判断是否已赋值
if (value != null)
requestParams.append("=").append(URLEncoder.encode(value, "UTF-8"));
requestParams.append("&");
}

// sends POST data
// OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
// writer.write(requestParams.toString());
// writer.flush();
// 获取URLConnection对象对应的输出流
// out = new PrintWriter(conn.getOutputStream());
// // 发送请求参数
// out.print(requestParams);
// // flush输出流的缓冲
// out.flush();
}
//byte[] postBytes = requestXML.getBytes("UTF-8");
//byte[] postBytes = new byte[]{ (byte) 0x65, (byte)0x10, (byte)0xf3, (byte)0x29};
// 将 StringBuffer 转换为 byte[] 数组 - 参考 http://stackoverflow.com/questions/8005327/convert-a-stringbuffer-to-a-byte-array-in-java
byte[] postBytes = String.valueOf(requestParams).getBytes();
Networker.HttpRequest request = Networker.HttpRequest.newBuilder()
.friendlyName("ShangYun")
.method(Networker.HttpMethod.POST)
.url(url)
.body(postBytes)
.build();
Networker.get().submit(request, mStoreRssResponse);

} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return response;
}

//Get请求
public void sendOneReq() {
Networker.HttpRequest request = Networker.HttpRequest.newBuilder()
.friendlyName("Bai Du")
.method(Networker.HttpMethod.GET)
.url("http://www.baidu.com")
.build();
Networker.get().submit(request, mStoreRssResponse);
}

private final Networker.Callback mStoreRssResponse = new Networker.Callback() {
@Override
public void onResponse(Networker.HttpResponse result) {
if (result.statusCode == 200) {
String page22222=new String(result.body);
System.out.println("page22222 : "+page22222);
}else{
System.out.println("result.statusCode is not 200");
}
}
@Override
public void onFailure(IOException e) {
System.out.println("onFailure");
}
};

最后在Chrome 浏览器输入 chrome://inspect/#devices 调试

参考列表
HTTP post body lost spuriously (HttpURLConnection & Jetty)
Post byte array in parameter
Convert a StringBuffer to a byte Array in Java

Title

Android 调试工具

Android调试自测工具01 (Hugo. Timber. Scalpel)
发布版包关闭所有log日志 Remove all debug logging calls before publishing: are there tools to do this?
【团队分享】刀锋铁骑:常见Android Native崩溃及错误原因

使用 CrashWoodpecker 捕捉开发过程中运行时崩溃异常

比 Logcat 更加美观易用的异常信息接收器: CrashWoodpecker
square/leakcanary

捕获异常

Android应用崩溃后异常捕获并重启
崩溃和异常 - Android SDK v4

Title

Conversations 相关

Conversations.im 官网
GitHub - Conversations

分享位置插件
siacs/ShareLocationPlugin
kriztan/OpenShareLocationPlugin

Conversation 迁移指南 05-06 v1.12.1

  • 将 Conversation/lib/MemorizingTrustManager 复制到本项目 ./libs/ 下,注意在项目依赖加入模块
  • 将 C:\Users\yafei\AppDeveloperWorkspace\Android\Conversations-master\src\ 下的 [playstore free] 文件夹拷贝到同等级目录下覆盖
  • 复制 Conversations 项目下 build.gradle 的依赖项,修改为如下部分添加到 项目 CoamAndroid/app/build.gradle 下,修改参见 CoamAndroid/app/build.gradle 中配置
  • 复制 Conversation/src/main/jave 下的整个项目文件夹结构 (eu.siacs.conversations) 到 本项目 CoamAndroid/app/main/src 下,修改部分为
1
2
[eu.siacs.conversations.R;] 全部替换为 [import com.coam.shangyun.R;]
修改 eu.siacs.conversations.ui.widget.Switch 继承自 com.coam.plugins.switchbutton.SwitchButton
  • 将 [Conversation/src/main/java/AndroidManifest.xml] 下的 application 迁移到本项目 [CoamAndroid/app/src/main/java/AndroidManifest.xml] ,修改部分参见 [CoamAndroid/app/src/main/java/AndroidManifest.xml]

Conversation/src/main/res/* 下的复制

  • 将 [Conversation/src/main/res/drawable] 全部复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/drawable]
  • 将 [Conversation/src/main/res/xml/] 目录文件全部复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/xml/]
  • 将 [Conversation/src/main/res/raw/] 目录文件全部复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/raw/]
  • 将 [Conversation/src/main/res/layout/] 目录文件全部添加 conversations_ 前缀 并复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/layout/]
  • 将 [Conversation/src/main/res/menu/] 目录文件全部添加 conversations_ 前缀 并复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/menu/]
  • 将 [Conversation/src/main/res/values/] 目录文件全部添加 conversations_ 前缀 并复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/values/]
  • 将 [Conversation/src/main/res/values-] 目录文件全部复制到本项目相应文件夹下 [CoamAndroid/app/src/main/res/values-],其中与本项目有冲突部分 values-v21/dimens.xml 和 values-v21/themes.xml 前面添加 conversations_ 前缀

以上总结:

1.将 Conversation/src/main/res/ 下的所有除了 [layout layout-w945dp menu values values-v21 values-w360dp values-w384dp values-w585dp values-w945dp] 三个文件夹外,全部全盘复制到 Conversation/src/main/res/
2.[layout layout-w945dp menu values values-v21 values-w360dp values-w384dp values-w585dp values-w945dp] 三个文件夹下的所有文件前面统一添加 conversations_ 前缀[通过Python脚本程序 ConversationsMigrate.py],拷贝到 Conversation/src/main/res/

以上修改总结,最终方案:

1.将 Conversation/src/main/res/ 下的所有除了 [drawable-hdpi drawable-mdpi drawable-xhdpi drawable-xxhdpi drawable-xxxhdpi] 几个文件夹外,
所有文件夹的子文件前面统一添加 conversations_ 前缀[通过Python脚本程序 ConversationsMigrate.py],拷贝到 Conversation/src/main/res/* 下

复制 app\src\playstore\java\eu\siacs\conversations\services* 下的三个文件 [InstanceIdService.java PushManagementService.java PushMessageReceiver.java] 到 app\src\main\java\eu\siacs\conversations\services* 下

  • 修改部分

将 conversations_styles.xml 中以下样式注释掉哦

1
2
3
4
5
6
7
8
9
10
11
12
13
    <style name="MD">
<!-- <item name="animationVelocity">6</item>
<item name="insetBottom">16dp</item>
<item name="insetTop">16dp</item>
<item name="insetLeft">16dp</item>
<item name="insetRight">16dp</item>
<item name="measureFactor">1.4</item>
<item name="offDrawable">@drawable/switch_back_off</item>
<item name="onDrawable">@drawable/switch_back_on</item>
<item name="thumbDrawable">@drawable/switch_thumb</item>
<item name="thumb_margin">-17dp</item>
<item name="android:padding">16dp</item>-->
</style>

  • ui/ConversationAdapter.java Line 55

    1
    2
    //swipeableItem.setBackgroundColor(c);
    swipeableItem.setBackgroundColor(ContextCompat.getColor(activity, c));
  • ui/XmppActivity.java Line 733

    1
    2
    //trustToggle.setChecked(trust.trusted(), false);
    trustToggle.setChecked(trust.trusted());

Telegram 相关

https://my.telegram.org
google-services.json

DrKLO/Telegram
Android Telegram App –> java.lang.UnsatisfiedLinkError: No implementation found for void
Telegram NDK build : Command not found
NDK Downloads
How to compile telegram jni folder

Dove - 专为群组而建
tinfinite/dove-android
Dove
爲甚麼李笑來的新項目「Dove」基於 GPL v2 證書的「Telegram」,卻可以不讓用戶獲得它的源代碼?

编译 jni 的 so 文件

1
2
3
4
5
6
7
8
edit C:\cygwin64\home\yafei\.bashrc
add export ndkbuild=/cygdrive/C/TDDownload/android-ndk-r11b-windows-x86_64/android-ndk-r11b/ndk-build.cmd
open Cygwin64 cmd
cd /cygdrive/C/Users/yafei/AppDeveloperWorkspace/Android/CoamApp/Telegram-master/TMessagesProj/jni
$ $ndkbuild
=or win cmd==========
cd C:\Users\yafei\AppDeveloperWorkspace\Android\CoamApp\Telegram-master\TMessagesProj\jni
C:\TDDownload\android-ndk-r11c-windows-x86_64\android-ndk-r11c\ndk-build.cmd

开发约定规范事项

  • 所有 [CoamAndroid/app/main/src/eu.siacs.conversations/*] 下的代码,增加部分标注以 [//M +++>] 开头,以 [//M +++<] 结尾;删减部分标注以 [//M —>] 开头,以 [//M —<] 结尾

Title

Square OkHttp 开发指南

OkHttp文档

同步get

下载一个文件,打印他的响应头,以string形式打印响应体.
响应体的 string() 方法对于小文档来说十分方便. 高效.但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中.
对于超过1MB的响应body,应使用流的方式来处理body.

private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
}

异步get

在一个工作线程中下载文件,当响应可读时回调Callback接口.读取响应时会阻塞当前线程.OkHttp现阶段不提供异步api来接收响应体.

private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }

      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
}

提取响应头

典型的HTTP头 像是一个 Map :每个字段都有一个或没有值.但是一些头允许多个值,像Guava的Multimap.例如:HTTP响应里面提供的Vary响应头,就是多值的.OkHttp的api试图让这些情况都适用.
当写请求头的时候,使用header(name, value)可以设置唯一的name. value.如果已经有值,旧的将被移除,然后添加新的.使用addHeader(name, value)可以添加多值(添加,不移除已有的).
当读取响应头时,使用header(name)返回最 后出现的name. value.通常情况这也是唯一的name. value.如果没有值,那么header(name)将返回null.如果想读取字段对应的所有值,使用headers(name)会返回一个list.
为了获取所有的Header,Headers类支持按index访问.

private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
}

Post方式提交String

使用HTTP POST提交请求到服务.这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown.因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB).

public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

Post方式提交流

以流的方式POST提交请求体.请求体的内容由流写入产生.这个例子是流直接写入Okio的BufferedSink.你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取.

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

Post方式提交文件

以文件作为请求体是十分简单的.

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

Post方式提交表单

使用FormEncodingBuilder来构建和HTML

标签相同效果的请求体.键值对将使用一种HTML兼容形式的URL编码来进行编码.

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    RequestBody formBody = new FormEncodingBuilder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

Post方式提交分块请求

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容.多块请求体中每块请求都是一个请求体,可以定义自己的请求头.这些请求头可以用来描述这块请求,例如他的Content-Disposition.如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中.

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"title\""),
            RequestBody.create(null, "Square Logo"))
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"image\""),
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

使用Gson来解析JSON响应

Gson是一个在JSON和Java对象之间转换非常方便的api.这里我们用Gson来解析Github API的JSON响应.
注意:ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体.默认是UTF-8.

private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/gists/c2a7c39532239ff261be")
        .build();
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
      System.out.println(entry.getKey());
      System.out.println(entry.getValue().content);
    }
}

static class Gist {
    Map<String, GistFile> files;
}

static class GistFile {
    String content;
}

响应缓存

为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制.这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容.
一个缓存目录同时拥有多个缓存访问是错误的.大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了.否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃.
响应缓存使用HTTP头作为配置.你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持.你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600.

private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient();
    client.setCache(cache);
}

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

    String response1Body = response1.body().string();
    System.out.println("Response 1 response:          " + response1);
    System.out.println("Response 1 cache response:    " + response1.cacheResponse());
    System.out.println("Response 1 network response:  " + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

    String response2Body = response2.body().string();
    System.out.println("Response 2 response:          " + response2);
    System.out.println("Response 2 cache response:    " + response2.cacheResponse());
    System.out.println("Response 2 network response:  " + response2.networkResponse());

    System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
}

扩展

在这一节还提到了下面一句:
There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.

我不是很懂cache,平时用到的也不多,所以把Google在Android Developers一段相关的解析放到这里吧.

Force a Network Response
In some situations, such as after a user clicks a ‘refresh’ button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:
connection.addRequestProperty(“Cache-Control”, “no-cache”);
If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 instead:

connection.addRequestProperty(“Cache-Control”, “max-age=0”);
Force a Cache Response

Sometimes you’ll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:

try {
     connection.addRequestProperty("Cache-Control", "only-if-cached");
     InputStream cached = connection.getInputStream();
     // the resource was cached! show it
  catch (FileNotFoundException e) {
     // the resource was not cached
  }
}
This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:

int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);

以上信息来自:HttpResponseCache - Android SDK | Android Developers

取消一个Call

使用Call.cancel()可以立即停止掉一个正在执行的call.如果一个线程正在写请求或者读响应,将会引发IOException.当call没有必要的时候,使用这个api可以节约网络资源.例如当用户离开一个应用时.不管同步还是异步的call都可以取消.
你可以通过tags来同时取消多个请求.当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签.之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call.

private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
        call.cancel();
        System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    try {
      System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
      Response response = call.execute();
      System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf("%.2f Call failed as expected: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, e);
    }
}

超时

没有响应时使用超时结束call.没有响应的原因可能是客户点链接问题. 服务器可用性问题或者这之间的其他东西.OkHttp支持连接,读取和写入超时.

private final OkHttpClient client;

public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient();
    client.setConnectTimeout(10, TimeUnit.SECONDS);
    client.setWriteTimeout(10, TimeUnit.SECONDS);
    client.setReadTimeout(30, TimeUnit.SECONDS);
}

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    Response response = client.newCall(request).execute();
    System.out.println("Response completed: " + response);
}

每个call的配置

使用OkHttpClient,所有的HTTP Client配置包括代理设置. 超时设置. 缓存设置.当你需要为单个call改变配置的时候,clone 一个 OkHttpClient.这个api将会返回一个浅拷贝(shallow copy),你可以用来单独自定义.下面的例子中,我们让一个请求是500ms的超时. 另一个是3000ms的超时.

private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
        .build();

    try {
      Response response = client.clone() // Clone to make a customized OkHttp for this request.
          .setReadTimeout(500, TimeUnit.MILLISECONDS)
          .newCall(request)
          .execute();
      System.out.println("Response 1 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 1 failed: " + e);
    }

    try {
      Response response = client.clone() // Clone to make a customized OkHttp for this request.
          .setReadTimeout(3000, TimeUnit.MILLISECONDS)
          .newCall(request)
          .execute();
      System.out.println("Response 2 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 2 failed: " + e);
    }
}

处理验证

这部分和HTTP AUTH有关.
相关资料:HTTP AUTH 那些事 - 王绍全的博客 - 博客频道 - CSDN.NET
OkHttp会自动重试未验证的请求.当响应是401 Not Authorized时,Authenticator会被要求提供证书.Authenticator的实现中需要建立一个新的包含证书的请求.如果没有证书可用,返回null来跳过尝试.

public List<Challenge> challenges()
Returns the authorization challenges appropriate for this response's code. If the response code is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the response code is 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise this returns an empty list of challenges.
当需要实现一个Basic challenge, 使用Credentials.basic(username, password)来编码请求头.

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    client.setAuthenticator(new Authenticator() {
      @Override public Request authenticate(Proxy proxy, Response response) {
        System.out.println("Authenticating for response: " + response);
        System.out.println("Challenges: " + response.challenges());
        String credential = Credentials.basic("jesse", "password1");
        return response.request().newBuilder()
            .header("Authorization", credential)
            .build();
      }

      @Override public Request authenticateProxy(Proxy proxy, Response response) {
        return null; // Null indicates no attempt to authenticate.
      }
    });

    Request request = new Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}