Android ProGuard

根据官网的注解:

ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier.

原文非常简洁,翻译过来也很容易明白,ProGuard 能对 Java 字节码文件进行压缩、优化、混淆和预验证。ProGuard 几个典型的用法就是:1. 缩减应用的大小;2. 移动设备优化代码; 3. 防止恶意反编译或者篡改程序。

ProGuard 相较于其他 Java 混淆器优势的地方在于

  • compact template-based configuration
  • fast
  • command-line tool with an optional graphical user interface

ProGuard 可以删除 Java 代码中无用的类、字段、方法和属性,并混淆代码 ,从而减小 APK 的大小,打包 apk 上线时,代码是一定要混淆的,有些情况下,部分代码是不能混淆的,否则就会导致程序功能不能正常运行

配置 ProGuard

ProGuard 四大功能的工作顺序是压缩->优化—>混淆—>预验证。

压缩:检测和删除项目中没有使用的类、字段、方法和属性。如果存在两个类互相调用,但项目实际上并没有使用到这两个类,这两个类也会被删除。

优化:优化代码的复杂调用,使代码运行顺序更为合理。

混淆:依据用户的配置,将代码的包名、类名、方法名和变量名等改成 a,b,c 等无意义的名称。Java 源代码编译后产生二进制 class 文件,这个 class 文件可以反编译成 Java 代码。反编译后生成的 Java 代码除了原来的注释外,其他代码比如变量名、类名和方法名等基本都能看到,和 Java 源代码几乎没有区别。为了防止项目代码被泄露,我们需要把项目代码中的包、类、方法和变量名等 Java 元素的名称改成无意义的名称。这个过程就是混淆。混淆之后的代码只是在一些名称上有修改,对代码的结构没有影响,所以混淆后的代码依然可以运行。项目经混淆后并不是说代码就不能被反编译了,只是反编译混淆后的项目后看到的代码将不是源码,变量名变得不再能一眼看懂其意思,此时想弄懂原有的项目代码架构很难。在混淆的过程中,不影响正常工作的信息将永久删除,使得反编译之后的代码变得更加难以理解。

预验证:在 Java 平台对处理后的代码进行预检,保证代码能够正常运行。这一步在 Android 项目中不需要。

Android Studio 已经集成了 ProGuard 工具。对 Android 项目进行混淆时,我们只需修改一些配置即可。 随便打开一个 Android 项目,在 app 目录下的 build.grade 文件中可以看到如下代码

buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }
}

以上述代码为例,minifyEnabled 置为 true 表示在 release 环境下启用 ProGuard 工具。在打包程序时,这一项必须置为 true, 否则 release 打出的包将没有压缩、优化、混淆。

proguardFiles 命令后面的两个文件是 ProGuard 的配置文件,启用 ProGuard 后, ProGuard 对项目的处理将遵循这两个文件的配置。proguard-android.txt 是 Android Studio 集成 ProGuard 工具时自带的 ProGuard 配置文件,该文件在 Android SDK tools/proguard/ 目录下,proguard-rules.pro 是开发项目时由程序员添加的配置文件。

官网建议,为了更好地压缩代码,Android 还在SDK同目录中提供了 proguard-android-optimize.txt 文件,它包含了相同的规则,但是提供了额外针对 bytecode 的分析优化。而与此同时 proguard-android.txt 是不提供代码优化的。

proguard-rules.pro 文件可以在当前 Android 项目 app 目录下找到。初始时此文件是空白的,开发者可以在这个文件中添加自己项目的 ProGuard 配置,不要也不能在 proguard-android.txtproguard-android-optimize.txt 中添加自定义规则。proguardFiles 后面可以加入多个文件名,意味着 ProGuard 工作会遵循多个文件的配置。

当在项目中配置了 minifyEnabled 为 true 时,如果不配置 ProGuard 文件,ProGuard 会混淆所有代码。而有些情况下,有些代码是不能混淆的,否则就会导致程序出错。常用的不能混淆的代码如下:

  1. 使用反射的地方
  2. JNI 方法
  3. 系统接口(Framework 层下所有的类),Manifest 中声明的所有类,四大组件,Application 子类不混淆
  4. xml 中声明的所有资源名
  5. 使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则
  6. Parcelable的子类和Creator静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
  7. 使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
  8. 有用到WebView 的 JS 调用也需要保证写的接口方法不混淆

Android Studio 启用 ProGuard 工具后,会默认不混淆系统接口、Manifest 中声明的所有类和 xml 中声明的所有资源名,因此这些情况下不需要手动配置 ProGuard 文件。而其他情况下不应该混淆的代码就需要对 ProGuard 文件进行配置。

常用的 ProGuard 配置语法

从给定的文件中读取配置参数

-include {filename}

保护给定的可选属性,例如 Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,Annotation,EnclosingMethod

-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
            SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

例如,在代码中使用了反射,加入以下混淆规则:

-keepattributes Signature,EnclosingMethod

保护指定的类文件和类的成员(类中的方法和成员变量都是类的成员,下同)

-keep {Modifier} {class_specification}

例如,保护指定包下面的类:

-keep class info.einverne.jsonobject.** { *; }  // 不混淆 info.einverne.jsonobject 包下面类和所有类成员变量
-keep class info.einverne.jsonobject.personinfo        // 不混淆具体类

保护指定类的成员

-keepclassmembers {modifier} {class_specification}

例如,保护继承了 Serializable 接口的类:

-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

或者,不混淆 enum 类:

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(Java.lang.String);
}

保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。

-keepclasseswithmembers {class\_specification}

例如,不混淆 native 方法:

  -keepclasseswithmembernames class * {
      native <methods>;
  }

如果不在压缩步骤中删除,则保护指定的类和类的成员的名称

-keepnames {class\_specification}

如果不在压缩步骤中删除,保护指定的类的成员的名称

-keepclassmembernames {class\_specification}

如果不在压缩步骤中删除,保护指定的类和类的成员的名称

-keepclasseswithmembernames {class\_specification}

