开发需要要求和阿里云交互,于是就去阿里云官网找SDK,不得不说相比于AWS阿里云接口和文档上差距很大,可以看见阿里云SDK就支持JAVA、Python、.NET和PHP。好在他提供了一个Java SDK,原本觉得都是 Java 的环境,Android 上应该也没啥问题,结果发现有依赖上的冲突,运行时给了个 NoSuchMethodError
经过分析发现Android 框架中集成了 Apache 的 httpClient 包,阿里云 OSS Java SDK 也需要用 httpClient。异常是 OSSClient 构造所调的 ThreadSafeClientConnManager 构造方法。虽然依赖都是单独 jar 发布的,尝试过改变构建顺序、去除附加 jar 包等方案,结果依旧。

最后结局方案是用第三方的一个SDK,用起来也很别扭,希望阿里早日解决对android的支持吧。

在研究从阿里云下载文件的时候(由于阿里SDK不支持android,所以用的是第三方SDK),总是出现dialog导致内存泄露的错误。找了许久的bug,最后发现,真正的原因是在Activity中出现异常导致生成dailog的Activity对象提前死亡,这时候dailog并没有dismiss。

进一步研究发现真正的引起Activity死亡的错误是Ossclient客户端引起的unparseable date错误,最终的错误根源在jar包中,于是找来源码,将错误定位在下面的函数:

