Wednesday, June 22, 2011

安能饭否笔记3-提取weibo包

大侠说的不错,还是比较简单的。

先导入http,fanfou, data包,会在如下问题报错:
1,对于com.ch_linghu.fanfoudroid.TwitterApplication.DEBUG的引用。
2,对于string.xml的引用。
3,fanfou包中个别模块会用到数据库。
4, 上述包还会引用业务无关的基础代码,如util。

我的处理是:
1,新造一个com.ch_linghu.fanfoudroid.TwitterApplication,用来保存需要的常量。

2,在相关模块里简单地修改代码,转向引用新的TwitterApplication。
//import com.ch_linghu.fanfoudroid.R;
import com.ch_linghu.fanfoudroid.TwitterApplication;
// sb.append(R.string.pref_rt_prefix_default + ":@");
sb.append(TwitterApplication.pref_rt_prefix_default + ":@");

3,对于个别模块里遇到数据库相关代码,注释掉就可以了。
比如在com.ch_linghu.fanfoudroid.fanfou.User中,
//import com.ch_linghu.fanfoudroid.db.MessageTable;
//import com.ch_linghu.fanfoudroid.db.TwitterDatabase;
//import com.ch_linghu.fanfoudroid.db.UserInfoTable;
/* 
    //TODO:增加从游标解析User的方法,用于和data里User转换一条数据
    public static User parseUser(Cursor cursor){

4,完整地引入util包,注意在util包里也会引用string.xml。

5,最终TwitterApplication很简单,
public class TwitterApplication 
{
public final static boolean DEBUG = true; // Configuration.getDebug();
public final static String pref_rt_prefix_default = "热饭";
public final static String tweet_source_prefix = "来自";
public final static String tweet_reply_to_prefix = "给";
public final static String tweet_reply_to_suffix = "的回复";
public final static String tweet_created_at_beautify_prefix = "大约";
public final static String tweet_created_at_beautify_sec = "秒";
public final static String tweet_created_at_beautify_min = "分钟";
public final static String tweet_created_at_beautify_hour = "小时";
public final static String tweet_created_at_beautify_day = "天";
public final static String tweet_created_at_beautify_suffix = "前";
public final static String pref_photo_preview_type_thumbnail = "缩略图尺寸";
public final static String pref_photo_preview_type_middle = "小尺寸";
public final static String pref_photo_preview_type_original = "大尺寸";

一点感想:
仅从代码复用的考虑出发,
1,问题集中在字符串资源上。
   fanfou包是否可以有自己的字符串资源?
   util、http包里string.xml相关的代码是否可以集中到fanfou包里?
2,fanfou包是否可以和数据库无关?
3, 正因为如此的简单,如果再进一步,拿过来就可以用,就好了。

=======================================================================

备注:
导入代码时遇到注释乱码的问题,解决如下:

MyEclipse6.5 注释乱码

打开Window -> preference, 左边 General -> Content Types, 然后在右边上面的框中打开Text, 选中Java Source File (你看到下面的框中有个*.java 就对了), 然后在下面的“Default edcodng”文本框中输入“UTF-8”, 点“Update”,就OK了。(什么文件的编码都可以在这里设置!)
对不起,忘了保留参考url。

Friday, June 17, 2011

安能饭否笔记2-请求响应

想要借用安能的weibo、http代码新建一个项目,学习测试fanfou api。做下去才知道这2个包的并非完全独立,对应用层模块有不小的依赖性。

先想闭着眼睛做加法,结果要加入的模块越来越多。看来改代码是必然的啦。
再想做减法,也没有完全的把握,不如退而结网,把代码看明白。看了代码以后,确定做减法是可行的。

== 以最简单的login为例,从task出发
com.ch_linghu.fanfoudroid.LoginActivity.LoginTask._doInBackground(TaskParams...)
    user= TwitterApplication.mApi.login(username, password);


== 进入weibo模块
com.ch_linghu.fanfoudroid.fanfou.Weibo.login(String, String)
    http.setCredentials(username, password);// 保存id/password到httpclient
    User user = verifyCredentials();

// 发送请求,返回User对象
com.ch_linghu.fanfoudroid.fanfou.Weibo.verifyCredentials()
        return new User(get(getBaseURL() + "account/verify_credentials.json"
                , true).asJSONObject());
get的输入参数就是fanfou检验用户名密码是否正确的路径:
http://api.fanfou.com/account/verify_credentials.[json|xml]

// 调用httpClient.Get 返回respond
com.ch_linghu.fanfoudroid.fanfou.Weibo.get(String, boolean)
       return get(url, null, authenticate);
com.ch_linghu.fanfoudroid.fanfou.Weibo.get(String, ArrayList<BasicNameValuePair>, boolean)
return http.get(url, authenticated);


== 进入httpClient:req/rsp
// 初始化,对于http还有不少不明白,只是简单地概述了一下
public HttpClient() {
        prepareHttpClient();
// 关于连接        
设置最大连接数 10
设置http版本 1.1
设置http:80,https:443
把上述设置保存在param里,创建DefaultHttpClient
mClient = new DefaultHttpClient(cm, params);      
支持gzip

// 关于认证
设置basic auth
mAuthScope
(m)localcontext


// Interceptor==拦截,preemptive=先发制人
mClient.addRequestInterceptor(preemptiveAuth, 0);
private static HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor()
用来预处理处理http的request?



// 发送指令
com.ch_linghu.fanfoudroid.http.HttpClient.get(String, boolean)
        return httpRequest(url, null, authenticated, HttpGet.METHOD_NAME);


com.ch_linghu.fanfoudroid.http.HttpClient.httpRequest(String, ArrayList<BasicNameValuePair>, File, boolean, String)
  // Create POST, GET or DELETE METHOD
        method = createMethod(httpMethod, uri, file, postParams);
        // Setup ConnectionParams, Request Headers
        SetupHTTPConnectionParams(method);
        // Execute Request
       // 因为是同步协议,现在响应已经收到
        response = mClient.execute(method, localcontext);
        res = new Response(response);
        
        if (response != null) {
            int statusCode = response.getStatusLine().getStatusCode(); // 处理状态 当响应码不为200时都会报出此异常:
            // It will throw a weiboException while status code is not 200
            HandleResponseStatusCode(statusCode, res);
    // 返回respond     
            return res;


== 通用respond(json) ---> 业务数据(user)
// 发送请求
com.ch_linghu.fanfoudroid.fanfou.Weibo.verifyCredentials()
        return new User(get(getBaseURL() + "account/verify_credentials.json"
                , true).asJSONObject());
                
com.ch_linghu.fanfoudroid.fanfou.User.User(JSONObject)
init(json);

com.ch_linghu.fanfoudroid.fanfou.User.init(JSONObject)
id = json.getString("id");
    name = json.getString("name");                
    .....

=================================================================================

再看看业务数据怎么显示到界面上?
== 以首页TweetActivity为例,还是从Task出发,发送请求
com.ch_linghu.fanfoudroid.ui.base.TwitterCursorBaseActivity.RetrieveTask._doInBackground(TaskParams...)
    String maxId = fetchMaxId(); 
    statusList = getMessageSinceId(maxId);
上面2个方法都是abstract

取出当前页(TYPE_HOME)的最大id
com.ch_linghu.fanfoudroid.TwitterActivity.fetchMaxId()
    return getDb().fetchMaxTweetId(getUserId(), StatusTable.TYPE_HOME);

已知消息id,查询时间线
com.ch_linghu.fanfoudroid.TwitterActivity.getMessageSinceId(String)    
    return getApi().getFriendsTimeline(new Paging(maxId));
    return getApi().getFriendsTimeline();

访问weibo, 返回List<Status>
com.ch_linghu.fanfoudroid.fanfou.Weibo.getFriendsTimeline(Paging)
    return Status.constructStatuses(get(getBaseURL() + "statuses/friends_timeline.json",null, paging, true));


== Task处理响应
// List<Status> ===〉ArrayList<Tweet>

com.ch_linghu.fanfoudroid.ui.base.TwitterCursorBaseActivity.RetrieveTask._doInBackground(TaskParams...)
    ArrayList<Tweet> tweets = new ArrayList<Tweet>();
    for (com.ch_linghu.fanfoudroid.fanfou.Status status : statusList)
    tweets.add(Tweet.create(status));
    mRetrieveCount = addMessages(tweets, false); // 又是abstract


// ArrayList<Tweet> ===〉dbcom.ch_linghu.fanfoudroid.TwitterActivity.addMessages(ArrayList<Tweet>, boolean)
for (Tweet t : tweets)
            getDb().createWeiboUserInfo(t.user);
    return getDb().putTweets(tweets, getUserId(), StatusTable.TYPE_HOME, isUnread);      

后续应该会同步db和listView吧?

Friday, June 10, 2011

安能饭否笔记1-初始化

拿到代码总想找到入口,然后慢慢看下去。不知道是不是一个好方法?

1,从AndroidManifest.xml开始,
<application android:icon="@drawable/icon" android:label="@string/app_name" android:name="TwitterApplication">
找到Application模块,com.ch_linghu.fanfoudroid.TwitterApplication
这里保存了很多全局的对象,如数据库,微博接口,图片加载......
还要为cmwap用户设置代理上网,难怪程序的设置里要说明网络类型。

2,再找启动的activity

<activity android:name=".TwitterActivity"
    <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>

应该是com.ch_linghu.fanfoudroid.TwitterActivity

3,找布局文件,怎么没有onCreate, setContentView?
其实,我已经花了不少力气找发送查询指令的地方,所以知道显示推的ListActivity有1个很深的类层次。
直到com.ch_linghu.fanfoudroid.ui.base.BaseActivity才找到onCreate。
在onCreate里只是调用了_onCreate(savedInstanceState);  所以,可以把_onCreate看作onCreate。
为什么要用_onCreate呢?

    // 因为onCreate方法无法返回状态,因此无法进行状态判断,
    // 为了能对上层返回的信息进行判断处理,我们使用_onCreate代替真正的
    // onCreate进行工作。onCreate仅在顶层调用_onCreate。
    protected boolean _onCreate(Bundle savedInstanceState) 

com.ch_linghu.fanfoudroid.TwitterActivity._onCreate(Bundle)里没有setContentView,
Search setContentView,找到了。
com.ch_linghu.fanfoudroid.ui.base.TwitterListBaseActivity._onCreate(Bundle)
   setContentView(getLayoutId());
getLayoutId应该是1个抽象函数
com.ch_linghu.fanfoudroid.ui.base.TwitterCursorBaseActivity.getLayoutId()
   return R.layout.main; 
所以,所有List页面使用相同的layout。

main.xml里嵌套了1个工具条的布局,又学了1招

<!-- Header -->
    <include layout="@layout/header"/>

4,想知道发送指令的过程?
去找工具栏里的“刷新”按钮。
List页面的工具栏在这里,com.ch_linghu.fanfoudroid.ui.base.TwitterListBaseActivity.mNavbar
所以,List页面的navibar都是相同的模块。

为“刷新”按钮设置处理方法的代码在这里:
com.ch_linghu.fanfoudroid.ui.module.NavBar.addRefreshButtonTo(Activity)
public void onClick(View v) {
((Refreshable) activity).doRetrieve();

doRetrieve是1个接口方法,具体实现在:
com.ch_linghu.fanfoudroid.ui.base.TwitterCursorBaseActivity.doRetrieve()
mRetrieveTask = new RetrieveTask();

在RetriveTask里应该发送指令了
com.ch_linghu.fanfoudroid.ui.base.TwitterCursorBaseActivity.RetrieveTask._doInBackground(TaskParams...)
   statusList = getMessageSinceId(maxId);

getMessageSinceId又是1个abstract.
com.ch_linghu.fanfoudroid.TwitterActivity.getMoreMessageFromId(String)里总算看到了api
   return getApi().getFriendsTimeline(paging);

5,收数据的过程:
    以后学习ListActivity类层次的时候再看。

6,注销登入后,再次打开程序,为什么会显示登入页面?
    不是在Application里,就在Activity基础类里
    Application里没有,就是从Acitivity发initent消息过来。
    Search “LoginActivity”, 找到了发送intent的地方。
    com.ch_linghu.fanfoudroid.ui.base.BaseActivity.showLogin()
        Intent intent = new Intent(this, LoginActivity.class);
    继续向上找
    com.ch_linghu.fanfoudroid.ui.base.BaseActivity.handleLoggedOut()

    com.ch_linghu.fanfoudroid.ui.base.BaseActivity.checkIsLogedIn()
    com.ch_linghu.fanfoudroid.ui.base.BaseActivity._onCreate(Bundle)
    各大Activity的onResume里也都有checkIsLogedIn
    所以无论打开哪个页面,都有可能回到登入页面。

=================================================================
一些感想:
1,程序使用了很深的继承,然后依靠接口、抽象函数来具体化特定的功能。
     所以在上述的文字里会经常看到:“所有......”, "相同.......", "是1个抽象函数.............."
     继承是一种非常强的偶合, 如果过多地使用,是否会限制后续层次的扩展?
     是否可以适当的使用设计模式,使得类层次扁平化?

2,在学习代码的过程中,一直在纠结为什么不把LoginActivity作为laucher?
     一部分原因是,一直在做的程序每次打开都需要手工验证,而且采用的是tcp长连接。和安能的差别非常大,一下子没有适应过来。
     更多的原因是,对于android程序生命周期、http协议还不熟悉,需要以后不断体会。

Wednesday, June 8, 2011

android ApiDemos笔记1 - 运行时遍历activity

关于启动activity: com.example.android.apis

在运行时,递归遍历属下所有的activity (queryIntentActivities)
过滤出和指定前缀相关的activity
归纳所有activity lable路径的第1层目录,构造1个list

list元素的title = activity lable(优先)或者activity的pack路径,
list元素的object = 启动activity的intent

如果title是activity lable,使用browseIntent创建intent
如果title是activity的pack路径,使用activityIntent创建

通过 SimpleAdapter,把list中的title显示到listActivity上。
选中listActivity中的项目,找到list只能给的intent,触发intent

2个activity的处理过程,
06-08 02:49:52.451: DEBUG/getData(382): ===> i = 0
06-08 02:49:52.511: DEBUG/getData(382): labelSeq = App/Activity/Hello World
06-08 02:49:52.521: DEBUG/getData(382): info.activityInfo.name = com.example.android.apis.app.HelloWorld
06-08 02:49:52.521: DEBUG/getData(382): label = App/Activity/Hello World
06-08 02:49:52.531: DEBUG/getData(382): prefix = 
06-08 02:49:52.531: DEBUG/getData(382): if (prefix.length() == 0 || label.startsWith(prefix))
06-08 02:49:52.531: DEBUG/getData(382): nextLabel = App
06-08 02:49:52.542: DEBUG/getData(382): prefixPath = null
06-08 02:49:52.542: DEBUG/getData(382): labelPath = [Ljava.lang.String;@40599058
06-08 02:49:52.550: DEBUG/getData(382): else if (entries.get(nextLabel) == null)
06-08 02:49:52.561: DEBUG/getData(382): browseIntent(App)


06-08 02:49:53.880: DEBUG/getData(382): ===> i = 58
06-08 02:49:53.880: DEBUG/getData(382): labelSeq = Views/Layouts/RelativeLayout/2. Simple Form
06-08 02:49:53.880: DEBUG/getData(382): info.activityInfo.name = com.example.android.apis.view.RelativeLayout2
06-08 02:49:53.880: DEBUG/getData(382): label = Views/Layouts/RelativeLayout/2. Simple Form
06-08 02:49:53.880: DEBUG/getData(382): prefix = 
06-08 02:49:53.880: DEBUG/getData(382): if (prefix.length() == 0 || label.startsWith(prefix))
06-08 02:49:53.880: DEBUG/getData(382): nextLabel = Views
06-08 02:49:53.880: DEBUG/getData(382): prefixPath = null
06-08 02:49:53.880: DEBUG/getData(382): labelPath = [Ljava.lang.String;@405270c8


2套Activity目录

06-08 02:05:50.591: DEBUG/getData(346): label = Content/Storage/External Storage
06-08 02:05:50.591: DEBUG/getData(346): nextLabel = Content
06-08 02:05:50.661: DEBUG/getData(346): label = Content/Resources/Styled Text
06-08 02:05:50.661: DEBUG/getData(346): nextLabel = Content
06-08 02:05:50.661: DEBUG/getData(346): label = Content/Assets/Read Asset
06-08 02:05:50.661: DEBUG/getData(346): nextLabel = Content
06-08 02:05:50.692: DEBUG/getData(346): label = Content/Resources/Resources
06-08 02:05:50.692: DEBUG/getData(346): nextLabel = Content
06-08 02:05:50.692: DEBUG/getData(346): label = Content/Provider/Pick Contact
06-08 02:05:50.692: DEBUG/getData(346): nextLabel = Content


06-08 02:05:50.692: DEBUG/getData(346): label = OS/Morse Code
06-08 02:05:50.692: DEBUG/getData(346): nextLabel = OS
06-08 02:05:50.741: DEBUG/getData(346): label = OS/Sensors
06-08 02:05:50.741: DEBUG/getData(346): nextLabel = OS
06-08 02:05:50.741: DEBUG/getData(346): label = OS/Rotation Vector
06-08 02:05:50.741: DEBUG/getData(346): nextLabel = OS
06-08 02:05:50.780: DEBUG/getData(346): label = OS/SMS Messaging
06-08 02:05:50.780: DEBUG/getData(346): nextLabel = OS

=============================================================================
后续层次的activity列表也都采用ApiDemos这个Activity

在创建activity中,获得当前activity的lable路径
public void onCreate(Bundle savedInstanceState)
   String path = intent.getStringExtra("com.example.android.apis.Path");

在创建ListActivity项目中,每个项目对应1个新的Activity。
如果新的activity还是1个列表Activity。
构建intent时,需要保存这个activity的lable路径
protected Intent browseIntent(String path)
   Intent result = new Intent();
   result.setClass(this, ApiDemos.class);
   result.putExtra("com.example.android.apis.Path", path);

如果不是列表Activity,就是最底层的具体的功能activity了。
构建Intent是保存包名和其他Activity类型
protected Intent activityIntent(String pkg, String componentName) {
        Intent result = new Intent();
        result.setClassName(pkg, componentName);

触发intent,显示Intent指定的Activity类型。

连续深入列表Activity的过程如下:
06-08 05:56:48.590: DEBUG/ListItemClick(514): path = intent.getStringExtra = 
06-08 05:56:49.330: DEBUG/ListItemClick(514): intent.putExtra = App
06-08 05:56:50.380: DEBUG/ListItemClick(514): intent.putExtra = Content
06-08 05:56:50.530: DEBUG/ListItemClick(514): intent.putExtra = OS
06-08 05:56:50.540: DEBUG/ListItemClick(514): intent.putExtra = Views
06-08 05:56:52.381: DEBUG/ListItemClick(514): intent.putExtra = Graphics
06-08 05:56:53.271: DEBUG/ListItemClick(514): intent.putExtra = Media
06-08 05:56:53.281: DEBUG/ListItemClick(514): intent.putExtra = Text
06-08 05:56:53.331: DEBUG/ListItemClick(514): intent.putExtra = NFC

06-08 05:58:16.340: DEBUG/ListItemClick(514): position, id, title = 0, 0, App
06-08 05:58:16.440: DEBUG/ListItemClick(514): path = intent.getStringExtra = App
06-08 05:58:17.321: DEBUG/ListItemClick(514): intent.putExtra = App/Activity
06-08 05:58:17.780: DEBUG/ListItemClick(514): intent.putExtra = App/Service
06-08 05:58:17.910: DEBUG/ListItemClick(514): intent.putExtra = App/Alarm
06-08 05:58:17.920: DEBUG/ListItemClick(514): intent.putExtra = App/Notification
06-08 05:58:18.020: DEBUG/ListItemClick(514): intent.putExtra = App/Search
06-08 05:58:18.060: DEBUG/ListItemClick(514): intent.putExtra = App/Menu
06-08 05:58:18.070: DEBUG/ListItemClick(514): intent.putExtra = App/Preferences

06-08 05:58:27.281: DEBUG/ListItemClick(514): position, id, title = 0, 0, Activity
06-08 05:58:27.371: DEBUG/ListItemClick(514): path = intent.getStringExtra = App/Activity

=============================================================================
1,运行时分析的功能很不错,代码用来处理统一的数据结构,更加简洁。
   ApiDemos专门用来列举下一层activity的目录清单。

2,原来以为ApiDemos只是1个启动用的Activity,没想到功能这么强大。
   接下来,只要看每个具体功能的Activity就可以了。
   幸亏没有把他放一放。

3,intent是一个命令对象,包括需要创建的activity的类型,还可以附带一些命令参数。

Sunday, June 5, 2011

在ubuntu中使用ovpn文件设置openvpn

先简单说个结果:

安装openvpn

sudo apt-get install network-manager-openvpn openvpn


如果你有1个openvpn配置文件,是ovpn形式的,在ubuntu下怎么导入这些设置呢?
1,把ovpn文件改名为conf文件
2,提取其中的证书文件,如 ca.crt, user.crt, private.key,
     比如ovpn文件中保存为:
 <ca>
-----BEGIN CERTIFICATE-----
MIIDijCCAvOgAwIBAgIJAMNr50wqSaM7MA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEjMCEG
......
MIKdTHYQotNz0MHtOTIyJKBDqzNikGVes8F0yfnOSOsuoBJzMwbmVAKKPUxExys4
Xz9s2a2wboOb+NfrKbkOsBrL8EzytrZ8SELDF8cOPqLc8P/5/v8dd3zI/vKTNw==
-----END CERTIFICATE-----
</ca>    
    在ca.crt文件中保存为:
-----BEGIN CERTIFICATE-----
MIIDijCCAvOgAwIBAgIJAMNr50wqSaM7MA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEjMCEG
......
MIKdTHYQotNz0MHtOTIyJKBDqzNikGVes8F0yfnOSOsuoBJzMwbmVAKKPUxExys4
Xz9s2a2wboOb+NfrKbkOsBrL8EzytrZ8SELDF8cOPqLc8P/5/v8dd3zI/vKTNw==
-----END CERTIFICATE-----

3,在conf文件中,使用上述文件的位置描述替代原有的配置段落,比如
ca /home/wang/openvpn.cfg/ca.crt
cert /home/wang/openvpn.cfg/user.crt
key /home/wang/openvpn.cfg/private.key
tls-auth /home/wang/openvpn.cfg/ta.key

4,把conf文件导入vpn设置界面后,就是基本完成了设置。不过还要手工补充一些设置, 比如:采用“带有证书的密码(TLS)”后,可以输入帐号、密码。
不要写私钥密码,其实从ovpn文件中也看不出来。
 高级-TLS认证中,使用附加TLS认证,密钥方向无。

现在至少可以使用openvpn了。
应该还有更合理的方式,  以后再请教也来得及。


 =================================================
参考资料:

Ubuntu 10.04 OpenVPN



Ubuntu 10.04 OpenVPN by StrongVPN tutorial


再回顾一下摸索的过程:

1,按照参考资料设置,结果报错。当时的疑问集中在TLS相关设置上,如TLS密码,加密方向。 
2,导出错误的设置,想看看和ovpn设置的差别在哪里?差别在于使用内嵌方式还是外接方式保存各种证书和密钥 。
3,修改ovpn文件,修改后缀为conf,采用外接方式保存证书和密钥 。
4,导入修改后的conf文件,基本就对了。