忽略警告,避免打包时某些警告出现

-ignorewarnings

是否使用大小写混合

-dontusemixedcaseclassnames

其他选项

-dontshrink    不压缩(不配的话默认是使用 ProGuard 的压缩功能)
-dontoptimize    不优化(不配的话默认是使用 ProGuard 的优化功能)
-dontobfuscate    不混淆(不配的话默认是使用 ProGuard 的混淆功能),需要注意 release 版本不要启用此选线
-dontusemixedcaseclassnames    混淆时不会产生形形色色的类名

ProGuard 配置通配符

? 匹配除包分隔符外的任意单个字符
*  匹配除包分隔符外的所有符号
** 匹配多个字符(任意)
%  匹配所有基本类型(不包含 void)
***  匹配任何类型
!  不匹配
<init>   匹配所有构造函数
<fields>  匹配所有字段
<methods> 匹配所有方法

reference


2016-11-15 Android , Java , AndroidDev

Android Http 调试及抓包

Android 开发中可能需要对网络请求进行调试,这时刻需要对程序发出的请求进行抓包。下面记录一下之前调试使用过的工具和使用方法,以便于未来快速查询。

Charles

Charles 是 Mac 下非常好用的抓包工具,不仅是对 Android,而 iOS,或者其他局域网能够使用本机http代理的任何设备都能够进行抓包的操作。

Charles实现对 Https 进行抓包,使用的原理就是中间人技术(man-in-the-middle)。Charles会动态生成一个使用自己根证书签名的证书,Charles接收web服务器的证书,而客户端浏览器/客户端 接收Charles生成的证书,以此客户端和Charles之间建立Https连接,Charles和Web服务器之间建立Https连接,实现对Https传输信息的抓包。

具体原理就是,通过 HTTP 代理,将手机的流量转到 Charles ,在通过 Charles 分析。Charles是一个抓包工具,支持抓取 HTTP、HTTPS 协议的请求。

优缺点

优点:

  • 实时抓包
  • 纯界面,简单
  • 设置一次,终生受益
  • 手机不需要Root

缺点:

  • 授权很贵

配置

  • 安装包

官网地址: http://www.charlesproxy.com/

安装完成之后需要配置

  • 菜单 Proxy > Proxy Settings, 选择端口 8888, 勾选 “ Enable transparent HTTP proxying”
  • 菜单 Proxy > SSL Proxy Settings… ,选择 “Enable SSL Proxying”, 然后添加,在 HOST 中填入 “.” , Port 中填 443
  • 在 Help 菜单中,选择 ” SSL Proxying”,分别安装 Mac 端证书,Mobile 端证书,需要在 KeyChain 钥匙串访问中始终信任 Charles 证书,而手机端也要通过 Charles 给出的网络地址来下载证书并安装,Android 相对容易,iOS 比较麻烦。

安装完桌面证书之后,在手机端配置

  • 在 WIFI 面板中,长按 WIFI,打开高级设置,然后设置 HTTP 代理
  • 选择 Manual, 代理地址为,本机局域网 IP,端口 8888

Configure your device to use Charles as its HTTP proxy on ip:8888, then browse to chls.pro/ssl to download and install the certificate.

然后在 Charles 中就能够检测到手机上的请求,在 Charles 界面中会有对话框弹出“A connection attempt to Charles has been made from the host xx.xx.xx.xx. You should allow …. “ 等等,选择允许即可,Charles 会自动分析请求参数及返回。

界面

左侧为所有请求列表,右侧为具体请求数据,可以点击 Request 和 Response 来查看请求和回复的数据。

tcpdump

使用 tcpdump 工具,需要准备:

优缺点

  • 操作简单,但是需要熟悉 adb 命令
  • 手机数据包完整抓取

缺点:

  • 只能针对 Android 手机
  • 不能实时抓取
  • 电脑端需要安装 Wireshark

步骤

检查手机连接

adb devices

将 tcpdump 推到 Android 设备

adb push /path/to/tcpdump /sdcard/

进入手机,将 tcpdum 移动位置

adb shell

su

mv /sdcard/tcpdump /data/local/

修改权限

chmod 777 /data/local/tcpdump

执行抓包

/data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap
# "-i any": listen on any network interface 
# "-p": disable promiscuous mode (doesn't work anyway) 
# "-s 0": capture the entire packet 
# "-w": write packets to a file (rather than printing to stdout)   ... do whatever you want to capture, then ^C to stop it ...

使用 Ctrl + C 停止,然后将 pcap 文件拉取到本地

adb pull /sdcard/capture.pcap ~/wiresharp/

然后使用本地安装的 wiresharp 应用来分析

Linux 下可以使用

sudo apt-get install wireshark

然后使用

wireshark capture.pcap

图片展示

wireshark

更加详细的参数及使用可以参考1

其他抓包工具

可以实现抓包的方式有很多,正如上面所讲可以直接在 设备中运行 tcpdump,也可以将流量通过 HTTP 代理转到Charles 抓包,当然如果有条件甚至可以在网卡上实时抓包。

工具名字 支持平台 授权
Fiddler 只支持Windows  
Charles Win/Mac/Linux 三大平台 单用户授权$50, 免费使用30天
Wireshark Win/Mac 只能查看信息  
mitmproxy Mac/Linux 命令行工具 free

reference


2016-11-14 Android , AndroidDev

文件夹和标签的区别使用

文件夹和标签最直观的一个特点就是,一件物品只能属于一个文件夹,但是可以有很多标签Tag。这件物品可以是一封邮件,一个文件,或者是一篇文档。如果为了更加直观的看,可以做个比喻,当收到一件快递时,打开包装看完内容,可以选择放到一个抽屉里,也可以贴上各种颜色的标签纸。而这里的的“放入一个抽屉”就是放入一个文件夹,而标签就是各种标签纸。文件夹是设置方便我们分类保存使用,而标签是为了便于我们检索和记忆。因此文件夹分类的方式,物品的位置移动了,而标签可以在物品原地加上。