1
2
3
4
public static Date getDateFromString(String format, String dateString)
throws ParseException {

SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.parse(dateString);

在执行字符串转换到date对象时,出现错误。查找到调用getDateFromString方法的函数

1
2
3
public static Date getGMTDateFromString(String dateStr) throws ParseException {
return getDateFromString("E, dd MMM yyyy HH:mm:ss 'GMT'", dateStr);
}

发现传入的形参format是E, dd MMM yyyy HH:mm:ss ‘GMT’这个格式在中文环境下并不支持,把手机的语言调成英文,错误就消失了。但是要在中文环境下运行不报错的方法就是在新建SimpleDateFormat类时,用第二个参数规定所在的地点在非中国地区。

1
2
3
4
5
public static Date getDateFromString(String format, String dateString)
throws ParseException {

SimpleDateFormat sdf = new SimpleDateFormat(format,Locale.ENGLISH );
return sdf.parse(dateString);
}

下面进一步研究SimpleDateFormat这个类的问题,通过查阅OSChina的android API(谷歌被墙你懂的)提供的在线文档可以看到SimpleDateFormat的构造方法有四个:
image
其中

1
2
3
4
public SimpleDateFormat(String pattern,Locale locale)
Constructs a SimpleDateFormat using the given pattern and the default date format symbols for the given locale. Note: This constructor may not support all locales. For full coverage, use the factory methods in the DateFormat class.
Parameters:pattern - the pattern describing the date and time formatlocale - the locale whose date format symbols should be used
Throws:NullPointerException - if the given pattern or locale is nullIllegalArgumentException - if the given pattern is invalid

而public SimpleDateFormat(String pattern)使用传入的格式和系统所在地区默认的日期格式标志

1
2
3
4
public SimpleDateFormat(String pattern)
Constructs a SimpleDateFormat using the given pattern and the default date format symbols for the default locale. Note: This constructor may not support all locales. For full coverage, use the factory methods in the DateFormat class.
Parameters:pattern - the pattern describing the date and time format
Throws:NullPointerException - if the given pattern is nullIllegalArgumentException - if the given pattern is invalid

由于很多日期格式在中文语境下是不能转换成date对象,所以推荐使用指定local的构造函数

题外话SimpleDateFormat不是线程安全的,可以看这篇精彩的文章
这也同时提醒我们在开发和设计系统的时候注意下一下三点:
  1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
  2.多线程环境下,对每一个共享的可变变量都要注意其线程安全性
  3.我们的类和方法在做设计的时候,要尽量设计成无状态的

在Android中调用系统服务很多时候都需要用到Context对象,如果是在MainActivity等Activity的子类中自然没有问题,调用系统服务默认会添加context.或者可以显式的使用MainActivity.this,但是在非Activity子类的情况下就要求在构造该类对象的时候,将context传递进去,不免出现反复传递context的现象,毕竟程序员懒是一种美德,所以找到了如下的方法可以全局获取context。非常简单首先在AndroidManifest.xml中声明自定义的Application的子类

1
2
3
4
<application
android:name="com.njuptjsy.cloudclient.MyApplication"
......
/application>

接着在项目中新建一个MyApplication类继承Application

1
2
3
4
5
6
7
8
9
10
public class MyApplication extends Application {
private static Context context;

public void onCreate() {
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
}

这样就可以通过MyApplication的静态方法getContext()全局获取context对象了

出于毕设客户端的开发需要,想要收集Android设备的内存、CPU、存储和电量信息,通过查找现在这四个信息的获取代码如下:

电量信息

通过广播实现,系统在电池电量信息发送变化时,回向外发送广播。只要注册一个监听Intent.ACTION_BATTERY_CHANGED的广播接收器并声明权限即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void batteryLevel() {  
BroadcastReceiver batteryLevelReceiver = new BroadcastReceiver() {
int scale = -1;
int level = -1;
int voltage = -1;
int temperature = -1;
@Override
public void onReceive(Context context, Intent intent) {
level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
Log.i("BatteryManager", "level is "+level+"/"+scale+", temp is "+ temperature +", voltage is "+voltage);
}
};
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
MyApplication.getContext().registerReceiver(batteryLevelReceiver, filter);
}

这里要注意的是在广播接收器BroadcastReceiver的onReceiver方法中的语句只会在接受到系统广播之后才能得到执行,若果刚刚注册这个广播监听器接着就是获取系统电量,那么一定会遇到得到的电量为0等问题,可以参见stackoverflow上这个问题。解决方法应该是先注册广播接收器,一段时间后在获取电量信息。

CPU使用率

主要是借助top命令,这里我只需要用户的cup使用率和系统cup使用率两个参数即可,所以只需输出的第一行如下
User 7%, System 8%, IOW 0%, IRQ 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
public void CpuUsage() throws Exception{
String topresult;
Process process = Runtime.getRuntime().exec("top -n 1");

BufferedReader br=new BufferedReader(new InputStreamReader(process.getInputStream()));
while((topresult = br.readLine())!=null)
{
if(topresult.trim().length()<1){
continue;
}else{
Log.i("CpuUsage", topresult);
String[] cpuInfoStrings = topresult.split(",");
cpuInfo = new HashMap<String,Integer>();
cpuInfo.put("Users",findNumInString(cpuInfoStrings[0]));
cpuInfo.put("sys", findNumInString(cpuInfoStrings[1]));
break;
}
}
}

private int findNumInString(String cpuInfoString){
int percent;
Pattern pattern = Pattern.compile("\\w*\\s(\\d*)\\%");
Matcher matcher = pattern.matcher(cpuInfoString);
if (matcher.find()) {
String temp = matcher.group(1);
percent = Integer.parseInt(temp);
}
else {
percent = -1;
}
return percent;
}

存储信息

存储信息分为SD卡和系统自身的,手机自身除了系统之外的存储空间被称为ExternalStorage,这里要注意这不是值自己的插入的TF卡

SD卡信息

下面是获取SD卡存储空间的代码,主要是获取块数量和每块的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void SDCardSize() {  
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
File sdcardDir = Environment.getExternalStorageDirectory();
StatFs sf = new StatFs(sdcardDir.getPath());
long blockSize = sf.getBlockSize();
long blockCount = sf.getBlockCount();
long availCount = sf.getAvailableBlocks();
Log.d("" , "block大小:" + blockSize+ ",block数目:" + blockCount+ ",总大小:" +blockSize*blockCount/ 1024 /1024+ "MB" );
totalSizeInSd = blockSize*blockCount/ 1024 /1024;
Log.d("" , "可用的block数目::" + availCount+ ",剩余空间:" + availCount*blockSize/ 1024 /1024+ "MB" );
usefulSizeInSd = availCount*blockSize/ 1024 / 1024;
}
else {
totalSizeInSd = 0L;
usefulSizeInSd = 0L;
}
}

内部存储信息

原理很SD卡一样,都是根据块数量和每块的大小

1
2
3
4
5
6
7
8
9
10
11
public void internalStorageSize(){
File root = Environment.getRootDirectory();
StatFs sf = new StatFs(root.getPath());
long blockSize = sf.getBlockSize();
long blockCount = sf.getBlockCount();
long availCount = sf.getAvailableBlocks();
Log.d("", "block大小:"+ blockSize+",block数目:"+ blockCount+",总大小:"+blockSize*blockCount/1024+"MB");
totalSizeInternal = blockSize*blockCount/1024/1024;
Log.d("", "可用的block数目::"+ availCount+",可用大小:"+ availCount*blockSize/1024+"MB");
usefulSizeInternal = availCount*blockSize/1024/1024;
}

内存信息

内存主要是通过获取系统活动管理器对象(ActivityManager)和ActivityManager.MemoryInfo对象,调用ActivityManager对象的getMemoryInfo方法,将Android内存信息保存在在ActivityManager.MemoryInfo对象对象中

1
2
3
4
5
6
7
8
9
10
public void memoryInfo(){
//获得MemoryInfo对象
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
//获取系统服务信息
ActivityManager myActivityManager = (ActivityManager)MyApplication.getContext().getSystemService(Activity.ACTIVITY_SERVICE);
//获得系统可用内存,保存在MemoryInfo对象上
myActivityManager.getMemoryInfo(memoryInfo);
usefulMemorySize = memoryInfo.availMem / 1024 /1024;
//totalMemorySize = memoryInfo.totalMem;
}

这里的一个小问题是,memoryInfo.totalMem添加之后,Eclipse并没有在该语句周围报错,但却在整个.java文件中报错。查询andriod API发现totalMen成员变量是在API 16之后加入的,但是我这里target API也是大于16的。

安装准备

首先需要有自己的github并且创建自己的个人站点,下载好Node.j

安装Hexo

之前试过使用jekyll是在是觉累不爱了,国内连ruby的官方源都被墙了,必须要换淘宝源。如果想使用jekyll搭建可以参考这篇文章

安装好Node.js之后,重启git shell,使用如下命令安装hexo

1
$ npm install -g hexo

在磁盘上新建一个hexo文件夹,在git shell中将工作路径切换到该文件夹下,运行如下命令

1
$ hexo init

Hexo随后会自动在目标文件夹建立网站所需要的所有文件。这时可以通过下面的命令预览现在blog的效果,在浏览器中输入localhost:4000

1
2
$ hexo g
$ hexo s

这时就可以看到一个最基础的hexo blog的效果。

修改主题

如果不想用原有的主题可以使用其他主题,以我的主题为例

1
$ git clone https://github.com/wuchong/jacman.git themes/jacman

接着,修改Hexo根目录下的config.yml配置文件中的theme属性,将其设置为jacman。

1
theme: jacman

使用下面的命令更新主题

1
2
$ cd themes/jacman
$ git pull

这时可以使用这三条命令来预览现在的blog

1
2
3
$ hexo clean
$ hexo g
$ hexo s

部署到Github

部署到Github前需要配置_config.yml文件

1
2
3
4
5
deploy:
type: git
repository: git@github.com:njuptjsy/njuptjsy.github.com.git
branch: master
message:XXXX

这一步要注意的有两点,第一是type:后面需要接一个空格在接上需要的值,而且type:的属性相对于deploy都有两个空格的缩进。第二type:github这个类型不再支持了,需要先

1
$ npm install hexo-deployer-git --save

在把type:改成git
下面提交到github

1
2
3
$ hexo clean
$ hexo generate
$ hexo deploy

修改Hexo设置

网站搭建完成后,就可以根据自己爱好来对Hexo生成的网站进行设置了,对整站的设置,只要修改项目目录的_config.yml就可以了,这是我的设置,可供参考。

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
# Hexo Configuration
## Docs: http://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/

# Site
title: More Than Words
subtitle:Perfection'snot attainable, but if we chase it we can catch excellence..
description:学习总结 思考感悟 知识管理
author: Siyu Jin
language:zh-CN
timezone:

# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://njuptjsy.github.io/
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:

# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:

# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
enable: true
line_number: true
tab_replace:

# Category & Tag
default_category: uncategorized
category_map:
tag_map:

# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss

# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page

# Extensions
## Plugins: http://hexo.io/plugins/
## Themes: http://hexo.io/themes/
theme: jacman

# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repository: git@github.com:njuptjsy/njuptjsy.github.com.git
branch: master
message: this is frist try

这里需要注意的是,所以冒号后面必须有一个空格

修改局部页面

页面展现的全部逻辑都在每个主题中控制,源代码在hexo\themes\jacman\中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
├── languages #多语言
| ├── default.yml#默认语言
| └── zh-CN.yml #中文语言
├── layout #布局,根目录下的*.ejs文件是对主页,分页,存档等的控制
| ├── _partial #局部的布局,此目录下的*.ejs是对头尾等局部的控制
| └── _widget#小挂件的布局,页面下方小挂件的控制
├── source #源码
| ├── css#css源码
| | ├── _base #*.styl基础css
| | ├── _partial #*.styl局部css
| | ├── fonts #字体
| | ├── images #图片
| | └── style.styl #*.styl引入需要的css源码
| ├── fancybox #fancybox效果源码
| └── js #javascript源代码
├── _config.yml#主题配置文件
└── README.md #用GitHub的都知道

到这里一个简单的hexo搭建的博客就部署好了,使用markdown写博的语法可以阅读这篇文章