使用 GitCafe Pages 搭建 Maven Repository

随着 Android Studio 的普及,越来越多 Android 第三方开源项目使用 Gradle 作为构建工具。Gradle 其中的一个优势是优秀的包管理。我们只需要在脚本里定义要引用的库的名字,以及要引用的版本,构建的时候就会自动从 Maven 中央仓库中获取对应的 aar / jar。

而如果要上传自己的包到 Maven 中央仓库,并不是一件容易操作的事情。这里介绍利用 GitCafe 的 Pages 服务搭建 Maven Repository 的方法。

准备工作

首先把 aar-quickstart 项目 fork 一份到自己的账号上,并 clone 到本地。

把要构建的开源库项目作为 submodule 添加到本地的,例如下面这样:

git submodule add https://gitcafe.com/fython/MaterialPreferenceCompat-AAR

这样的话现在我们本地有一个 quickstart-aar 项目,且里面有目标第三方开源项目的 git 作为 submodule。

修改脚本

用文本编辑器打开 build.gradle,修改以下地方:

1
2
3
4
5
6
7
8
9
10
11
12
android {

...

sourceSets {
main {
manifest.srcFile 'projectpath/library/src/main/AndroidManifest.xml'
java.srcDirs = ['projectpath/library/src/main/java']
res.srcDirs = ['projectpath/library/src/main/res']
}
}
}

这里把 projectpath/library 修改为开源项目对应的源码目录(相对目录)。

1
2
3
4
5
6
7
dependencies {
compile 'com.android.support:appcompat-v7:22.1.0'
}

group = 'packagename'
version = '1.0.0'
def artifactId = 'library'

dependencies 部分自行按照开源项目的依赖修改。

下面 groupversionartifactId 都按照 Maven 的命名规则修改,下面是一个示例:

1
2
3
group = 'moe.feng.materialcompat'
version = '1.0.0'
def artifactId = 'library'

部署到 Pages

在 Shell 窗口中执行:

./build-gitcafe-pages.sh

即可自动构建出必要的文件,以及生成作为索引的 index.html,并且会自动部署到服务器上,如下图:

在自己的项目中引用这个 library

修改项目的 build.gradle ,作如下修改:

1
2
3
4
5
6
7
8
9
repositories {
maven { url "http://fython.gitcafe.io/MaterialPreferenceCompat-AAR" }
...
}

dependencies {
compile 'moe.feng.materialcompat:library:1.0@aar'
...
}

其中 url 部分替换为该项目的 pages url,dependencies 部分按照上面修改过的那部分来操作。

在 Android 中使用 data-binder 绑定布局 xml 与数据

在前几天的 Google IO 2015 中,Google 在 support-v7 中新增了 data-binder,使用 data-binder 可以直接在布局的 xml 中绑定布局与数据,从而简化代码。

因为 data-binder 是包含在 support-v7 包里面的,所以可以向下兼容到最低 Android 2.1 (API level 7+).


构建环境

需要在 Android SDK Manager 中更新 Support repository 到最新版本,并使用 Android Studio 1.3.0-beta1 或更高的版本。

为了使用 data-binder,我们必须在 build.gradle 中声明对它的依赖:

1
2
3
4
5
dependencies {
classpath "com.android.tools.build:gradle:1.2.3"
classpath "com.android.databinding:dataBinder:1.0-rc0"
}
}

同样的我们要保证所有的子项目中能找到 jcenter 的中央仓库:

1
2
3
4
5
allprojects {
repositories {
jcenter()
}
}

在我们需要引用到 data-binder 的子项目中,我们需要引入 data binding 的插件支持:

1
2
apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

data binding 插件必须你项目的 provided 和 configuration dependencies 列表中。

Data Binding 布局文件


第一个数据绑定表达式

数据绑定的布局文件并不像以往的布局文件那样以一个 View 作为根元素,而是在根元素在有一个 data 元素以及一个 view 根元素。如果只有 view 作为根元素的话,这个布局文件并不能绑定数据。以下是一个正确的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>

<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>

</LinearLayout>
</layout>

data 描述的 user 变量是这样用到布局里的:

1
<variable name="user" type="com.example.User"/>

Expressions within the layout are written in the attribute properties using the “@{}” syntax. Here, the TextView’s text is set to the firstName property of user:

在布局引用这个 data 属性的变量应该使用 “@{}” 语句来描述。这里引用的是把 user 的 fristname 属性引用到 TextView 中:

1
2
3
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>


数据对象

(这块我认为没啥好翻译的了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}

