《煤的历史》简读

如果把煤这个主角当作一个物种来看待,整本书就像在讲一个物种的复仇一样~

煤给人带来了气候,带来了温暖,也带来了死亡。人类为了挖煤,深入几百几千米的地下,终日不见阳光,却是为了挖掘亿万年前的太阳能源。

1

Read More

Android 简单的日志工具

地址 : https://github.com/xesam/AndroidLogTools

现在包含两个类:

  1. 打印日志
  2. 崩溃记录

使用方式

compile 'dev.xesam.android:AndroidLogTools:0.1.2'

打印日志 L

对 android.util.Log 的简单封装,支持 d(Object… content) 的调用形式,避免对 String 的硬性要求,使用示例:

开启日志(默认不打印):

L.enable(true);

打印日志:

L.d();
L.d(null);
L.d(null, null);
L.d(this);
L.d(this, this);
L.d(1);
L.d(1, 2);
L.d("a");
L.d("a", "b");

结果如下:

D/L[empty_tag]: L[empty_content]
D/L[null]: L[null]
D/L[null]: L[null]
D/MainActivity: L[empty_content]
D/MainActivity: dev.xesam.android.logtools.demo.MainActivity@32dd3da6
D/1: L[empty_content]
D/1: 2
D/a: L[empty_content]
D/a: b

崩溃记录 CrashLog

将崩溃记录写入外部文件中,便于检查。(注意,只在测试的时候才使用),使用示例:

在 Application 中注册:

CrashLog.register(this);

即可。

记录日志保存在 XXX/sdcard/Android/data/#{package_name}/files/目录之下,比如:

crash.2015-10-06T12:03:24.txt

Java与Js通信

Java 与 Js 互调封装 AndroidJavascriptBridge

JS 调用 Java

Js 简单调用

这个比较常见,适用于同步,没有回调的情况

示例:

java :

@JavascriptInterface
public void java_fn() {
}

webView.addJavascriptInterface(this, "Java");

js:

Java.java_fn();

Js 回调

两种形式

js.call({
    data : {},
    fn:function(){}
})

js.call(data, function(){
})

主要问题 :

  1. 普通对象无法无法直接传递
  2. 回调函数无法传递

所以,一种处理方式就是在 js 上下文种保持整个请求,在 java 执行完毕之后,从 java 去主动调用 js 方法,找到原始的请求,并执行正确的回调。
示意图如下:

js_bridge.png

如此可以嵌套调用下去。一个完整的封装实现:AndroidJavascriptBridge

使用示例

java:

final JavascriptBridge javascriptBridge = new JavascriptBridge(webView);
javascriptBridge.registerLocalRequestHandler("java_fn1", new LocalCallRequest.RequestHandler<Person>() {
    @Override
    public Person formJson(String requestString) {
        Gson gson = new Gson();
        return gson.fromJson(requestString, Person.class);
    }

    @Override
    public void handle(LocalCallRequest localCallRequest, Person data) {
        Toast.makeText(getApplicationContext(), data.name, Toast.LENGTH_SHORT).show();
        if (localCallRequest.hasCallback()) {
            javascriptBridge.deliveryRemoteCallback(localCallRequest, "succ", new Person().getJSONObject());
        }
    }
});

js:

document.getElementById('call_java_2').addEventListener('click', function () {
    bridge.invoke_remote_call('java_fn2',
        {
            succ: function (resp) {
                log("call_java_2:" + resp.name);
            }
        },
        true);
}, false);

Java 调用 Js

Java 简单调用

KitKat 之后可以使用 evaluateJavascript。其他择可以使用传统的 WebView.loadUrl

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    mWebView.evaluateJavascript(script, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {

        }
    });
} else {
    mWebView.loadUrl("javascript:" + script);
}

Java 回调

见上图

使用示例

js:

bridge.register_local_request_handler('js_fn1', function (data, callback_id) {
    log("js_fn1:java call js[js_fn1]");
});

java:

findViewById(R.id.js_fn1).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        RemoteCallRequest remoteCallRequest = new RemoteCallRequest("js_fn1", new Person().getJSONObject());
        javascriptBridge.invokeRemoteCall(remoteCallRequest);

    }
});

效果图

demo.png

LinearLayoutCompat

LinearLayoutCompat 所在位置 android.support.v7.widget.LinearLayoutCompat

主要特性:

  1. 支持分割线

注意

2.3 中使用xml定义drawable的时候有bug,所以在2.3 下最好还是使用图片作为分隔符

使用

定义分割线 /drawable/linearlayout_compat_divider.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#000000" />
    <size android:width="5dp" />
</shape>

定义layout