标签的历史

Gmail 最早在邮件系统中使用标签 label,而我也是初次在 Gmail 中体会到标签的好用。而此后接触的大大小小的工具都有着类似的概念,笔记类软件 Evernote 和 Wiznote 他们都有完整的文件夹和标签系统,更甚至最近 Google Photos 的更新让相册对应了文件夹,而其中的人物,地理等等信息对应了标签。而网络中每一篇 Blog 都有对应的 Category 和很多的 Tag。Flickr 中的每一张照片都只能属于一个相册,但是可以有很多的标签。

如何使用文件夹和标签

至于如何使用这两者,网上 有句话说的很明白,“文件夹是给自己看的,而便签是给搜索引擎看的。” 因此

  • 文件夹应当用来分类消息,并使得分类数尽量降低,并且使得每个分类中的信息尽量均衡,如果有子分类,尽量有清晰的父子关系
  • 而对于标签系统,每个物品的标签应当尽量的丰富,标签数应当尽可能多。但是,同义的标签应该尽量少。

而就我自己的对于标签的使用来看,对于每个标签的使用也应当尽量的考虑,尤其现在全文检索能力的增强,标签一定程度上已经弱化为文件夹的作用。并且是增强型文件夹,原始的文件夹因为分类的排他性,只能产生一对一的映射,而标签可以产生一对多的映射。

瞎想

写到这里,突然想到 Google 对于 Google+ 所做的尝试,最早 Google+ 推出的时候, Circle 这个概念广受好评,而圈子这一功能不也是对联系人的扩充嘛? 原本一个联系人只能属于我自己的一个分组,比如家人,好友,同学,而这也是我一直对我联系人进行的分类,而 Google+ 做所的圈子功能让一个人可以同时从属与不同圈子,作为现实的抽象同样也很好理解,我的同学,可能成为好友,或许也可能成为家人。而圈子的功能就完美的解决了从属的问题,而细想,圈子的功能不就是标签的功能嘛?更甚至在网页版 Contact 上,联系人侧边栏的设计和 Gmail 中文件夹和标签的设计相似。至于最后逐渐弱化对圈子功能的支持,可能也是用户的妥协,只有对整个系统熟悉的人才会不惜花费时间去分类标签,因为建立起这一套分类标准之后对整个交流和生活都能节省很多时间,而对于新人用户这本身便是一件非常耗时和复杂的事情。

reference


2016-11-07 Folder , tag , lable , Gmail , WizNote , Evernote , GooglePlus , Google

离线文档查看工具

在离线状态下看文档利器,速度非常迅速。最早知道是 Mac 下的 Dash,但是后来转 Linux,就想着要一个同类型的工具,没想到真的发现了更加好用的 Zeal。但就离线看文档而言,Zeal不仅轻量并且快速,

Mac 下 Dash

去除快速查看文档之外,还有另一个非常实用的功能就是 Code snippets,并且和一些 IDE,编辑器结合,能够通过少量的快捷键到达非常快速的输入。从另外一方面想,作为本地代码片段管理似乎也是不错的选择。

Dash 比较突出的几大功能:

  • 自动保存状态,每次关闭都会记录状态,下次打开时会自动定位
  • 设备间同步,包括安装的文档,个人信息及收藏代码片段
  • 开发人员可以给公开的或者私有的文档或者例子添加评论
  • 对市面上常见的IDE都有插件支持

Linux 下 Zeal

Zeal 有如下几个功能:

  • 生成自己的技术文档
  • 免费并且开源,文档内容源自 Dash
  • 有 Vim,Emacs,Sublime Text 等插件

Linux 下安装

详情可以查看官方文档。 推荐使用 PPA 安装,官方更新比较及时

$ sudo add-apt-repository ppa:zeal-developers/ppa
$ sudo apt-get update
$ sudo apt-get install zeal

遇到 Bug 或者任何问题都可以到 GitHub 提出 issue:https://github.com/zealdocs/zeal/issues

Zeal 安安稳稳的做好文档查看就已经很好了。

配置

Linux 下默认配置地址在:~/.config/Zeal/Zeal.conf

离线文档地址:~/.local/share/Zeal/Zeal/docsets

Chrome下离线文档

在搜索过程中又发现了一个查看文档的网站 http://devdocs.io/ ,也同样可以使用Chrome插件达到离线查看文档的效果,但是看了一眼似乎没有 Android 的文档,但是其他似乎挺全的。并且整个网站还是开源的。 https://github.com/Thibaut/devdocs

reference


2016-10-22 Document , 经验总结 , Dash , Zeal , Linux

Android 过渡动画框架

为了帮助视图层次内部和视图之间的过渡更加容易实现动画效果,Android 提供了 Transition 框架。这一套框架能够在视图之间提供一种或者多种动画过渡效果。过渡动画要解决的另一个主要问题就是对同一场景中的多个 View 做动画,弥补了之前Android 在动画方面的缺失。

这一套框架提供如下功能:

  • Group-level 动画 在同一视图层次中一种或者多种动画效果

  • Transition-based 动画 在初始场景和终止场景之间动画

  • Built-in 动画 内置普通效果动画,包括 fade out 和 movement

  • Resource 文件支持 从 layout 资源文件中加载视图以及内置动画资源

  • Lifecycle callbacks 随心所欲的控制动画的过程

Transition API 在 Android 5.0 及以上被引入,但是在 4.4 Kitkat 时就引入了 Scenes 和 Transitions 的概念。在 Android 4.0 API level 14 及以上,可以使用 android.support.transition 包中内容

创建场景

Scene 保存视图状态及层次结构,包括所有的视图及其属性。 Transition 框架能够从场景到场景进行过渡动画。初始场景(Staring scene)经常是当前的视图层次,而结束场景(ending scene)通常由用户从 layout 创建或者用代码从一组视图中创建。

从 Layout 创建 Scene

利用静态方法 Scene.getSceneForLayout()