建议使用 jackdaw 自动生成 Setter 与 Getter.


绑定数据到 Activity

不啰唆,直接上代码:

1
2
3
4
5
6
7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}

完事了。概括来说就是用 DataBindingUtil.setContentView() 来取代以往的 setContentView()

如果只是要生成 View 对象而不是显示到 Activity 上,那么应该用以下的代码:

1
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

如果绑定的对象是 RecyclerView 或 ListView 的 Item,那么应该这样做:

1
2
3
4
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

进阶:如果需要绑定可观察对象,以及想了解绑定的表达式,请参考原文:
https://developer.android.com/tools/data-binding/guide.html
(完)

(转)Android 判断用户2G/3G/4G移动数据网络

在做 Android App 的时候,为了给用户省流量,为了不激起用户的愤怒,为了更好的用户体验,是需(要根据用户当前网络情况来做一些调整的,也可以在 App 的设置模块里,让用户自己选择,在 2G / 3G / 4G 网络条件下,是否允许请求一些流量比较大的数据。

通过 Android 提供的 TelephonyManager 和 ConnectivityManager 都可以获取到 NetworksInfo 对象,可以通过 getType() 获取类型,判断是 wifi 还是 mobile ,如果是 mobile ,可以通过 NetworksInfo 对象的 getSubType() 和 getSubTypeName() 可以获取到对于的网络类型和名字。

网络类型和名字定义在 TelephonyManager 类里。

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
/** Network type is unknown */
public static final int NETWORK_TYPE_UNKNOWN = 0;
/** Current network is GPRS */
public static final int NETWORK_TYPE_GPRS = 1;
/** Current network is EDGE */
public static final int NETWORK_TYPE_EDGE = 2;
/** Current network is UMTS */
public static final int NETWORK_TYPE_UMTS = 3;
/** Current network is CDMA: Either IS95A or IS95B*/
public static final int NETWORK_TYPE_CDMA = 4;
/** Current network is EVDO revision 0*/
public static final int NETWORK_TYPE_EVDO_0 = 5;
/** Current network is EVDO revision A*/
public static final int NETWORK_TYPE_EVDO_A = 6;
/** Current network is 1xRTT*/
public static final int NETWORK_TYPE_1xRTT = 7;
/** Current network is HSDPA */
public static final int NETWORK_TYPE_HSDPA = 8;
/** Current network is HSUPA */
public static final int NETWORK_TYPE_HSUPA = 9;
/** Current network is HSPA */
public static final int NETWORK_TYPE_HSPA = 10;
/** Current network is iDen */
public static final int NETWORK_TYPE_IDEN = 11;
/** Current network is EVDO revision B*/
public static final int NETWORK_TYPE_EVDO_B = 12;
/** Current network is LTE */
public static final int NETWORK_TYPE_LTE = 13;
/** Current network is eHRPD */
public static final int NETWORK_TYPE_EHRPD = 14;
/** Current network is HSPA+ */
public static final int NETWORK_TYPE_HSPAP = 15;


看到这个代码和注释,相信没有这方面知识的人很难看懂,都啥玩意?这注释跟没注释有啥区别?!就是让人看着更加闹心而已。所以说,注释对阅读代码的人很重要。当然这些东西可能太专业了,写这些代码的人估计是想写也不知道该怎么了,得写多大一坨啊?!我在最后会贴上一些我整理的资料,可以供大家参考一下,不是很详细,也不专业,就是大概有个印象。

TelephonyManager 还提供了 getNetworkTypeName(int type) 的方法,这个方法可以返回一个字符串,但是信息量不大。

那怎么判断是 2G , 3G 还是 4G 网络呢?TelephonyManager 还提供了另外一个方法,getNetworkClass(int networkType) ,但这个方法被隐藏掉了,我把代码贴一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int getNetworkClass(int networkType) {
switch (networkType) {
case NETWORK_TYPE_GPRS:
case NETWORK_TYPE_EDGE:
case NETWORK_TYPE_CDMA:
case NETWORK_TYPE_1xRTT:
case NETWORK_TYPE_IDEN:
return NETWORK_CLASS_2_G;
case NETWORK_TYPE_UMTS:
case NETWORK_TYPE_EVDO_0:
case NETWORK_TYPE_EVDO_A:
case NETWORK_TYPE_HSDPA:
case NETWORK_TYPE_HSUPA:
case NETWORK_TYPE_HSPA:
case NETWORK_TYPE_EVDO_B:
case NETWORK_TYPE_EHRPD:
case NETWORK_TYPE_HSPAP:
return NETWORK_CLASS_3_G;
case NETWORK_TYPE_LTE:
return NETWORK_CLASS_4_G;
default:
return NETWORK_CLASS_UNKNOWN;
}
}


然后下面是这几个常量的值。

1
2
3
4
5
6
7
8
/** Unknown network class. {@hide} */
public static final int NETWORK_CLASS_UNKNOWN = 0;
/** Class of broadly defined "2G" networks. {@hide} */
public static final int NETWORK_CLASS_2_G = 1;
/** Class of broadly defined "3G" networks. {@hide} */
public static final int NETWORK_CLASS_3_G = 2;
/** Class of broadly defined "4G" networks. {@hide} */
public static final int NETWORK_CLASS_4_G = 3;


不知道为啥要把这些东西给隐藏起来,然道是不靠谱?!还是其他的更好的方式?!不知道,先这样吧,现在通过上面的手段,是可以知道用户用的是什么网络,当然也可以区分出来用户使用的是 2G , 3G 还是 4G 了。当然,你获取到这些数据后,你也可以推算出用户用的是哪家公司的网络,移动的,联通的,还是电信的,当然,只在中国。而且虚拟运营商开始真正上市后,这个就区分不出来是京东的,还是国美,苏宁的了,但是你可以知道你的手机号用的是联通的网还是移动的网。

在 OpenWRT 上使用 ShadowSocks 建立透明代理

0.前言

因为众所周知的原因,使用 ShadowSocks 对网络进行加速已经是一种普遍使用的行为。但是不管是 VPN 也好,ShadowSocks 也罢,在使用中总会碰见各种不完美的问题。如每个终端都需要单独配置,连接上服务器后所有流量都会走该服务器等等。撰写本文的目的是对最近在路由器上解决这些问题的一个总结,如果在路由器上部署好了 ShadowSocks 的话,不但所有接入该路由器的设备能自动走 ShadowSocks ,而且可以针对网站选择合适的线路,这些工作对客户端来说都是透明的,只要跟平常一样连接上路由器就好。

1.准备工作

硬件准备:

可以刷 OpenWRT 的路由器一台(废旧 PC 也可以),建议使用小米路由器mini或联想的New Wifi Mini,两者硬件配置和价格几乎都一样,都是便宜耐操的货(认为本文是软文的话现在就可以 Ctrl + W 了)。

软件准备:

路由器对应的 OpenWRT 固件。下载地址 点击这里

shadowsocks-libev-spec 的 ipk 包,项目地址 点击这里 请下载 luci-app-shadowsocks 以及对应 CPU 架构的安装包。 如果是刚才提到的那两个型号,请下载 ramips 架构最新的 shadowsocks-libev-spec。

几个版本的区别是,带 spec 的是针对 OpenWRT 优化过的版本,只要装上了对应的 luci-app 就可以通过 Web 界面管理。而名字带 polarssl 的使用的是 polarssl 库,据说相当于名字没带 polarssl 的版本来说,体积会更小。但是默认的使用 openssl 的版本性能会更好。

2.操作步骤

2.1 刷入 OpenWRT

以下以小米路由器mini为例子,假定本文的读者已打开了 SSH 登录(详细操作请查询官方的操作教程。注意:获得 SSH 会失去官方保修,本文不对这种行为负任何责任)。

我这里使用的是小米路由器mini的 PandoraBox r355固件,把该固件上传到路由器的 /tmp 目录下后,SSH 登录上去并执行以下指令:

1
mtd -r write /tmp/PandoraBox-ralink-xiaomi-mini-r466-20140701.bin firmware

执行后路由器会自动重启,重启完成后即可通过 WiFi 或网线连接到已经刷入 OpenWRT 的路由器。

如果是使用 x86 的 PC 作为软路由的话,把U盘/存储卡做成 OpenWRT 的启动盘然后重启即可。

2.2 修改 opkg.conf 使包管理器生效

因为这个版本的 opkg.conf 有错,导致 opkg 有问题,所以请在 Web 管理界面中的”系统“→”软件包“→”管理“中,把几个镜像源改成这两个地址:

1
2
src/gz oldpackages http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/oldpackages 
src/gz packages http://downloads.openwrt.org.cn/PandoraBox/ralink/mt7620/packages

另外因为架构没填写正确,部分 ipk 软件包(如 ShadowSocks)会安装失败,所以还得继续修改 opkg.conf 加入以下几句:

1
2
3
4
arch all 100
arch noarch 200
arch ralink 300
arch ramips_24kec 400

2.3 打开 SSH

这个版本默认不带 SSH,请在 Web 界面的“系统”→”软件包“中安装 openssh-server。安装完成后即可通过 SSH 登录到路由器。

Windows 下可以使用 putty 作为 SSH 客户端登录

2.4 ShadowSocks 的安装及配置

用 WinSCP 工具上传 shadowsocks-libev-spec_2.1.3-1_ramips_24kec.ipk 以及 luci-app-shadowsocks-spec_1.3.0-1_all.ipk 到路由器的 /tmp 下,并在 SSH 窗口里执行:

1
2
3
cd /tmp
opkg install shadowsocks-libev-spec_2.1.3-1_ramips_24kec.ipk
opkg install luci-app-shadowsocks-spec_1.3.0-1_all.ipk

即可完成 ShadowSocks 部分的配置。

然后登录到路由器的 Web 管理界面,“服务”→“Shadowsocks”,把“使用配置文件”的勾去掉,填入你的 ShadowSocks 服务器信息,保存并应用。

然后在 SSH 窗口里执行:

1
2
/etc/init.d/shadowsocks enable
/etc/init.d/shadowsocks start

刷新 Web 的 Shadowsocks 管理界面,如果显示“Shadowsocks 已在运行”即已成功启动。

2.5 DNS的配置

在进行以下操作前,请先在 Web 管理界面的“网络”→“DHCP/DNS”→“HOSTS和解析文件”中勾上“忽略解析文件”以防止上级 DNS 的污染。

这个版本的 Shadowsocks 可以打开端口转发,如启用,默认会把本地的 5300 端口转发到 8.8.4.4#53 端口。所以如果只需要使用 Google 的 DNS 服务器作为上游的话,只需要在/etc/dnsmasq.conf 的末尾里添加一行:

1
server=127.0.0.1#5300

但是如果全走 Google 的公共 DNS 的话,虽然走加密通道可以防止 DNS 污染,但是同样的,对有 CDN 优化的网站来说,返回的将会是国外域名。如淘宝就会打开国际版。所以必须要加入针对国内网站的优化。

首先我们继续在 /etc/dnsmasq.conf 的末尾里继续加入:

1
conf-dir=/etc/dnsmasq.d

然后在 SSH 窗口中输入以下命令:

1
2
3
4
5
6
7
8
9
10
# 创建 dnsmasq 的配置目录
mkdir /etc/dnsmasq.d
# 安装 git
opkg install git
# 克隆 dnsmasq-china-list 项目到本地
git clone git://github.com/felixonmars/dnsmasq-china-list /root/dnsmasq-china-list
# 软连接配置文件到 dnsmasq 的配置目录
ln -s /root/dnsmasq-china-list/accelerated-domains.china.conf /etc/dnsmasq.d/accelerated-domains.china.conf
# 重启dnsmasq 服务
/etc/init.d/dnsmasq restart

另外有一点要注意的是,如果 ShadowSocks 的服务器填写的是域名而不是 ip 的话,会陷入一个死循环(ShadowSocks 未启动→域名无法解析→无法启动 ShadowSocks 服务),所以对于这种情况,在重启 dnsmasq 服务前,建议在 /etc/dnsmasq.d/ 下新建一个 ss-server.conf,内容为:

1
server=/服务器的根域名xxx.com/114.114.114.114

或者在 /etc/hosts 里写死该服务器对应的 ip 地址。

2.6 进阶:自动更新配置文件

因为安装包的问题所以这个 git 无法使用 pull 以及 merge 命令,我们首先对其修复。

在 SSH 窗口中输入以下命令:

1
2
ln -s /usr/bin/git /usr/bin/git-merge
ln-s /usr/bin/git /usr/bin/git-pull

然后新建 /root/update.sh,内容如下:

1
2
3
4
5
6
wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /etc/shadowsocks/ignore.list
cd /root/dnsmasq-china-list
git pull --rebase

/etc/init.d/dnsmasq restart
/etc/init.d/shadowsocks restart

然后用 chomod +x /root/update.sh 命令给予该脚本执行权限,然后执行 /root/update.sh 试试看。正常的话配置文件应该会成功更新。

在路由器的 Web 管理界面中点击“系统”→“计划任务”中,输入以下内容:

1
30 4 * * * /root/update.sh

保存后,应该每天的凌晨4点就会自动执行这个更新脚本了。

3.小结

经过以上的调教后路由器应该就能变身智能分流的路由器了。如果在测试中发现什么问题,欢迎留言讨论。