<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    app:divider="@drawable/linearlayout_compat_divider"
    app:dividerPadding="10dp"
    app:showDividers="middle"
    tools:context="dev.xesam.android.support.v7.widget.LinearLayoutCompatDemo">

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1.0"
        android:background="#ff0000"
        android:gravity="center"
        android:text="@string/hello_blank_fragment" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1.0"
        android:background="#00ff00"
        android:gravity="center"
        android:text="@string/hello_blank_fragment" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1.0"
        android:background="#0000ff"
        android:gravity="center"
        android:text="@string/hello_blank_fragment" />

</android.support.v7.widget.LinearLayoutCompat>

效果

输入图片说明

使用 dalvikvm 命令

整理版本.原文地址: http://bbs.pediy.com/showthread.php?t=184592

步骤

  1. 编写 .java 源文件,编译为 .class
  2. 使用 dx(在sdk 目录的 build-tools 下面)将 .class 编译为 .dex 文件
  3. 将 .dex 文件载入 android 设备, 使用 dalvikvm 运行

具体操作

创建 java 源文件

public class Hello{
    public static void main(String[] argc){
        System.out.println("Hello, Android!");
    }
}

编译 java 源文件

javac Hello.java

生成 Hello.class 文件

编译成 dex 文件

dx --dex --output=Hello.dex Hello.class 

编译正常会生成 Hello.dex 文件 。

使用 ADB 运行测试

adb root
adb push Hello.dex  /sdcard/
adb shell

dalvikvm -cp /sdcard/Hello.dex Hello  

得到输出如下:

Hello, Android!

重要说明

  1. Android4.4 的官方模拟器和自己的手机上测试都提示找不到 Class 路径 ,在Android 老的版本4.3(以及 4.0 模拟器)上测试还是有输出的 。
  2. 因为命令在执行时 , dalvikvm 会在 /data/dalvik-cache/ 目录下创建 .dex 文件 ,因此要求 ADB 的执行 Shell 对目录 /data/dalvik-cache/ 有读、写和执行的权限 ,否则无法达到预期效果 。

uick-demo-creator

在我们写一些 demo 的时候,经常需要针对每种情况写一个用户示例,新建 Activity 的过程太麻烦,所以这个库的作用就是自动帮你创建索引式的导航列表,一行代码搞定所有的示例。

使用方式

compile 'dev.xesam.android:quick-demo-creator:0.1.0'

两种模式:

1. 列出所有已经注册的Activity,点击即可打开

用法:

1
QuickDemo.inflateActivity(activity, R.id.listview);

2. 像文件管理器一样,列出 app 中 package 的目录索引,并按照给定的过滤规则过滤需要展示的组件(Activity 以及 Fragment)

用法:

将 dev.xesam.android.quickdemo.QuickDemoActivity 设置为 LAUNCHER Activity 即可

默认过滤规则

如果觉得不想使用demo,sample之类的名称,可以自定义多虑规则 参见 SimpleFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimpleFilter implements QuickDemoFilter {

Pattern target = Pattern.compile("demo|sample|example", Pattern.CASE_INSENSITIVE);
String pkgName;

public SimpleFilter(Context context) {
pkgName = context.getPackageName();
}

@Override
public boolean filter(String className) {
String[] comps = className.split("\\.");
String simpleClassName = comps[comps.length - 1];
return className.startsWith(pkgName) && target.matcher(simpleClassName).find();
}
}

效果

Screenshot_2015-08-12-23-36-42.png

Screenshot_2015-08-12-23-36-47.png

Screenshot_2015-08-12-23-36-53.png

Android 定时任务

基于Handler的Android定时器与倒计时器

源码地址:Github AndroidTimer

特性

支持操作 :

  1. start
  2. pause
  3. resume
  4. cancel

注意

以上方法是同步方法,请不要在回调方法里面调用以上方法。

使用

CountTimer

1
2
3
4
5
6
7
8
new CountTimer(100) {

@Override
public void onTick(long millisFly) { // millisFly is the Elapsed time at *Running State*
vCountSwitcher.setText((millisFly) + "");
Log.d("onTick", millisFly + "");
}
};

CountDownTimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
new CountDownTimer(100) {

@Override
public void onTick(long millisUntilFinished) { // millisUntilFinished is the left time at *Running State*
Log.d("onTick", millisFly + "");
}

@Override
public void onCancel(long millisUntilFinished) {
}

@Override
public void onPause(long millisUntilFinished) {
}

@Override
public void onResume(long millisUntilFinished) {
}

@Override
public void onFinish() {
}
};

使用一个 Handler 同时管理多个定时任务

创建多个任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MultiCountTimer multiCountTimer = new MultiCountTimer(10);
multiCountTimer.registerTask(new CounterTimerTask(1) {
@Override
public void onTick(long millisFly) {
vMulti1.setText("multi_1:" + millisFly);
}
}).registerTask(new CounterTimerTask(2, 100) {
@Override
public void onTick(long millisFly) {
vMulti2.setText("multi_2:" + millisFly);
}
}).registerTask(new CounterTimerTask(3, 1000) {
@Override
public void onTick(long millisFly) {
vMulti3.setText("multi_3:" + millisFly);
}
});