// Create the scene root for the scenes in this app
mSceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes
mAScene = Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this);
mAnotherScene =
    Scene.getSceneForLayout(mSceneRoot, R.layout.another_scene, this);

或者使用构造函数构造:

Scene mScene;

// Obtain the scene root element
mSceneRoot = (ViewGroup) mSomeLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered
mViewHierarchy = (ViewGroup) someOtherLayoutElement;

// Create a scene
mScene = new Scene(mSceneRoot, mViewHierarchy);

场景间动画

一般情况下 Android 系统会自动在场景间动画,自定义动画可省略,但是如果想自定义动画,可以在场景退出或者进入的时候自定义动画,需要注意以下两点:

  • 动画视图不在同一个视图层次
  • Transition 框架不能给 Listview 自动创建动画,Transition 框架的限制

可以使用 Scene.setExitAction(Runnable action) 或者 Scene.setEnterAction(Runnable action) 来自定义进入或者退出的动作。

Scene 总结

Scene 类中几个重要的方法:

  • 构造方法,Scene(ViewGroup sceneRoot, View layout)。 从 Layout 中创建 Scene,当调用 enter() 方法时将 sceneRoot 下子view 全部移除并将新 Layout 中内容填充。
  • 静态方法,创建新 Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
  • enter() 方法 exit() 方法
  • setEnterAction(Runnable action) 和 setExitAction(Runnable action)

Transition

在 Transition 框架中,动画在开始和结束场景间创建了一系列动画帧。

创建 Transition

系统提供的内置 transition 有如下:

Class Tag Attributes Effect
AutoTransition - 默认过渡动画,Fade out 渐隐, move 位移 和 resize 大小缩放,fade in 渐显 ,按顺序
Fade android:fadingMode=”[fade_in fade_out fade_in_out]” fade_in_out (default) 渐隐之后跟随者 渐显
ChangeBounds - 位移和缩放

通过资源文件创建

也通过资源文件即可修改过渡效果,避免大幅度修改 Activity 中代码。并且通过资源文件方式创建有效地将复杂的动画和代码文件分离。

通过如下步骤添加过渡动画资源:

  • 添加 res/transition/ 目录
  • 在该目录下创建 XML 资源
  • 在 XML 文件中添加内置的过渡动画子节点

例如如下资源文件则指定了渐隐渐显动画, res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

利用如下代码片段从资源文件中创建 Transition:

Transition mFadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

通过代码创建 Transition

可以在代码中动态地创建过渡效果,可以通过非常少的代码创建。通过直接调用 Transition 的子类构造函数直接创建,例如:

Transition mFadeTransition = new Fade();

执行 Transition

直接调用 TransitionManager.go() 静态方法,并提供一个终止场景:

TransitionManager.go(mEndingScene, mFadeTransition);

如果不指定 transition ,则系统自动套用默认的转场动画。

选择特定目标

转场动画默认对所有的Views进行动画,如果只希望某一些特定的 Views 动画,或者某一些不做动画。 在 transition 中,每一个视图都被称作一个 target,但是得注意,只有在Scene 中的views 才被称作 target。

可以使用 removeTarget() 来从 transition 中移除某一个 view,或者使用 addTarget() 来给 Transition 添加一个 view。

使用多重动画

如下定义的 transitionSet 和 AutoTransition 表现一致:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

代码中可以使用 TransitionInflater.from() 来加载 TransitionSet ,因为 TransitionSet 继承自 Transition,因此可以像使用 Transition 一样使用 TransitionSet。

不使用 Scene 来使用 Transition

改变视图层次并不是唯一改变UI的方式,同样也可以通过添加、修改、移除子view来对当前的视图作出修改。或者当两个场景非常相似,为了避免维护两份近乎一致的视图层次,可以只维护一份,并做一些微小的调整。

此时可以使用 delayed transition 来在两个状态之间进行转换。Transition 系统自动选择当前的视图层次作为初始状态,并记录用户对视图的修改,然后应用为终止状态,并在之后进行动态地变化。

在单一视图中使用 delay transition :

  • 调用 ` TransitionManager.beginDelayedTransition()` 来提供提供一个父view包括所有的待变化的view,此时系统自动保存当前传入的所有 views 的状态及属性
  • 修改子views ,系统会记录该views 的变化
  • 当系统重绘时系统自动在原始及新状态间做动画

创建自定义 Transitions

自定义过渡动画(custom transition)可以实现区别于内置转场动画的效果。比如,你可以定义一个动画来将文字和输入框的前景颜色变成灰色来表示当前禁止输入。

继承 Transition 类

继承 Transition 类,并实现如下方法:

public class CustomTransition extends Transition {

    @Override
    public void captureStartValues(TransitionValues values) {}

    @Override
    public void captureEndValues(TransitionValues values) {}

    @Override
    public Animator createAnimator(ViewGroup sceneRoot,
                                   TransitionValues startValues,
                                   TransitionValues endValues) {}
}

Transition 动画使用 property 动画系统,属性动画动态地修改视图的属性,因此得知道初始和终止的值。

captureStartValues

系统会对初始场景中的每一个view调用 captureStartValues(transitionValues) ,参数 TransitionValues 对象包含了一个指向 view 的引用和一个保存view状态的 Map 实例。在自己的实现中,获取这些属性,并将这些属性传回给map。

为了避免key 值和已经拥有的key值矛盾,可以使用如下的 TransitionValues 键的形式:

package_name:transition_name:property_name

例如下面的例子:

public class CustomTransition extends Transition {

    // Define a key for storing a property value in
    // TransitionValues.values with the syntax
    // package_name:transition_class:property_name to avoid collisions
    private static final String PROPNAME_BACKGROUND =
            "com.example.android.customtransition:CustomTransition:background";

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        // Call the convenience method captureValues
        captureValues(transitionValues);
    }


    // For the view in transitionValues.view, get the values you
    // want and put them in transitionValues.values
    private void captureValues(TransitionValues transitionValues) {
        // Get a reference to the view
        View view = transitionValues.view;
        // Store its background property in the values map
        transitionValues.values.put(PROPNAME_BACKGROUND, view.getBackground());
    }
    ...
}

captureEndValues

系统会给终止场景中的每一个target view自动调用 ` captureEndValues(TransitionValues) ,其他方面,captureEndValues()captureStartValues()` 表现一致。

animator

在初始和终止视图层次间进行动画,提供一个 animator ,通过复写 createAnimator() ,当系统调用此方法时,他将 root view 和开始和结束 TransitionValues 传入。系统调用 createAnimator() 方法的次数由开始和终止场景之间的变化决定。

Android 系统自带 Transition

虽然教程上只罗列了一些 Transition ,但是看文档,还是能发现不少 Transition 的直接子类。

Transition

Transition 在 XML 定义时有如下属性:

  • android:duration 动画时长毫秒
  • android:interpolator 动画使用的 interpolator
  • android:matchOrder 过渡动画执行顺序
  • android:startDelay 在过渡动画之前延迟时间 毫秒

TransitionSet

TransitionSet 在 XML 中有如下属性:

  • android:transitionOrdering 过渡动画执行顺序,有两种值, together 和 sequential

以下转场动画中原生提供的包括 Fade,Slide,Explode,但场景之间存在共享元素时,有如下的转场动画 changeBound,changeClipBounds,changeImageTransform,ChangeTransform,ChangeScroll。

Fade

渐隐渐显动画

Fade 在 XML 中有如下属性:

  • android:fadingMode 渐变模式, fade_in, fade_out , fade_in_out ,默认为 fade_in_out

Slide

元素从四个方向滑动进入

Slide 属性:

  • android:slideEdge 从那边滑动出,有 left, top, right, bottom, start, end 模式

Explode

屏幕中间上下移走

ChangeBounds

通过获取前后Scene 中 target view 的边界,并对这些 view 做动画,改变 View 的大小

ChangeClipBounds

获取前后 Scene 中 getClipBounds() 的边界,并做动画,有如下属性:

  • android:resizeClip 通过改变 clipBounds 来改变 view,而不是改变view 自身的大小

ChangeImageTransform

通过获取开始和结束时 Scene 中 ImageView 的 matrix ,并对他们做动画。 和 ChangeBounds 一起使用,来对 ImageView 改变大小,形状,和 ScaleType 来使动画更加流畅。

ChangeTransform

获取 Scene 中的尺寸和旋转角度,并做动画,有如下属性:

  • android:reparent 追踪父view 的变化
  • android:reparentWithOverlay A parent change should use an overlay or affect the transform of the transitionining View.

ChangeScroll

改变滑动位置

下面分享一个 transitionSet 的 XML 语法:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
     android:transitionOrdering="sequential">
    <changeBounds/>
    <fade android:fadingMode="fade_out" >
        <targets>
            <target android:targetId="@id/grayscaleContainer" />
        </targets>
    </fade>
</transitionSet>

以上就是文档中对 Transition 的解释,关于自定义 Transition 还可以具体展开细讲,但是因为 Transition API 要求的 API 版本比较高,基本 Android 5.0 及以上才可以很好的支持,所以目前使用并不广,但是相信不久以后就能看到越来越多的转场动画了。

reference


2016-10-21 Android , transition , animation , AndroidDev

Podcast 托管

Podcast 近两年又突然火起来,一度认为 Podcast 是最近才流行起来的媒体,后来才发现这种媒介早在上世纪80年代就诞生 。近两年被再一次提起可能iOS 系统内置播客系统是个契机,一时间让 Podcast 进入大众的视听,而国内近些年也陆陆续续出现了很多播客平台,喜马拉雅,荔枝等等, 播客这种形式在播客,视频播客新奇之后似乎很难找到自己的定位,但是在日常生活中还是有很多情况适用播客,在开车等双手双眼需要时刻准备着的时候,播客系统可以可以代替收音机,而在长途跋涉需要闭目养神时,播客也可以成为音乐的代替。

但是经过这么多年的演进, Podcast 还是依赖于 RSS 2.0 标准,不论是 iTunes 还是 Play Music ,虽然 Google 已经用关闭 Google Reader 来表明对 RSS 的态度,却还是依然无法将 Podcast 从 RSS 剥离。其实这也很矛盾,就像之前看到的一则评论,Google 托管了最大的视频播客YouTube,却连小小的音频文件都懒得托管。所以无奈才有了此篇文章。

Podcast Hosting

有几种免费靠谱的托管方式,这里就不讲收费的方案了,因为免费的可以做到很好。个人使用完全没有问题。

  • Dropbox, Dropbox 导出直链自行 Google
  • Google Drive,如何导出直链自行 Google
  • SoundCloud,有高级版
  • Internet Archive
  • 最后 Cloudup 我使用的方案,支持1000个文件托管,具体可参看之前总结的一篇文章

其实苹果官网也给了一个 Podcast Hosting 的列表 可以查看。

Generate RSS feed

iTunes 和 Google Play Music 都需要不同格式的 RSS,需要特别考虑。另外音频最好带封面 14001400 px 到 20482048 px 大小,这个是 iTunes Store 需要的。

iTunes 需要的标签

iTunes 中关于 Podcast 的介绍 。 每一个 Podcast 叫做 Episode,所有独立的可下载的音频,视屏,PDF,ePub,都属于 Episode。 iTunes 支持的文件格式包括: M4A,MP3,MOV,MP4,M4V,PDF,ePub 等等。通过 Feed Validation Service 验证 Feed 有效性。以下例子可以在帮助文档 中查看到。