multiCountTimer.startAll();

取消任务:

1
2
3
multiCountTimer.cancel(2);
或者
multiCountTimer.cancelAll();

截图

timer

参考自 Android SDK 中的 CountDownTimer

POST简略

简单描述:

post的数据在 request Payload 内, 因此,Request Headers 里面带有一个 boundary 属性,服务器收到 post 请求之后,通过 boundary 将参数从 Payload 中分离出来。

params = body.split(boundary)

唯一需要注意的是,CRLF(回车换行符)的使用。HTTP协议对CRLF的内容分割要求还是略严的。

Request Headers

Content-Type:multipart/form-data; boundary=----WebKitFormBoundarydKY7YrWhI3bdiNYz

Request Payload

--{boundary}
Content-Disposition: form-data; name="{name1}"

{value1}
--{boundary}
Content-Disposition: form-data; name="{name2}"

{value2}
--{boundary}
Content-Disposition: form-data; name="{file_name1}"; filename="xxx.png"
Content-Type: image/png

0x120x12...

--{boundary}--

Read More

如何实现ButterKnife

本文的目标是用最简代码实现 ButterKnife 的核心功能。示例代码在github上: ButterKnifeProcedure

Pluggable Annotation Processing

注解处理器
Java5 中叫APT(Annotation Processing Tool),在Java6开始,规范化为 Pluggable Annotation Processing。

第一步(收集信息)

找到所有被注解的属性或者方法,将所有的信息收集到对应的“类数据集”中。

第二步(生成源文件)

根据每一个“类数据集”,生成对应的java源文件。由于这些文件并不是在运行时生成的,因此也无需动态编译,注解处理器运行完成之后,
编译器会处理所有的编译流程。

第三步(动态注入)

运行时动态注入,即用户常规调用的 ButterKnife.bind(activity)

这一步为了避免蹩脚的调用,使用了运行时反射,但是作者对每一个类进行了缓存,因此,不会对执行效率产生多大影响。

在最新的 ButterKnife 源码(2015.06.08)中,ButterKnife已经重构了部分方法:

ButterKnife#inject -> ButterKnife#bind
@InjectView -> @FindView

等等,具体变化可以去看官方文档,本文档后续代码使用最新版本代码演示

极简实现演示

演示代码说明

  1. 示例代码由 ButterKnife 简化而来,部分定义和实现有删改,只能绑定 Activity 中的 View 字段
  2. 为了避免引入Android平台,但是又需要更直观,所以mock了android的两个类,ActivityView
  3. 为了避免使用 Pluggable Annotation Processing 过程中的jar包要求,以及波及不必要的java文件,请使用命令行运行演示,直接运行 ./run.sh 即可查看结果
  4. 保证 CLASSPATH 中含有tools.jar

第一步(收集信息)

  1. 在每一个类中找到所有被 FindView 注解的字段
  2. 每一个需要绑定的字段信息都保存为一个 FieldViewBinding 对象,比如:
1
2
@FindView(100)
View vView1;

得到:

1
new FieldViewBinding(vView1, android.view.View, 100)
  1. 将字段分类,获取每一个类的“类数据集”BindingClass,比如, MainActivity 对应的 “类数据集” 如下:
1
2
3
4
MainActivity:
List<FieldViewBinding> fieldViewBindings = new ArrayList<FieldViewBinding>();
fieldViewBindings.add(new FieldViewBinding(vView1, android.view.View, 100))
fieldViewBindings.add(new FieldViewBinding(vView2, android.view.View, 200))

第二步(生成 Bind 工具类源文件)

为了便于在反射时容易实例化生成的类,每一个生成的类都实现了一个 ActivityBinder 接口,因此,根据 MainActivity “类数据集”生成的文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sample;

import android.view.View;
import android.app.Activity;
import butterknife.ButterKnife.ActivityBinder;

public class MainActivity$$ViewBinder implements ActivityBinder<sample.MainActivity> {
@Override
public void bind(sample.MainActivity target) {
View view;
view = target.findViewById(100);
target.vView1 = view; //这里要求 vView1 的访问权限为 package 级别
view = target.findViewById(200);
target.vView2 = view;
}
}

第三步(动态注入)

我们在 MainActivity 中调用 ButterKnife#bind,第一件事就是找到对应生成的 Bind 工具类,这里遵循命名规则(在对应类后增加 $$ViewBinder 后缀),直接使用动态加载并实例化:

1
2
Class<?> activityBindingClass = Class.forName(targetClass.getName() + ButterKnifeProcessor.SUFFIX);
activityBinder = (ActivityBinder) activityBindingClass.newInstance();

获得相应的 ActivityBinder 之后,使用 ActivityBinder#bind 进行绑定,与手动调用 findViewById 效果相同

运行

运行:

ButterKnifeProcedure/src$ ./run.sh

结果:

mainActivity.vView1.id = 100
mainActivity.vView2.id = 200