iTunes 中需要的 Feed 文件格式:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
    <title>All About Everything</title>
    <link>http://www.example.com/podcasts/everything/index.html</link>
    <language>en-us</language>
    <copyright>&#x2117; &amp; &#xA9; 2014 John Doe &amp; Family</copyright>
    <itunes:subtitle>A show about everything</itunes:subtitle>
    <itunes:author>John Doe</itunes:author>
    <itunes:summary>All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store</itunes:summary>
    <description>All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store</description>
    <itunes:owner>
    <itunes:name>John Doe</itunes:name>
    <itunes:email>john.doe@example.com</itunes:email>
    </itunes:owner>
    <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything.jpg"/>
    <itunes:category text="Technology">
    <itunes:category text="Gadgets"/>
    </itunes:category>
    <itunes:category text="TV &amp; Film"/>
    <itunes:category text="Arts">
    <itunes:category text="Food"/>
    </itunes:category>
    <itunes:explicit>no</itunes:explicit>
    <item>
        <title>Shake Shake Shake Your Spices</title>
        <itunes:author>John Doe</itunes:author>
        <itunes:subtitle>A short primer on table spices</itunes:subtitle>
        <itunes:summary><![CDATA[This week we talk about <a href="https://itunes/apple.com/us/book/antique-trader-salt-pepper/id429691295?mt=11">salt and pepper shakers</a>, comparing and contrasting pour rates, construction materials, and overall aesthetics. Come and join the party!]] ></itunes:summary>
        <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode1.jpg"/>
        <enclosure length="8727310" type="audio/x-m4a" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode3.m4a"/>
        <guid>http://example.com/podcasts/archive/aae20140615.m4a</guid>
        <pubDate>Tue, 08 Mar 2016 12:00:00 GMT</pubDate>
        <itunes:duration>07:04</itunes:duration>
        <itunes:explicit>no</itunes:explicit>
    </item>
    <item>
        <title>Socket Wrench Shootout</title>
        <itunes:author>Jane Doe</itunes:author>
        <itunes:subtitle>Comparing socket wrenches is fun!</itunes:subtitle>
        <itunes:summary>This week we talk about metric vs. Old English socket wrenches. Which one is better? Do you really need both? Get all of your answers here.</itunes:summary>
        <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode2.jpg"/>
        <enclosure length="5650889" type="video/mp4" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode2.mp4"/>
        <guid>http://example.com/podcasts/archive/aae20140608.mp4</guid>
        <pubDate>Wed, 09 Mar 2016 13:00:00 EST</pubDate>
        <itunes:duration>04:34</itunes:duration>
        <itunes:explicit>no</itunes:explicit>
    </item>
    <item>
        <title>The Best Chili</title>
        <itunes:author>Jane Doe</itunes:author>
        <itunes:subtitle>Jane and Eric</itunes:subtitle>
        <itunes:summary>This week we talk about the best Chili in the world. Which chili is better?</itunes:summary>
        <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode3.jpg"/>
        <enclosure length="5650889" type="video/x-m4v" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode2.m4v"/>
        <guid>http://example.com/podcasts/archive/aae20140697.m4v</guid>
        <pubDate>Thu, 10 Mar 2016 02:00:00 -0700</pubDate>
        <itunes:duration>04:34</itunes:duration>
        <itunes:explicit>no</itunes:explicit>
        <itunes:isClosedCaptioned>Yes</itunes:isClosedCaptioned>
    </item>
    <item>
        <title>Red,Whine, &amp; Blue</title>
        <itunes:author>Various</itunes:author>
        <itunes:subtitle>Red + Blue != Purple</itunes:subtitle>
        <itunes:summary>This week we talk about surviving in a Red state if you are a Blue person. Or vice versa.</itunes:summary>
        <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode4.jpg"/>
        <enclosure length="498537" type="audio/mpeg" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3"/>
        <guid>http://example.com/podcasts/archive/aae20140601.mp3</guid>
        <pubDate>Fri, 11 Mar 2016 01:15:00 +3000</pubDate>
        <itunes:duration>03:59</itunes:duration>
        <itunes:explicit>no</itunes:explicit>
    </item>
</channel>
</rss>

Play Music 需要的标签

完整的字段及解释可以参考官方文档,里面有非常详细的字段解释。Play Music 要求的大多数内容和 iTunes 类似,只有一些字段要求有些变化,Google 要求图片为方形,最小为 600*600 ,并且建议大小在 1200*12007000*7000 px 之间,支持 JPEG 和 PNG。

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0" version="2.0">
<channel>
    <title>The Unknown Podcast</title>
    <link>http://sample.com/podcasts/unknown/index.html</link>
    <language>en-us</language>
    <copyright>℗ 2025 Unknown Podcaster Corp</copyright>
    <googleplay:author>Unannounced Podcaster</googleplay:author>
    <googleplay:description>The Unknown Podcast will look at all the things that are unknown or unknowable. Find us on Google Play Music!</googleplay:description>
    <description>The Unknown Podcast will look at all the things that are unknown or unknowable.</description>
    <googleplay:email>unknown-podcast@sample.com</googleplay:email>
    <googleplay:image href="http://sample.com/podcasts/unknown/UnknownLargeImage.jpg" />
    <googleplay:category text="Technology"/>
    <item>
        <title>What's out there?</title>
        <googleplay:author>Engima</googleplay:author>
        <googleplay:description>We look at all the things that are out there that we'd like to know.</googleplay:description>
        <googleplay:image href="http://sample.com/podcasts/unknown/Episode1.jpg" />
        <enclosure url="http://sample.com/podcasts/UnknownPodcastEpisode1.mp3" length="2320111" type="audio/mpeg" />
        <guid>http://sample.com/podcasts/UnknownPodcastEpisode1.mp3</guid>
        <pubDate>Mon, 29 Jun 2015 19:00:00 GMT</pubDate>
    </item>
    <item>
        <title>What can we know, really?</title>
        <googleplay:author>The everyman</googleplay:author>
        <googleplay:description>And then we follow up on last week's podcast to examine what can and cannot be known.</googleplay:description>
        <googleplay:image href="http://sample.com/podcasts/unknown/Episode2.jpg" />
        <enclosure url="http://sample.com/podcasts/UnknownPodcastEpisode2.m4a" length="6421543" type="audio/x-m4a" />
        <guid>http://sample.com/podcasts/UnknownPodcastEpisode2.m4a</guid>
        <pubDate>Mon, 6 Jul 2015 19:00:00 GMT</pubDate>
    </item>
</channel>
</rss>

提交认证

可以向以下两个网址提交申请认证

最后可以从这里访问到网站 https://einverne.github.io/podcast ,网站源代码在 https://github.com/einverne/podcast

Demo 的Play Music 地址在 这里

reference


2016-10-17 Podcast , Google , iTunes

Android 提醒

Android 对话框

实现对话框通常有三种方式:

  • 使用 Dialog 类,或者其派生类 每个类被用来提供特定功能,比如日期选择,单选等等
  • 对话框主题的 Activity 可以将对话框主题应用到 Activity 上,使 Activity 外观类似于对话框
  • Toast 特殊的,短暂的,非模态的消息对话,通常在 Broadcast Receiver 和 Service 中使用,来提示用户响应事件

以下重点考虑 Dialog 类的使用,其派生子类 DatePickerDialog,TimePickerDialog 以后扩展开讲。响应代码可以参考此次 提交。

最常见的对话框 AlertDialog

private void showNormalDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Title");
    builder.setMessage("your message here.");
    builder.setCancelable(true);

    /**
    * Typically, a dialog is dismissed when its job is finished
    * and it is being removed from the screen.
    * A dialog is canceled when the user wants to escape the dialog
    * and presses the Back button.
    * For example, you have a standard Yes/No dialog on the screen.
    * If the user clicks No, then the dialog is dismissed
    * and the value for No is returned to the caller.
    * If instead of choosing Yes or No, the user clicks Back to escape the dialog
    * rather than make a choice then the dialog is canceled
    * and no value is returned to the caller.
    */
    builder.setPositiveButton(
            "Yes",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    Toast.makeText(DialogTestActivity.this, "Yes", Toast.LENGTH_SHORT).show();
                    dialog.dismiss();

                }
            });

    builder.setNegativeButton(
            "No",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    Toast.makeText(DialogTestActivity.this, "No", Toast.LENGTH_SHORT).show();
                    dialog.dismiss();
                }
            });

    AlertDialog alert = builder.create();
    alert.show();
}

三选对话框

private void showThreeOptionsDialog() {
    Dialog dialog = new AlertDialog.Builder(this)
            .setIcon(android.R.drawable.btn_star)
            .setTitle("喜好调查")
            .setMessage("你喜欢李连杰的电影吗?")
            .setPositiveButton("很喜欢", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(DialogTestActivity.this, "我很喜欢他的电影。",
                            Toast.LENGTH_LONG).show();
                }
            })
            .setNegativeButton("不喜欢", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(DialogTestActivity.this, "我不喜欢他的电影。", Toast.LENGTH_LONG).show();
                }
            })
            .setNeutralButton("一般", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(DialogTestActivity.this, "谈不上喜欢不喜欢。", Toast.LENGTH_LONG).show();
                }
            }).create();
    dialog.show();
}

带输入框的对话框

private void showInputDialog() {
    final EditText editText = new EditText(this);
    Dialog dialog = new AlertDialog.Builder(this)
            .setIcon(android.R.drawable.ic_dialog_info)
            .setTitle("Input your message")
            .setView(editText)
            .setPositiveButton("Sure", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    String inputString = editText.getText().toString();
                    Toast.makeText(DialogTestActivity.this, inputString, Toast.LENGTH_SHORT).show();
                }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            }).create();
    dialog.show();
}

单选对话框

private void showSingleChoiceDialog() {
    Dialog dialog = new AlertDialog.Builder(this)
            .setTitle("Single Choice")
            .setSingleChoiceItems(new String[]{"item1", "item2"}, 0, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(DialogTestActivity.this, "" + which, Toast.LENGTH_SHORT).show();
                    dialog.dismiss();
                }
            }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            }).create();
    dialog.show();
}

复选对话框

private void showMultiChoiceDialog() {
    Dialog dialog = new AlertDialog.Builder(this)
            .setTitle("Multi Choice")
            .setMultiChoiceItems(new String[]{"item0", "item1"}, new boolean[]{false, true}, new DialogInterface.OnMultiChoiceClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which, boolean isChecked) {

                    Toast.makeText(DialogTestActivity.this, "" + which + " " + isChecked, Toast.LENGTH_SHORT).show();
                }
            }).create();
    dialog.show();
}

自定义布局对话框

private void showCustomDialog() {
    LayoutInflater inflater = getLayoutInflater();
    View layout = inflater.inflate(R.layout.dialog_custom, null);
    final EditText editText = (EditText) layout.findViewById(R.id.editText);
    Dialog dialog = new AlertDialog.Builder(this)
            .setTitle("Custom")
            .setView(layout)
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(DialogTestActivity.this, editText.getText().toString() + " ", Toast.LENGTH_SHORT).show();
                }
            }).create();
    dialog.show();
}

reference


2016-10-11 Android , AndroidDev

Android 使用自定义 keystore 调试

可以在 build.gradle 文件中指定debug 下的 keystore 文件路径,一般放到项目跟目录下,并使用如下方式指定。 更多语法可以参考 Signing Configurations 部分 http://tools.android.com/tech-docs/new-build-system/user-guide

指定 debug variant 下 keystore 路径:

android {
    signingConfigs {
        debug {
            storeFile file('your.keystore')
            keyAlias 'androiddebugkey'
            keyPassword 'android'
            storePassword 'android'
        }
    }
}

推荐还是讲 debug 和 release 下的 keystore 分开。

保存一些和 keystore 相关的命令

  1. 查询 keystore 中条目

    keytool -list -keystore your.keystore -storepass yourpassword

  2. 修改 keystore 中,使用上一步获取到的证书名字

    keytool -changealias -alias 证书名字 -destalias androiddebugkey -keystore your.keystore -storepass yourpassword

  3. 修改密钥库密码

    keytool -storepasswd -keystore your.keystore -storepass yourpassword

  4. 修改证书密码

    keytool -keypasswd -alias androiddebugkey -keystore 证书名字 -storepass yourpassword

reference


2016-10-09 Android , AndroidDev

全平台录制 GIF:LICEcap

以前写过一篇文章讲的是如何在 Linux 下使用 byzanz 来录制 GIF,并且能够达到很好的效果,并且文件大小非常合适网络分享。现在就介绍下在其他两大平台 Win/Mac 下录制 GIF 的工具。

LICEcap

录屏 GIF 工具名字叫做: LICEcap 。这也是一款我在使用很长时间之后感觉非常好用的工具。他的官网地址:

http://www.cockos.com/licecap/

他有如下优点:

  • 开源,简单,小巧
  • 支持直接录屏转 GIF,或者自由 .LCF 格式,使用 REAPER 可以转制成 .gif 或者其他视频格式
  • 支持录制中移动录制窗口
  • 支持暂停和继续
  • 全局快捷键 shift + space 可以暂停继续

更多其他的技巧和优点可查看他官网,目前为止他满足了我所有的需求。

使用:

record

输出文件:

output

ScreenToGif

在寻找的过程中还发现一款比较好用的工具,但只有在 Windows 平台上存在,这里也写上吧。

官方地址:https://screentogif.codeplex.com/

  • 同样支持直接输出 Gif
  • 支持调整大小

更多的介绍和使用说明可上官网查看。


2016-10-01 gif , 经验总结 , record

通过 IFTTT 自动下载 Instagram 图片到 Google Drive

在 Instagram 关闭 API 之前可以通过 IFTTT 获取别人的更新 Photos, 但是 Instagram 收紧了 API 政策。既拿不到别人更新的信息流,同样也自己Like 别人照片的信息也拿不到了,原来 IFTTT 有两个 Recipes:

  • 一个为自动下载个人 Liked 别人的 Photo 到 Dropbox
  • 另一个为自动下载其他 ID 的更新 Photo

然而这两个 Recipes 都被 IFTTT 删去了,我甚至在 StackOverflow 上问过这件事情,只是几个月过去了,也没有任何实质性的方案。

直到这些天,突然脑袋一道闪光,再此之前,我了解了方法可以导出 Instagram 到 RSS,然后看到了别人自动将 Imgur 中的图片上传到 Google Drive ,使用的是 RSS 导出图片,有人写了一脚本传入 URL,就可以提取 URL 中的图片链接。

正是基于这两个方案,我想到了使用 Instagram to RSS to IFTTT to Google Drive 的方案,然后经过尝试,不需要一行代码的情况下,我实现了自动转存的方案。省去了自己写脚本的时间,同样这个方案也自动适配的Instagram 的网页,并不会因为网页结构的变化而导致失败。只要 RSS 有效,那么就会一直生效。

Instagram to RSS

要做到这个事情,就需要借助 RSS bridge 这个项目,这个项目也是当时我在寻找 InoReader RSS 的时候发现的,关于导出微博、知乎、微信的订阅到 RSS 可以以后在展开详谈。回到 RSS bridge ,这个项目本身就是利用爬虫将网站更新内容生成 RSS,本身支持的网站还是很多的,Flickr、GooglePlus、Twitter、Youtube、Pinterest 等等,当然包括 Instagram。

而我使用了 https://bridge.suumitsu.eu/ 这个网站提供的服务,这个网站架设了 RSS bridge 的服务,当然如果有条件的话自己架设也是很不错的选择,只要一直维护就可以。在网页上选择 Instagram 然后填入 Instagram 的用户 ID ,然后获取 Atom 的源即可。拿到这个 Feed URL,在下一步使用。

if RSS to Google Drive

拿到 Feed URL 之后,到 IFTTT,使用 这个 新建一个 Recipe

分析一下刚刚拿到的 URL

https://bridge.suumitsu.eu/?action=display&bridge=Instagram&u=instagram&format=Atom

其中 u 参数后面跟随着的就是 Instagram 的用户 ID,改变 u 后面的参数为需要自动保存的 ID,然后 Save 即可。


2016-09-30 经验总结 , Instagram , Google Drive , RSS , IFTTT

Google+

最近文章

  • 将 MySQL 升级到 5.7 这些天折腾 Django 的时候用到了 MySQL,然而本地和VPS 上使用的版本不一致,本地使用了 5.7 版本,而 VPS 上使用了 5.5 的老版本,在数据迁移的时候遇到了 5.5 版本下不支持 DATETIME(6) 这样的数据类型。 DATETIME(6) 用来保存精确到微秒的时间。
  • Docker 入门 Docker 是一个能够把开发环境的应用程序自动部署到容器的开源引擎。该引擎的目标是提供一个轻量、快速的环境,能够运行开发者的程序,并方便高效地将程序从开发者的笔记本部署到测试环境,然后再部署到生产环境。 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。
  • 使用 nethogs 查看每个进程流量 在 Linux 上查看系统流量有很多命令,平时一直使用 iftop 来查看单块网卡或者系统整体的流量,iftop 可以查看 TCP 链接的流量情况,分析出流量连往的 IP 地址。但是 iftop 无法做到查看系统中单个进程的网络流量情况。所以 Google 一下之后发现了 nethogs 。
  • 使用 supervisor 管理进程 Supervisor (http://supervisord.org) 是一个用 Python 开发的进程管理工具(client/server),可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。除了对单个进程的控制,还可以同时启动、关闭多个进程,比如很不幸的服务器出问题导致所有应用程序都被杀死,此时可以用 supervisor 同时启动所有应用程序而不是一个一个地敲命令启动。
  • MySQL 数据类型 了解并熟悉 MySQL 中的数据类型,对建表和数据库优化都非常重要。 MySQL 实现了 SQL 定义的类型,也响应的增加乐意 tiny, small, big 的类型。 MySQL 的数据类型主要分成三个部分: Numeric Type 数值型 Date and Time Type 日期和时间 String Type 字符型