[转载]Android输入法框架分析(1)-三大组件 - 山水含清晖 - 博客园

mikel阅读(1075)

[转载]Android输入法框架分析(1)-三大组件 – 山水含清晖 – 博客园.

Android输入法框架(Input Method Framework,IMF)是Android中非常重要的模块,它分布于三个部分(确切的说,是三个进程),

  1. 包含编辑框的客户(Client)app,表示普通的使用输入法的app进程。当点击编辑框时,会切换出当前选中的输入法;当用户在输入法输入字 符,提交候选词,则会更新到编辑框中。为了完成这些行为,它需要跟下面的两个输入法相关服务进行交互。对于普通app开发者而言,他们一般使用系统提供的 EditText,该类和其父类TextView已经很好的封装了跟输入法服务之间的交互;如果是自定义的编辑框,则需要自己处理这种交互。
  2. 输入法(input method,IME)服务(service),是具体的输入法进程,例如自带的拉丁输入法或者谷歌,搜狗等拼音输入法。它们一般提供一个输入窗口,可以 根据用户的要求打开或者关闭;可以把用户输入的字符和提交的候选词更新给client等等。这是一个用户级别的Service。为了方便开发者编写新的输 入法,IMF提供了抽象基类InputMethodService供输入法开发者扩展。
  3. 输入法管理者(Input method manager,IMM)服务(Service),这是一个Android系统级的服务,用于管理多个输入法以及同其他系统服务(例如window manager service)进行交互。这部分代码是app开发者和IME开发者都不需要关心的。

为了方便描述,后文分别称该三个组件为:client,IME和IMM。

这三个部分需用共同合作才能完成输入法的工作。例如打开一个app,并且一个edit框获取了focus焦点。此时client会通知IMM打开输 入法,然后IMM查看当前选中的IME,并调用该IME的start操作。这个简单的开始操作需要三个组件的配合。再比如用户提交了候选词,此时IME需 要将候选词告诉client。这里须要IME和client的合作。

因为这三个部分是三个进程,所以它们之间必须通过IPC进行通讯。在Android中,IPC机制是通过binder机制和aidl接口进行通信的。

  1. 对于Client而言,它提供了两个接口IInputMethodClient.aidlIInputContext.aidl。前者是供IMM调用的,后者是供IME调用的。
  2. IMM提供了接口IInputMethodManager.aidl供其他两个组件调用。
  3. IME提供了两个接口IInputMethod.aidlIInputMethodSession.aidl,前者供IMM调用,后者供client直接调用。

这些调用关系可以参考下图:

这些接口定义都在java/com/android/internal/view目录下。那这些接口是如何实现的呢?

先看client提供的接口。IInputContext是由同一目录下的IInputConnectionWrapper实现的。正如名字所说,它只是一个wrapper,它把接收到的IPC消息委托给你InputConnection的一个实现。例如对于EditText而言,实现是EditableInputConneciton
在调用方,IME也不是直接操作IInputContext接口。它会调用实现了InputConnection接口的InputConnectionWrapper(也在前面目录下)。该对象封装了从client传过来的IInputContext实例。
对于IME对client的调用操作,它会经历下面流程(以调用commitText为例,它表示提交候选词):

  1. InputConnectionWrapper.commitText被IME进程中其他代码调用。
  2. 委托给IInputContext stub对象。
  3. 通过IPC跨进程传输
  4. IInputConnectionWrapper接受到该消息并调用其commitText处理。
  5. 如果当前在主UI线程,则直接嗲用InputConnection的实现(例如EditableInputConnection)的commitText方法;否则通过handler进行线程间通信。

在IME看来,接口是InputConnection;在client上,实现的也是InputConnection。IInputContext完全被隐藏起来了。所以Android官方文档说IME通过InputConnection接口来操作client。

再看client提供的另外一个接口IInputMethodClient,IMM是直接调用的。IMM的代码就是InputMethodManagerService。在client端,InputMethodManager类中有一个对IInputMethodClient.stub的实现。

对IMM提供的IInputMethodManager接口而言,它是由InputMethodManagerService来实现的。在 client端,InputMethodManager的getInstance(是个singleton)会调用 ServiceManager.getService(Context.INPUT_METHOD_SERVICE)获取该接口,然后创建 InputMethodManager。所以对于client而言,它跟IMM的交互都是通过InputMethodManager来封装完成的,并不需 要关心IInputMethodManager接口。对于IME,如果它想操作IMM,也同样通过InputMethodManager。

下面是IME提供的接口。类似于使用InputConnection封装IInputContext,有两个接口InputMethodInputMethodSession分别对应着了IInputMethod和IInputSession。
对于InputMethod,IInputMethodWrapper实现了IInputMethod.stub。对于收的的IPC请求,都转发给InputMethod实例。一般而言,这个实例是InputMethodService中 定义的InputMethodImpl。该实例是InputMethodService的内部类,所以可以操作InputMethodService。对 于其客户IMM,InputMethodManagerService会直接调用IInputMethod的方法发起IPC请求。
对于InputMethodSessoin,非常类似,IInputMethodSessionWrapper实 现了IInputMethodSession.stub。同样在InputMethodService中有InputMethodSessionImpl 实现了InputMethodSession接口,有一个该类型的对象在IInputMethodSessionWrapper中,负责具体处理过来的 IPC消息。在client端,InputMethodManager有一个IInputMethodSession mCurMethod对象。开发者只需要调用InputMethodManager,而由InputMethodManager调用 IInputMethodSession的IPC操作。

总结一下,无论是client还是IME的开发者,都不需要直接操作aidl接口。在client端,对于IMM和IME的操作都是通过 InputMethodManager发起的,用户甚至不用关心这些IPC操作是发给谁的;在IME端,开发者通过InputConnection给 client发IPC消息,通过InputMethodManager给IMM发。而在IMM端,虽然是直接操作aidl接口的stub对象,但因为一般 开发者不需要改写它,所以也无关紧要。通过这种方式,简化了开发者的跨进程操作。

最后再总结下代码位置:

代码主要就是这四个目录。

[转载]C#中Trim、TrimStart、TrimEnd的用法 - 猴子哥 - 博客园

mikel阅读(869)

转载C#中Trim、TrimStart、TrimEnd的用法 – 猴子哥 – 博客园.

    这三个方法用于删除字符串头尾出现的某些字符。Trim()删除字符串头部及尾部出现的空格,删除的过程为从外到内,直到碰到一个非空格的字符为止,所以 不管前后有多少个连续的空格都会被删除掉。TrimStart()只删除字符串的头部的空格。TrimEnd()只删除字符串尾部的空格。

      如果这三个函数带上字符型数组的参数,则是删除字符型数组中出现的任意字符。如Trim(“abcd”.ToCharArray())就是删除字符串头部及尾部出现的a或b或c或d字符,删除的过程直到碰到一个既不是a也不是b也不是c也不是d的字符才结束。
这里最容易引起的误会就是以为删除的是”abcd”字符串。如下例:
string s = ” from dual union all “;
s = s.Trim().TrimEnd(“union all”.ToCharArray());
可能有人以为上面s的最终结果是”from dual”,但真正的结果是”from d”。需要注意的是这种写法执行的删除对象是字符数组中出现的任意字符,而不是这些字符连在一起组成的字符串! 

一般TRIM函数用法:
Trim()   功能删除字符串首部和尾部的空格。   语法Trim ( string )   参数string:string类型,指定要删除首部和尾部空格的字符串返回值String。函数执行成功时返回删除了string字符串首部和尾部 空格的字符串,发生错误时返回空字符串(””)。如果任何参数的值为NULL,Trim()函数返回NULL。    ========================================================================   SQL 中的 TRIM 函数是用来移除掉一个字串中的字头或字尾。最常见的用途是移除字首或字尾的空白。这个函数在不同的资料库中有不同的名称:  

 MySQL: TRIM(), RTRIM(), LTRIM()   Oracle: RTRIM(), LTRIM()  

 SQL Server: RTRIM(), LTRIM()   

各种 trim 函数的语法如下:

 TRIM([[位置] [要移除的字串] FROM ] 字串): [位置] 的可能值为 LEADING (起头), TRAILING (结尾), or BOTH (起头及结尾)。 这个函数将把 [要移除的字串] 从字串的起头、结尾,或是起头及结尾移除。如果我们没有列出 [要移除的字串] 是什么的话,那空白就会被移除。   LTRIM(字串): 将所有字串起头的空白移除。   RTRIM(字串): 将所有字串结尾的空白移除。

[转载]iOS- 如何集成支付宝 - 清澈Saup - 博客园

mikel阅读(1935)

[转载]iOS- 如何集成支付宝 – 清澈Saup – 博客园.

现在不少app内都集成了支付宝功能
使用支付宝进行一个完整的支付功能,大致有以下步骤:
1>先与支付宝签约,获得商户ID(partner)和账号ID(seller)
(这个主要是公司的负责)
2>下载相应的公钥私钥文件(加密签名用)
3>下载支付宝SDK(登录网站http://club.alipay.com/
里面提供了非常详细的文档、如何签约、如何获得公钥私钥、如何调用支付接口。
4>生成订单信息
5>调用支付宝客户端,由支付宝客户端跟支付宝安全服务器打交道
6>支付完毕后返回支付结果给商户客户端和服务器
SDK里有集成支付宝功能的一个Demo>  集成支付功能的具体操作方式,可以参考Demo
当第一次打开Demo时,可能会出现以下问题:
错误原因很简单,就是项目的部署版本设置太低了,从3.0改为4.3即可
要想集成支付功能,依赖以下文件夹的库文件(把这3个添加到你的客户端中)
调用支付接口可以参考AlixPayDemoViewController的下面方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

如何创建订单 ( 订单根据自己公司看是什么样的)

如何签名

如何调用支付接口

都在这个方法里面了

//
//选中商品调用支付宝快捷支付
//
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    /*
     *点击获取prodcut实例并初始化订单信息
     */
    Product *product = [_products objectAtIndex:indexPath.row];
    
    /*
     *商户的唯一的parnter和seller。
     *本demo将parnter和seller信息存于(AlixPayDemo-Info.plist)中,外部商户可以考虑存于服务端或本地其他地方。
     *签约后,支付宝会为每个商户分配一个唯一的 parnter 和 seller。
     */
    //如果partner和seller数据存于其他位置,请改写下面两行代码
    NSString *partner = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"Partner"];
    NSString *seller = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"Seller"];
    
    //partner和seller获取失败,提示
    if ([partner length] == 0 || [seller length] == 0)
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
                                                        message:@"缺少partner或者seller。" 
                                                       delegate:self 
                                              cancelButtonTitle:@"确定" 
                                              otherButtonTitles:nil];
        [alert show];
        [alert release];
        return;
    }
    
    /*
     *生成订单信息及签名
     *由于demo的局限性,本demo中的公私钥存放在AlixPayDemo-Info.plist中,外部商户可以存放在服务端或本地其他地方。
     */
    //将商品信息赋予AlixPayOrder的成员变量
    AlixPayOrder *order = [[AlixPayOrder alloc] init];
    order.partner = partner;
    order.seller = seller;
    order.tradeNO = [self generateTradeNO]; //订单ID(由商家自行制定)
    order.productName = product.subject; //商品标题
    order.productDescription = product.body; //商品描述
    order.amount = [NSString stringWithFormat:@"%.2f",product.price]; //商品价格
    order.notifyURL =  @"http://www.xxx.com"; //回调URL
    
    //应用注册scheme,在AlixPayDemo-Info.plist定义URL types,用于快捷支付成功后重新唤起商户应用
    NSString *appScheme = @"AlixPayDemo"; 
    
    //将商品信息拼接成字符串
    NSString *orderSpec = [order description];
    NSLog(@"orderSpec = %@",orderSpec);
    
    //获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
    id<DataSigner> signer = CreateRSADataSigner([[NSBundle mainBundle] objectForInfoDictionaryKey:@"RSA private key"]);
    NSString *signedString = [signer signString:orderSpec];
    
    //将签名成功字符串格式化为订单字符串,请严格按照该格式
    NSString *orderString = nil;
    if (signedString != nil) {
        orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
                                 orderSpec, signedString, @"RSA"];
        
        //获取快捷支付单例并调用快捷支付接口
        AlixPay * alixpay = [AlixPay shared];
        int ret = [alixpay pay:orderString applicationScheme:appScheme];
        
        if (ret == kSPErrorAlipayClientNotInstalled) {
            UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"提示" 
                                                                 message:@"您还没有安装支付宝快捷支付,请先安装。" 
                                                                delegate:self 
                                                       cancelButtonTitle:@"确定" 
                                                       otherButtonTitles:nil];
            [alertView setTag:123];
            [alertView show];
            [alertView release];
        }
        else if (ret == kSPErrorSignError) {
            NSLog(@"签名错误!");
        }

    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

主要集成的关键就是下面几步:

//.封装订单模型
AlixPayOrder *order = [[AlixPayOrder alloc] init];
// 生成订单描述
NSString *orderSpec = [order description];

//2.签名
id<DataSigner> signer = CreateRSADataSigner(@“私钥key”);
// 传入订单描述 进行 签名
NSString *signedString = [signer signString:orderSpec];


//3.生成订单字符串
NSString *orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
                         orderSpec, signedString, @"RSA"];

//4.调用支付接口
AlixPay * alixpay = [AlixPay shared];
// appScheme:商户自己的协议头
int ret = [alixpay pay:orderString applicationScheme:appScheme];

[转载]跨服务器连接查询(补充) - select left('claro',2)的专栏 - 博客频道 - CSDN.NET

mikel阅读(1153)

[转载]跨服务器连接查询(补充) – select left(‘claro’,2)的专栏 – 博客频道 – CSDN.NET.

如何方便的建立远程链接服务器   的文章中说明如何在SQL2005环境下方便快捷有效的建立跨服务器查询;

 

-- 很多朋友问局域网多数据库服务器访问该如何操作?下面简单说明如何配置远程链接访问。

/*********** 环境说明 ***********/

-- 源机器 IP 为 10. 0.0.211 ,在该机器所在数据库建立与目标机器的远程数据库链接。

-- 目标机器 IP 为 10. 0.0.222 。

-- 步骤(一)是指直接用 IP 进行远程链接,个人觉得用 IP 链接虽然麻烦,但是直观而且在多服务器操作时不会出现误链接的情况。

-- 步骤(二)是指直接用别名进行远程链接,较方便;但在高压力,大脑暂时短路情况下容易出现误链接数据库情况发生。

 

/*********** 步骤(一) ***********/

-- 建立连接服务器

EXEC sp_addlinkedserver   '10.0.0.222' , 'SQL Server'

 

/*-- 如果数据库有架构名需要做远程登录之间的映射

-- 创建链接服务器上远程登录之间的映射

EXEC sp_addlinkedsrvlogin '10.0.0.222','false','SA',' 架构名 ',' 登录密码 '

*/

-- 查询数据

-- 含架构名

select top 10 * from [10.0.0.222]. 数据库名 . 架构名 . 表名

-- 不含架构名

select top 10 * from [10.0.0.222]. 数据库名 . dbo. 表名

 

 

-- 查看链接服务器信息

select name , product, provider, data_source, query_timeout, lazy_schema_validation, is_remote_login_enabled, is_rpc_out_enabled

  from sys.servers

where is_linked= 1

 

/*********** 建立步骤(二) ***********/

/*-- 如果用 IP 连接觉得麻烦,可以新建别名

-- 配置链接服务器属性

exec sp_serveroption '222','name','10.0.0.222'

*/

-- 查询数据

-- 含架构名

select top 10 * from 222. 数据库名 . 架构名 . 表名

-- 不含架构名

select top 10 * from 222. 数据库名 . dbo. 表名

 

 

-- 删除链接服务器登录名映射

exec sp_droplinkedsrvlogin '10.0.0.222' ,NULL

 

-- 删除链接服务器属性

exec sp_dropserver '222'

 

--注:如果执行 删除链接服务器时提示如下错误时,

消息 15190,级别 16,状态 1,过程 sp_dropserver,第 56 行
仍有对服务器 'XXXX' 的远程登录或链接登录。

执行

exec sp_dropserver ' 链接服务器 ' , 'droplogins' 

SQL2008和SQL2000可以跨服务器连接查询的测试实例   中说明如何在SQL2000和SQL2008环境下跨服务器的查询。

 

以上跨服务器查询都是在先建立链接服务器的基础上,

如果不建立链接服务器可以直接用SQL查询跨服务器的Table吗?

如果不建立链接服务器可以查询跨服务器的系统表吗?

 

当然可以!

SQL2008和SQL2000可以跨服务器连接查询的测试实例 中的各实例对应的sa如何不相同,建立链接服务器后仍无法查询系统表,所以这里强调可以查询跨服务器的系统表并告诉使用它的目的。

 

当然,我们都熟悉opendatasource的使用,这里也不例外。

环境:

目标服务器SERV1,IP地址10.0.0.250,数据库INTER,架构名DBO

目的:

在本地服务器查询目标服务器上的INTER数据库的表logdb。

 

脚本一:

查询服务器SERV1上INTER数据库的表logdb并返回前十行

select top 10 *

from opendatasource ( ‘SQLOLEDB’

, ‘Data Source=10.0.0.250;User ID=sa;PASSWORD=sa 密码 ;’ )   输入目标服务器地址(原因请见《如何方便的建立远程链接服务器》)

输入 sa 帐号及密码

. [INTER] . dbo . logdb

  如果结果 返回类似如下结果:

链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “登录超时已过期”。
链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “与 SQL Server 建立连接时发生了与网络相关的或特定于实例的错误。找不到或无法访问服务器。请检查实例名称是否正确以及 SQL Server 是否配置为允许远程连接。有关详细信息,请参阅 SQL Server 联机丛书。”。
消息 53,级别 16,状态 1,第 0 行
命名管道提供程序: 无法打开与 SQL Server 的连接 [53].

  请检查并确认输入的目标服务器地址及SA密码是否正确!

 

如果结果 返回类似如下结果:

 

消息 7314 ,级别 16 ,状态 1 ,第 1

链接服务器 “(null)” OLE DB 访问接口 “SQLNCLI10” 不包含表 “”INTERFACEDATA”.”dbo”.”logdb”” 。该表不存在,或者当前用户没有访问该表的权限。

  请检查并确认输入的表名是否正确!

 

 

 

脚本二:

这里我们假设输入的表名是错的,但不记得正确的表名是什么。此时通常我们会查询sys.objects是否存在该表并确认名称。这里也不例外:

查询服务器SERV1上INTER数据库中是否存在表 db log并返回正确的表名

select NAME

from opendatasource ( ‘SQLOLEDB’

, ‘Data Source=10.0.0.250;User ID=sa;PASSWORD=sa 密码 ;’ )

. [INTER] . [SYS] . [OBJECTS] 查询目标服务器的系统表

where TYPE = ‘U’ and NAME LIKE ‘%log’ 查询用户表以 “log” 结尾的表名

ORDER BY 1  NAME 排序

 

 

 

Posted by: select left(‘claro’,2) @10:27:00

lable: SQL

[转载]android获取web服务器端session并验证登陆 - 云忠飞鸽 - 博客频道 - CSDN.NET

mikel阅读(1023)

[转载]android获取web服务器端session并验证登陆 – 云忠飞鸽 – 博客频道 – CSDN.NET.

传统网页实现用户登陆一般采用session或cookie记录用户基本信息又或者两者结合起来使用。android也 可以采用session实现用户登陆验证并记录用户登陆状态时的基本信息,session是在服务器端的;而类似cookie的记录方式,则可以在客户端 采用xml文件记录用户基本信息,重要数据则可以加密存放客户端。Android实现的session登陆功能与网页请求不同的是,网页形式的一次成功的 登陆请求后,再点击其他页面时,session一直是存在的,在一定时间内是有效的;而采用Android客户端请求的一次成功登陆后,再次发送新的请 求,则会产生新的session,而不是原来的。这就需要记录session的id号,并在整个请求过程中都记录并传递这个id号,才能保证 session的一致性。

以获取php session为例,主要思路实现分为客户端与服务器端3个步骤。

附件:  GetWebSession.zip (71.44 KB, 下载次数: 63) 

2012-5-8 14:09 上传

点击文件名下载附件
下载积分: 下载豆 -1

1.)客户端(Android

建立一个名为GetWebSession的android项目,编写GetWebSession.java,LoginSuccessActivity.java,GetUserInfoActivity.java三个activity类。

 

1. GetWebSession.java主要是实现布局界面以及发送用户名和密码到php服务器端验证,如果验证成功则跳转到 LoginSuccessActivity.java类。GetWebSession.java主要涉及到与服务器端连接请求,对从服务器端返回的 json数据(如用户id,session等)进行解析,并存入HashMap,传递到LoginSuccessActivity.java

 

代码如下:

 

  1. package com.login.main;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import org.apache.http.HttpEntity;
  8. import org.apache.http.HttpResponse;
  9. import org.apache.http.client.ClientProtocolException;
  10. import org.apache.http.client.entity.UrlEncodedFormEntity;
  11. import org.apache.http.client.methods.HttpPost;
  12. import org.apache.http.impl.client.DefaultHttpClient;
  13. import org.apache.http.message.BasicNameValuePair;
  14. import org.apache.http.protocol.HTTP;
  15. import org.apache.http.util.EntityUtils;
  16. import org.json.JSONException;
  17. import org.json.JSONObject;
  18. import android.app.Activity;
  19. import android.content.Context;
  20. import android.content.Intent;
  21. import android.os.Bundle;
  22. import android.view.View;
  23. import android.view.View.OnClickListener;
  24. import android.widget.Button;
  25. import android.widget.EditText;
  26. import android.widget.Toast;
  27. public class GetWebSession extends Activity {
  28. /** Called when the activity is first created. */
  29. private EditText user;
  30. private EditText password;
  31. private Button loginBtn;
  32. private Button logoutBtn;
  33. //主要是记录用户会话过程中的一些用户的基本信息
  34. private HashMap<String, String> session =new HashMap<String, String>();
  35. @Override
  36. public void onCreate(Bundle savedInstanceState) {
  37. super.onCreate(savedInstanceState);
  38. setContentView(R.layout.main);
  39. user=(EditText)findViewById(R.id.user);
  40. password=(EditText)findViewById(R.id.password);
  41. loginBtn=(Button)findViewById(R.id.loginBtn);
  42. loginBtn.setOnClickListener(loginClick);
  43. logoutBtn=(Button)findViewById(R.id.logoutBtn);
  44. logoutBtn.setOnClickListener(logoutClick);
  45. }
  46. OnClickListener loginClick=new OnClickListener() {
  47. @Override
  48. public void onClick(View v) {
  49. // TODO Auto-generated method stub
  50. if(checkUser()){
  51. Toast.makeText(v.getContext(), “用户登录成功!”, Toast.LENGTH_SHORT).show();
  52. Context context = v.getContext();
  53. Intent intent = new Intent(context,
  54. LoginSuccessActivity.class);
  55. //传递session参数,在用户登录成功后为session初始化赋值,即传递HashMap的值
  56. Bundle map = new Bundle();
  57. map.putSerializable(“sessionid”, session);
  58. intent.putExtra(“session”, map);
  59. context.startActivity(intent); // 跳转到成功页面
  60. }
  61. else
  62. Toast.makeText(v.getContext(), “用户验证失败!”, Toast.LENGTH_SHORT).show();
  63. }
  64. };
  65. OnClickListener logoutClick=new OnClickListener() {
  66. @Override
  67. public void onClick(View v) {
  68. // TODO Auto-generated method stub
  69. System.exit(0);
  70. }
  71. };
  72. private boolean checkUser(){
  73. String username=user.getText().toString();
  74. String pass=password.getText().toString();
  75. DefaultHttpClient mHttpClient = new DefaultHttpClient();
  76. HttpPost mPost = new HttpPost(“http://10.0.2.2/web/php/login.php”);
  77. //传递用户名和密码相当于
  78. //http://10.0.2.2/web/php/login.php?username=”&password=”
  79. List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
  80. pairs.add(new BasicNameValuePair(“username”, username));
  81. pairs.add(new BasicNameValuePair(“password”, pass));
  82. try {
  83. mPost.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
  84. } catch (UnsupportedEncodingException e) {
  85. // TODO Auto-generated catch block
  86. e.printStackTrace();
  87. }
  88. try {
  89. HttpResponse response = mHttpClient.execute(mPost);
  90. int res = response.getStatusLine().getStatusCode();
  91. if (res == 200) {
  92. HttpEntity entity = response.getEntity();
  93. if (entity != null) {
  94. String info = EntityUtils.toString(entity);
  95. System.out.println(“info———–“+info);
  96. //以下主要是对服务器端返回的数据进行解析
  97. JSONObject jsonObject=null;
  98. //flag为登录成功与否的标记,从服务器端返回的数据
  99. String flag=””;
  100. String name=””;
  101. String userid=””;
  102. String sessionid=””;
  103. try {
  104. jsonObject = new JSONObject(info);
  105. flag = jsonObject.getString(“flag”);
  106. name = jsonObject.getString(“name”);
  107. userid = jsonObject.getString(“userid”);
  108. sessionid = jsonObject.getString(“sessionid”);
  109. } catch (JSONException e) {
  110. // TODO Auto-generated catch block
  111. e.printStackTrace();
  112. }
  113. //根据服务器端返回的标记,判断服务端端验证是否成功
  114. if(flag.equals(“success”)){
  115. //为session传递相应的值,用于在session过程中记录相关用户信息
  116. session.put(“s_userid”, userid);
  117. session.put(“s_username”, name);
  118. session.put(“s_sessionid”, sessionid);
  119. return true;
  120. }
  121. else{
  122. return false;
  123. }
  124. }
  125. else{
  126. return false;
  127. }
  128. }
  129. } catch (ClientProtocolException e) {
  130. // TODO Auto-generated catch block
  131. e.printStackTrace();
  132. } catch (IOException e) {
  133. // TODO Auto-generated catch block
  134. e.printStackTrace();
  135. }
  136. return false;
  137. }
  138. }

复制代码

 

2. LoginSuccessActivity.java主要获取php的session唯一的标识id以及用户的一些基本信息,session id则作为本次用户登录状态在服务器的唯一标识,即确定用户的唯一状态进行相关操作。LoginSuccessActivity.java类的方法与 GetWebSession.java类似。其主要功能是获取session id后再次发送session id到服务器进行验证,根据封装的session数据验证用户操作权限等。

 

代码如下:

 

  1. package com.login.main;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import org.apache.http.HttpEntity;
  8. import org.apache.http.HttpResponse;
  9. import org.apache.http.client.ClientProtocolException;
  10. import org.apache.http.client.entity.UrlEncodedFormEntity;
  11. import org.apache.http.client.methods.HttpPost;
  12. import org.apache.http.impl.client.DefaultHttpClient;
  13. import org.apache.http.message.BasicNameValuePair;
  14. import org.apache.http.protocol.HTTP;
  15. import org.apache.http.util.EntityUtils;
  16. import org.json.JSONException;
  17. import org.json.JSONObject;
  18. import android.app.Activity;
  19. import android.content.Context;
  20. import android.content.Intent;
  21. import android.os.Bundle;
  22. import android.view.View;
  23. import android.view.View.OnClickListener;
  24. import android.widget.Button;
  25. import android.widget.TextView;
  26. import android.widget.Toast;
  27. public class LoginSuccessActivity extends Activity{
  28. private HashMap<String, String>session;
  29. @SuppressWarnings(“unchecked”)
  30. @Override
  31. protected void onCreate(Bundle savedInstanceState) {
  32. // TODO Auto-generated method stub
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.login_success);
  35. //获取从登录成功后界面的传递的参数
  36. session =  (HashMap<String, String>) this.getIntent()
  37. .getBundleExtra(“session”).getSerializable(“sessionid”);
  38. //读取session的基本信息,并显示相应的控件
  39. String userid_info=session.get(“s_userid”);
  40. String username_info=session.get(“s_username”);
  41. String session_id=session.get(“s_sessionid”);
  42. //显示相应的内容到控件
  43. TextView userid_show=(TextView)findViewById(R.id.userid_show);
  44. userid_show.setText(userid_info);
  45. TextView username_show=(TextView)findViewById(R.id.username_show);
  46. username_show.setText(username_info);
  47. TextView sessionid_show=(TextView)findViewById(R.id.sessionid_show);
  48. sessionid_show.setText(session_id);
  49. //根据本次session再次获取用户信息
  50. Button getInfo=(Button)findViewById(R.id.getinfo);
  51. getInfo.setOnClickListener(getInfoClick);
  52. }
  53. OnClickListener getInfoClick=new OnClickListener() {
  54. @Override
  55. public void onClick(View v) {
  56. // TODO Auto-generated method stub
  57. if(getUserInfo()){
  58. Context context = v.getContext();
  59. Intent intent = new Intent(context,
  60. GetUserInfoActivity.class);
  61. //传递session参数,在用户登录成功后为session初始化赋值,即传递HashMap的值
  62. Bundle map = new Bundle();
  63. map.putSerializable(“sessionid”, session);
  64. intent.putExtra(“session”, map);
  65. context.startActivity(intent); // 跳转到成功页面
  66. }else{
  67. Toast.makeText(v.getContext(), “数据为空!”, Toast.LENGTH_SHORT).show();
  68. }
  69. }
  70. };
  71. private boolean getUserInfo(){
  72. String sess_username=session.get(“s_username”);
  73. String sess_userid=session.get(“s_userid”);
  74. String sess_id=session.get(“s_sessionid”);
  75. DefaultHttpClient mHttpClient = new DefaultHttpClient();
  76. HttpPost mPost = new HttpPost(“http://10.0.2.2/web/php/getinfo.php”);
  77. List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
  78. pairs.add(new BasicNameValuePair(“sess_userid”, sess_userid));
  79. pairs.add(new BasicNameValuePair(“sess_username”, sess_username));
  80. pairs.add(new BasicNameValuePair(“sess_sessionid”, sess_id));
  81. try {
  82. mPost.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
  83. } catch (UnsupportedEncodingException e) {
  84. // TODO Auto-generated catch block
  85. e.printStackTrace();
  86. }
  87. try {
  88. HttpResponse response = mHttpClient.execute(mPost);
  89. int res = response.getStatusLine().getStatusCode();
  90. if (res == 200) {
  91. HttpEntity entity = response.getEntity();
  92. if (entity != null) {
  93. String info = EntityUtils.toString(entity);
  94. System.out.println(“info———–“+info);
  95. //以下主要是对服务器端返回的数据进行解析
  96. JSONObject jsonObject=null;
  97. //flag为登录成功与否的标记,从服务器端返回的数据
  98. String flag=””;
  99. String userinfo=””;
  100. String level=””;
  101. String sessionid=””;
  102. try {
  103. jsonObject = new JSONObject(info);
  104. flag = jsonObject.getString(“flag”);
  105. userinfo = jsonObject.getString(“info”);
  106. level = jsonObject.getString(“level”);
  107. sessionid = jsonObject.getString(“sessionid”);
  108. } catch (JSONException e) {
  109. // TODO Auto-generated catch block
  110. e.printStackTrace();
  111. }
  112. //根据服务器端返回的标记,判断服务端端验证是否成功
  113. if(flag.equals(“notempty”)){
  114. //为session传递相应的值,用于在session过程中记录相关用户信息
  115. session.put(“info_userinfo”, userinfo);
  116. session.put(“info_level”, level);
  117. session.put(“info_sessionid”, sessionid);
  118. return true;
  119. }
  120. else{
  121. return false;
  122. }
  123. }
  124. else{
  125. return false;
  126. }
  127. }
  128. } catch (ClientProtocolException e) {
  129. // TODO Auto-generated catch block
  130. e.printStackTrace();
  131. } catch (IOException e) {
  132. // TODO Auto-generated catch block
  133. e.printStackTrace();
  134. }
  135. return false;
  136. }
  137. }

复制代码

 

3.GetUserInfoActivity.java类是根据用户登录后产生唯一session 标识进行操作获取用户详细信息的类。

 

代码如下:

 

  1. package com.login.main;
  2. import java.util.HashMap;
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.widget.TextView;
  6. public class GetUserInfoActivity extends Activity{
  7. private HashMap<String, String>session;
  8. @SuppressWarnings(“unchecked”)
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. // TODO Auto-generated method stub
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.get_info);
  14. //获取从登录成功后界面的再次传递的参数
  15. session =  (HashMap<String, String>) this.getIntent().
  16. getBundleExtra(“session”).getSerializable(“sessionid”);
  17. //读取session的基本信息,并显示相应的控件
  18. String session_info=session.get(“info_userinfo”);
  19. String session_level=session.get(“info_level”);
  20. String session_id=session.get(“info_sessionid”);
  21. //显示相应的内容到控件
  22. System.out.println(“session_info——–“+session_info);
  23. TextView get_info=(TextView)findViewById(R.id.get_info);
  24. get_info.setText(session_info);
  25. TextView get_level=(TextView)findViewById(R.id.get_level);
  26. get_level.setText(session_level);
  27. TextView get_sessionid=(TextView)findViewById(R.id.get_sessionid);
  28. get_sessionid.setText(session_id);
  29. }
  30. }

复制代码

 

4.三个布局的xml文件

 

(1.)main.xml

 

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:orientation=”vertical”
  4. android:layout_width=”fill_parent”
  5. android:layout_height=”fill_parent” >
  6. <TextView android:layout_height=”wrap_content”
  7. android:layout_width=”wrap_content”
  8. android:text=”用户”></TextView>
  9. <EditText android:layout_height=”wrap_content”
  10. android:text=”” android:layout_width=”fill_parent”
  11. android:singleLine=”true” android:id=”@+id/user”  ></EditText>
  12. <TextView android:layout_height=”wrap_content”
  13. android:layout_width=”wrap_content”
  14. android:text=”密码”></TextView>
  15. <EditText android:id=”@+id/password”
  16. android:layout_height=”wrap_content”
  17. android:text=”” android:layout_width=”fill_parent”
  18. android:password=”true” android:singleLine=”true”></EditText>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TableRow android:layout_width=”fill_parent”
  24. android:layout_height=”wrap_content”>
  25. <Button android:layout_height=”fill_parent”
  26. android:layout_width=”fill_parent” android:text=”登录”
  27. android:id=”@+id/loginBtn”
  28. android:layout_weight=”1″></Button>
  29. <Button android:layout_height=”fill_parent”
  30. android:layout_width=”fill_parent”
  31. android:text=”退出”
  32. android:id=”@+id/logoutBtn”
  33. android:layout_weight=”1″></Button>
  34. </TableRow> </LinearLayout> </LinearLayout>

复制代码

(2.)login_success.xml

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:layout_width=”fill_parent” android:layout_height=”fill_parent”
  4. android:orientation=”vertical”>
  5. <LinearLayout android:layout_height=”wrap_content”
  6. android:layout_width=”fill_parent”
  7. android:orientation=”horizontal”
  8. android:paddingLeft=”0dip”>
  9. <TextView
  10. android:layout_height=”fill_parent”
  11. android:layout_width=”wrap_content”
  12. android:text=”用户ID:”   >
  13. </TextView>
  14. <TextView android:layout_height=”fill_parent”
  15. android:layout_width=”fill_parent”
  16. android:text=””
  17. android:id=”@+id/userid_show” ></TextView>
  18. </LinearLayout>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TextView android:layout_height=”fill_parent”
  24. android:layout_width=”wrap_content”
  25. android:text=”用户名: ”   ></TextView>
  26. <TextView android:layout_height=”fill_parent”
  27. android:layout_width=”fill_parent”
  28. android:text=””
  29. android:id=”@+id/username_show” ></TextView>
  30. </LinearLayout>
  31. <LinearLayout android:layout_height=”wrap_content”
  32. android:layout_width=”fill_parent”
  33. android:orientation=”horizontal”
  34. android:paddingLeft=”0dip”>
  35. <TextView android:layout_height=”fill_parent”
  36. android:layout_width=”wrap_content”
  37. android:text=”本次会话:”   ></TextView>
  38. <TextView android:layout_height=”fill_parent”
  39. android:layout_width=”fill_parent”
  40. android:text=””
  41. android:id=”@+id/sessionid_show” ></TextView>
  42. </LinearLayout>
  43. <LinearLayout android:layout_height=”wrap_content”
  44. android:layout_width=”fill_parent”
  45. android:orientation=”horizontal”
  46. android:paddingLeft=”0dip”>
  47. <Button android:layout_height=”fill_parent”
  48. android:layout_width=”wrap_content”
  49. android:id=”@+id/getinfo”
  50. android:text=”根据本次会话再次获取用户信息”
  51. ></Button>
  52. </LinearLayout>
  53. </LinearLayout>

复制代码

(3.)get_info.xml

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:layout_width=”fill_parent”
  4. android:layout_height=”fill_parent”
  5. android:orientation=”vertical”>
  6. <LinearLayout android:layout_height=”wrap_content”
  7. android:layout_width=”fill_parent”
  8. android:orientation=”horizontal”
  9. android:paddingLeft=”0dip”>
  10. <TextView android:layout_height=”fill_parent”
  11. android:layout_width=”wrap_content”
  12. android:text=”用户信息: ”   >
  13. </TextView>
  14. <TextView android:layout_height=”fill_parent”
  15. android:layout_width=”fill_parent”
  16. android:text=””
  17. android:id=”@+id/get_info” ></TextView>
  18. </LinearLayout>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TextView android:layout_height=”fill_parent”
  24. android:layout_width=”wrap_content”
  25. android:text=”用户级别:”   ></TextView>
  26. <TextView android:layout_height=”fill_parent”
  27. android:layout_width=”fill_parent”
  28. android:text=””
  29. android:id=”@+id/get_level” ></TextView>
  30. </LinearLayout>
  31. <LinearLayout android:layout_height=”wrap_content”
  32. android:layout_width=”fill_parent”
  33. android:orientation=”horizontal”
  34. android:paddingLeft=”0dip”>
  35. <TextView android:layout_height=”fill_parent”
  36. android:layout_width=”wrap_content”
  37. android:text=”本次会话:”   ></TextView>
  38. <TextView android:layout_height=”fill_parent”
  39. android:layout_width=”fill_parent” android:text=””
  40. android:id=”@+id/get_sessionid” ></TextView>
  41. </LinearLayout>
  42. <LinearLayout android:layout_height=”wrap_content”
  43. android:layout_width=”fill_parent”
  44. android:orientation=”horizontal”
  45. android:paddingLeft=”0dip”> </LinearLayout> </LinearLayout>

复制代码

2.)服务器端(php)

php服务器端主要有三个文件,conn.php,login.php和getinfo.php。

 

1. conn.php是连接数据库的配置文件。

 

2. login.php主要是用来验证android客户端发送的请求,请求成功则返回flag=’success’的状态标识,采用数组记录用户基本信息, 存储用户数据到session,并且记录本次产生的session id。用户基本数据及本次session产生的id均封装成json格式(json_encode($arr)),发送android客户端。产生本次 session id的方法

 

$sessionid=session_id();//注意没有参数

 

具体代码如下:

 

  1. <?php
  2. header(“Content-Type: text/html; charset=utf-8”) ;
  3. //包含数据库连接文件
  4. include(‘conn.php’);
  5. session_start();
  6. $username = htmlspecialchars($_POST[“username”]);
  7. $password=$_POST[“password”];
  8. mySQL_query(“set names utf8”);
  9. //检测用户名及密码是否正确
  10. $check_query = mySQL_query(“select id ,name from user where name=’$username’ and
  11. password=’$password’ limit 1″);
  12. $arr=array();//空的数组,该数组主要是格式化数据并封装成json格式发送到客户端
  13. if($result = mySQL_fetch_array($check_query)){
  14. //登录成功
  15. $_SESSION[‘username’] = $result[‘name’];
  16. $_SESSION[‘userid’] = $result[‘id’];
  17. //获取当前session id
  18. $sessionid=session_id();
  19. $_SESSION[‘$sessionid’] = $sessionid;
  20. $arr = array(
  21. ‘flag’=>’success’,
  22. ‘name’=>$result[‘name’],
  23. ‘userid’=>$result[‘id’],
  24. ‘sessionid’=>$sessionid
  25. );
  26. //封装json,如果php版本低于5.2,则不支持json_encode()方法,
  27. //可以参考本文件夹中php_json_encode.php中php_json_encode()方法代替json_encode();
  28. echo json_encode($arr);
  29. } else {
  30. $arr = array(
  31. ‘flag’=>’error’,
  32. ‘name’=>”,
  33. ‘userid’=>”,
  34. ‘sessionid’=>”
  35. ); //封装json,如果php版本低于5.2,则不支持json_encode()方法,
  36. //可以参考本文件夹中php_json_encode.php中php_json_encode()方法代替json_encode();
  37. echo json_encode($arr);
  38. }
  39. ?>

复制代码

 

3. getinfo.php文件主要是用户再次查询信息验证session,而不是重新产生session,以记录用户状态。通过验证flag是否为empty判断数据是否显示。最后封装成json发送到客户端

 

获取本次session的方法:

 

$sessionid=$_POST[“sess_sessionid”];//获取android客户端的sessionid

 

session_id($sessionid);//有参数

 

session_start();//启动session

 

具体代码如下:

 

  1. <?php
  2. header(“Content-Type: text/html; charset=utf-8”) ;
  3. include(‘conn.php’);
  4. //获取从客户端LoginSuccessActivity类传递的参数
  5. $userid=$_POST[“sess_userid”];
  6. $username=$_POST[“sess_username”];
  7. //获取客户端传递的session标识
  8. $sessionid=$_POST[“sess_sessionid”];
  9. session_id($sessionid);
  10. //将会根据session id获得原来的session
  11. session_start();
  12. //获取服务器端原来session记录的username,并且根据客户端传过来的username比较进行验证操作
  13. $sess_username=$_SESSION[‘username’];
  14. if($username==$sess_username){
  15. mysql_query(“set names utf8”);
  16. //查询用户基本信息
  17. $check_query = mysql_query(“select userinfo,level from info where userid=’$userid’  limit 1”);
  18. $arr=array();//空的数组
  19. if($result = mysql_fetch_array($check_query)){
  20. $arr = array(
  21. ‘flag’=>’notempty’,
  22. ‘info’=>$result[‘userinfo’],
  23. ‘level’=>$result[‘level’],
  24. ‘sessionid’=>$sessionid
  25. );
  26. echo json_encode($arr);
  27. }
  28. } else {
  29. $arr = array(
  30. ‘flag’=>’empty’,
  31. ‘name’=>”,
  32. ‘userid’=>”,
  33. ‘sessionid’=>$sessionid
  34. );
  35. echo json_encode($arr);
  36. }
  37. ?>

复制代码

3.)数据库端(mysql)

采用mysql建立数据库,建立两个简单的数据表:user和info。

 

  1. /*
  2. MySQL Data Transfer
  3. Source Host: localhost
  4. Source Database: login
  5. Target Host: localhost
  6. Target Database: login
  7. Date: 2011-6-14 11:10:46
  8. */
  9. SET FOREIGN_KEY_CHECKS=0;
  10. — —————————-
  11. — Table structure for info
  12. — —————————-
  13. CREATE TABLE `info` (
  14. `id` int(12) NOT NULL AUTO_INCREMENT,
  15. `userid` int(12) DEFAULT NULL,
  16. `userinfo` varchar(100) DEFAULT NULL,
  17. `level` int(2) DEFAULT NULL,
  18. PRIMARY KEY (`id`),
  19. KEY `useid` (`userid`),
  20. CONSTRAINT `useid` FOREIGN KEY (`userid`) REFERENCES `user` (`id`)
  21. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  22. — —————————-
  23. — Table structure for user
  24. — —————————-
  25. CREATE TABLE `user` (
  26. `id` int(12) NOT NULL AUTO_INCREMENT,
  27. `name` varchar(20) DEFAULT NULL,
  28. `password` varchar(20) DEFAULT NULL,
  29. `status` int(2) DEFAULT NULL,
  30. PRIMARY KEY (`id`)
  31. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  32. — —————————-
  33. — Records
  34. — —————————-
  35. INSERT INTO `info` VALUES (‘1’, ‘1’, ‘charlie  is a developer.’, ‘1’);
  36. INSERT INTO `info` VALUES (‘2’, ‘2’, ‘william is a boss.’, ’20’);
  37. INSERT INTO `user` VALUES (‘1’, ‘charlie’, ‘password’, ‘1’);
  38. INSERT INTO `user` VALUES (‘2’, ‘william’, ‘mypassword’, ‘2’);

复制代码

运行效果如图:
user-login-ui.jpg

2012-5-8 14:14 上传

下载附件 (149.96 KB)

图 -1 GetWebSession.java类的布局
login-and-get-session.jpg

2012-5-8 14:12 上传

下载附件 (61.43 KB)

图 -2 LoginSuccessActivity.java类获取的session id以及用户基本信息
get-user-info-by-session-again.jpg

[转载]通过Android View的两种事件响应方法比较inheritance和composition - 山水含清晖 - 博客园

mikel阅读(1041)

[转载]通过Android View的两种事件响应方法比较inheritance和composition – 山水含清晖 – 博客园.

Android view有两种主要的处理事件的方式,

  1. 在View的子类中覆盖onXXX方法。因为这是在子类中通过覆盖的方式来响应事件,我称之为基于继承(inheritance)的响应方式。
  2. 调用View.setXXXListener,参数会实现View.OnXXXListener接口。因为View对象和Listner组合起来完成工作,我称之为基于组合(composition)的响应方式。

例如对于touch事件而言,View.dispatchTouchEvent接收到touch事件对象,然后:

  1. 调用通过View.setOnTouchListener注册的listener。
  2. 调用可以被子类覆盖的onTouchEvent方法。

举个简单例子,一个Activity中有一个edit框(EditText对象)。EditText是通过代码添加的到Activity中的。我希望在点击它时,自动把该框的底色设置为红色。

如果采用第一种方式,其EditText的子类是:

public class TouchChangeBackgroundColorEditText extends EditText {

    public MyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.setBackgroundColor(Color.RED);
        return super.onTouchEvent(event);
    }
}

然后我们在Activity中使用该EditText的子类。

如果采用第二种方法,则需要创建一个对touch事件的listener:

public class TouchChangeBackgroundColorListener implements View.OnTouchListener {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        v.setBackgroundColor(Color.RED);
        return false;
    }
}

然后在Activity中使用普通EditText,并对该EditText调用setOnTouchListener(new TouchChangeBackgroundColorListener)。

然后用户又想对某些EditText使用Key event来改变背景颜色,那如果采用基于继承的方式,用户需要创建KeyChangeBackGroundColorEditText的子类,在其中覆盖onKeyDown方法;如果采用组合的方式,则需要实现OnKeyListener并添加到EditText中。

现在问题来了,用户希望某些EditText是不响应touch和key事件的,有些只响应一种,有些两种都响应。那么如果采用继承的方式,那需要四种对象,一个基本都EditText和三种针对only touch,only key和both touch and key的子类。

但如果采用组合的方式,我们仍然只需要两个listener,只需对不同的EditText添加不同的listener即可。

如果更复杂,我们还想针对trackball改变颜色,那如果采用继承,则可能有3×3=9种类。但采用组合,只需要三种listener,然后EditText根据需要添加即可。

所以说,在可能有多种因素导致变化的时候,继承可能导致对象种类(即类)爆炸式增长;而组合可以用不同的对象封装不同的变化,减少类的个数。

但这里减少的只是编码中类的个数,而在运行时如果采用继承,那对象的个数是EditText的个数;但如果采用组合,那每个EditText都对应着一个Listener,所以有更多的对象个数。所以说,组合一般较继承产生更多的运行时对象,这需要更多的内存(对象存储)和运算时间(对象间相互调用)。

现在假设app的界面上已经有一个EditText控件,我们又想在app中通过选项来控制该EditText的行为,例如我们有三个选项,分别对应touch,key和trackball事件发生时是否改变该EditText的背景颜色。那么如果采用组合,可以先创建好三个listener,然后根据选择添加listener到现有的的EditText中。但如果使用继承,根本是无法动态改变其行为了。所以说,组合可以动态改变对象的行为,而继承只能在静态改变。

再回到前面继承的onTouchEvent方法,在最后一行,我需要调用super.onTouchEvent。事实上,我开始实现时忘记调用这行了,结果点击edit框时,可以改变颜色,但无法切出输入法。在现实中覆盖父类的方法时,有的父类方法是需要在子类中被调用的,但有的却不需要。覆盖的时候要详细查看父类文档和代码。但如果采用组合的方式,我只需要关系listener自己的视线即可,并不需要调用view中跟touch处理相关的代码。所以说,继承是一种白盒复用,在覆盖父类的方法时,需要关心父类方法的实现;但组合是黑盒测试,我只需要实现接口即可,而不需要关心该接口如何被调用。

故而Design pattern中所有模式的两大基石之一就是:Favor object composition over class inheritance。

[转载]iOS- 显示数据列表最常用的一个控件UITableView - 清澈Saup - 博客园

mikel阅读(1097)

[转载]iOS- 显示数据列表最常用的一个控件UITableView – 清澈Saup – 博客园.

相信做过iOS的程序员,最熟悉的控件一定少不了UITableView,最常用的控件也一定少不了UITableView!

今天分享一下自己对UITableView的实现大体思路,和整理出来的学习笔记!

1.UITableView里的结构图                       

 

2.UITableView数据展示的条件                      

1> UITableView的所有数据都是由数据源(dataSource)提供的,所以要想在UITableView展示数据,必须设置UITableViewdataSource数据源对象

2> 要想当UITableViewdataSource对象,必须遵守UITableViewDataSource协议,实现相应的数据源方法

3> UITableView想要展示数据的时候,就会给数据源发送消息(调用数据源方法),UITableView会根据方法返回值决定展示怎样的数据

 

3.数据展示的过程                             

数据显示,三步走

1> 先调用数据源的

– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

得知一共有多少组

 

2> 然后调用数据源的

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

得知第section组一共有多少行

 

3> 然后调用数据源的

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

得知indexPath.section组 第indexPath.row 行显示怎样的cell(显示什么内容)

 

4.开发中经常用到的UITableView数据源方法               

1> 一共有多少组

– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

 

@required  2.3 是必须实现

2> 第section组一共有多少行

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

 

3> 第indexPath.section组 第indexPath.row行显示怎样的cell(显示什么内容)

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

 

4> 第section组显示怎样的头部标题

– (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

 

5> 第section组显示怎样的尾部标题

– (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

 

5.开发中经常用到的UITableView代理方法               

1.- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

选中了UITableView的某一行

2.- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

某一行的高度

3.- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

第section分区头部的高度

4.- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section

第section分区尾部的高度

5.- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

第section分区头部显示的视图

6.- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section

第section分区尾部显示的视图

7.- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath

设置每一行的等级缩进(数字越小,等级越高)

 

6.tableView刷新数据的方式                      

 1> 修改模型数据

2> 刷新表格

– reloadData

整体刷新(每一行都会刷新)

– (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

局部刷新

[转载]iOS- iOS 和 Android 的后台推送原理各是什么?有什么区别? - 清澈Saup - 博客园

mikel阅读(1190)

[转载]iOS- iOS 和 Android 的后台推送原理各是什么?有什么区别? – 清澈Saup – 博客园.

iOS 的推送
iOS 在系统级别有一个推送服务程序使用 5223 端口。使用这个端口的协议源于 Jabber 后来发展为 XMPP ,被用于 Gtalk 等 IM 软件中。

所以, iOS 的推送,可以不严谨的理解为:
苹果服务器朝手机后台挂的一个 IM 服务程序发送的消息。
然后,系统根据该 IM 消息识别告诉哪个 Apps 具体发生了什么事。
然后,系统分别通知这些 Apps 。

这个消息的内容是这样的:
应该说,苹果这种方式在技术上没有什么创新。但是,整个架构是很了不起的。 因为:

使用久经考验的协议,技术风险小。


苹果勇于承担责任:
他需要维护一个代价不小的服务器集群,而且要为服务器的 down 机负责。

选择低风险的技术方案 Bug 更少,减轻了用户的痛苦,这是构架师的功劳。
苹果承担责任,尽可能的减少了不可控的意外,保证了用户体验。
这,只能说是公司决策者的功劳。
(从侧面说明有个懂技术的 VP 是多重要。。。而 Scott 走人了。。)

他们带给用户的好处也是实实在在的。
1 安全。
只有登录过的开发者可以通过苹果的服务器推送。

2 快速,稳定,可靠。
苹果掌控推送服务器和 OS 。

3 更省电。

4 让整个系统的体验更统一和简单。
不会出现杀后台这种脑残事。(不用大量 Apps / Apps 的服务为了推送挂后台)。
也不会出现 Apps 被杀就收不到推送这种脑残事(早一点的新浪微博 Android 版仍然如此)。

5 开发容易。
当然,开发者还是要做些事情,比如维护个服务器什么的: ifanr.com/3979。但是复杂度无疑降低很多了。

Android 的推送
Apps 挂后台一直是 Android 引以为豪的特性(虽然我真的不知道是好处多还是坏处多。。)。。。大家挂后台等待推送就成为技术选择。

当然, Google 事后也提供类似苹果的推送方式了。倒也谈不上抄袭,毕竟苹果的整个技术实现也没有什么特别创新之处。

用户的电池? 

Apps 的开发者不会站在系统层面考虑的。他会假设其他 Apps 没有那么“不自觉”。而 Google 不强制的结果就是:没人真正为用户的电池负责。

但是, Google 的方案也并非全是悲剧:
也因为整个技术方案非强制, Android 的 Apps 在接收到推送后的表现更为灵活。
像 Line 的 Android 版本可以在推送通知的 Popup 上直接回复, iOS 就需要越狱才能做到了。

最后的话
强制和封闭,有时候并非坏事。他意味着做出这个决定的人,要为此负责。

所以,如果说苹果的推送方案有何创新?

我以为是超越技术,不惜让公司承担更多风险和责任的解决方案。(类似的还有 BB 的专用网络, Kindle 的全球 3G )

个人相信,担负起这些“额外”的责任,是值得的。。。

只要是为了用户。

[转载]如何打一手好Log - luguo3000 - 博客园

mikel阅读(1416)

[转载]如何打一手好Log – luguo3000 – 博客园.

  如果项目上过线的话,那你一定知道Log是多么重要。

为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题。这时打一手好Log的重要性绝不亚于写一手好代码。项目出问题 时,你要能拿出Log证明自己负责的部分没有问题,如果是自己的问题,要从Log里快速找出错误原因。如果没有从Log里找出错误原因,那一定是一件很悲 催的事情,特别是在bug不容易重现的情况下。那简直就是叫天天不灵,叫地地不应啊。

 

一.Log级别

Log最常用的级别就是Debug,INFO,WARN,ERROR,其他的很少用。如何运用合适的Log级别也是非常重要的,在不该用ERROR的地方用了ERROR,可能会给你带来额外的麻烦。下边仅根据自己的使用习惯,分别说一下我对各种级别的理解。

1.ERROR:

ERROR是错误的意思,但不代表出现异常的地方就该打ERROR。我认为ERROR是相对程序正确运行来说的,如果出现了ERROR那就代表 出问题了,开发人员必须要查一下原因,或许是程序问题,或许是环境问题,或许是理论上不该出错的地方出错了。总之,如果你觉得某个地方出问题时需要解决, 就打ERROR,如果不需要解决就不要打ERROR。

举例来说,如果有一个接口。调用者传过来的参数不在你的接受范围内,在这种情况下你不能打ERROR,因为传什么值是用户决定的,并不影响程序 正确运行。想象一下,如果你的服务器上有监控程序的话,检测到ERROR或WARN就报警,参数错误你也打ERROR,那运维人员会疯掉的。

如果做一个对讲机,在解析语音数据包时出错了,那就要打ERROR了,因为这个是理论上不该出错的地方,要不就是你的解析代码有问题,要不就是开发人员在拼凑语音包时存在问题,这个时候需要你来找出问题的原因。所以应该打ERROR。

2.WARN:

WARN是指出现了不影响程序正确运行的问题,WARN也是问题但不影响程序正常运行,如果WARN出现的过于频繁或次数太多,那就代表你要检查一下程序或环境或依赖程序是否真的出问题了。

假如你访问一个接口,设置了一个超时,超时之后会抛异常,你在try块里不该打ERROR也不该打INFO来无视它,这时你应该打WARN,紧紧是警告一下,如果超时过多那就该检查一下了,是不是对方接口有问题了或者是网络环境出问题了。

3.INFO和Debug

ERROR和WARN是指有问题,而INFO和Debug就是指一般的信息了。在程序出问题时,如果这条log可以帮助你分析问题或查看程序的 运行情况,那就应该打个INFO。如果仅仅是为了在调试阶段查看程序是否运行正确那就要打DEBUG。前边讨论的接口参数错误问题,就应该打个INFO 了,调用者说你的接口总是返回错误代码,你可以告诉他,是他的哪个参数传错了。

 

二.如何打

1.log必备信息

在每一条log中都要将时间、类名及函数名,可以的话将行号也打印出来(不建议手写行号),像java的log4j就是不错的。

2.函数开始结束处

在重要函数的开始结束出应该打上log ,这样在看log时会比较直观,什么时候开始什么时候结束就会一目了然,万一中间出异常导致程序退出了,也知道是在哪个函数突然中断的。也同样适用于一个重要逻辑块的开始结束。

3.返回结果

尽量在重要函数或web接口的每个返回分支打印返回结果。在出现不好分析的异常时,从细节下手,这时log会派上用场。如果跟合作方在数据方面出现争议也可以及时拿出证据。

4.添加Exception异常的捕获

如果你在代码中捕获了某种异常,那你要在try块后添加Exception的捕获,以防出现运行时异常中断程序。

5.务必打印堆栈信息

在异常捕获代码中务必要将堆栈信息打印出来,否则打了那么多的log可能会功亏一篑。

6.多线程的log

在多线程的程序中,log最好要标记thredId,否则可能不知道是哪个线程的作业,也无法有条理的来观察一个线程。

7.成功失败标志

如果某个函数是做一件比较关键的事情,那么这件事情成功还是失败了,要打印log,否则关键事件运行结果如何都拿不出证据的话,实在是不能让人信服。

8.前后log的关系

如果是web程序或接口,那log就不是按照你预定的顺序出现的,可能是好几个响应的log穿插在一起的。代码里如果有几条log前后存在一定 的数据关系,那么要将这几条log的关联信息打出来,用来确定是针对同一个响应的。如果没有明确的标志,很难说后边的log跟前边的log是同一个响应或 者是针对同一条数据。

9.关于耗时

访问一个第三方接口、上传下载文件等可能耗时的操作,都要记录完成这个操作所耗的时间。否则程序性能出了问题,你不知道是网络原因呢,还是你调用的第三方接口性能出现问题呢,还是你自己程序的问题呢。

10.关于数量

涉及到数量的操作要打印log,比如查询数据库和批量拷贝文件、上传下载、批量格式转换等批量操作,设计到的数量要打印出来。

 

总之,打log的目的是为了迅速排错或在有争议时拿出证据证明自己。基于这个目的,log不在多,只要抓住一切对自己有利的信息,就可以了。

想起其他的再继续补充吧,欢迎大家拍砖补充。

本人学识尚浅,写文目的是为了得到大家指点。 倘若文章帮到了您,那真是好极了。

[转载]基于Lumisoft.NET组件的SMTP账号登陆检测 - 伍华聪 - 博客园

mikel阅读(1063)

[转载]基于Lumisoft.NET组件的SMTP账号登陆检测 – 伍华聪 – 博客园.

在邮件处理的方面,Lumisoft.NET可以说是非常不错的一个选择,我在前面几篇文章中都介绍过这个组件。

基于Lumisoft.NET组件开发碰到乱码等一些问题的解决

基于Lumisoft.NET组件的POP3邮件接收和删除操作

基于Lumisoft.NET组件开发碰到乱码等一些问题的解决

我的博客内容几乎全部来源我的项目开发或者研究工作,因此对于这个组件的使用还是有一定的说服力,这个组件封装了很多底层的一些操作,对于我们操作 邮件的发送、接收的处理,更加方便高效。本文主要介绍该组件一个小的应用,基于Lumisoft.NET组件的SMTP账号登陆检测。

做某种工作,一般都有一个背景知识,从背景里面,我们可以大致了解这个工作的应用场景和具体目的,这个基于SMTP账号登陆检测的功能来源我对一批 邮件账号进行交替邮件发送的时候需要,我手上有一批免费邮箱的SMTP账号,可以用于邮件的发送,但是我不确信是否所有的账号密码都是可以登录使用的,因 此我需要对它们进行检测,只有正确的账号密码,我才用来批量发送邮件。这种检测我希望是通过自动化进行实现,不要人工一个个登录,在偶尔的一次 Lumisoft.NET组件的使用过程中,我发现它发送邮件前,都会进行账号密码的登录操作,因此如果有成功的回应,那么就算达到目的了。

Lumisoft组件里面,SMTP_Client的对象有一个Auth方法(或者Authenticate方法),并且调用后,对象本身有一个IsAuthenticated的bool属性,这样就可以达到我们的操作目的了。

具体对SMTP_Client的SMTP账号登陆检测代码如下所示。

private bool CheckLogin(string smtp, string username, string password)
{
bool result = false;
using (SMTP_Client client = new SMTP_Client())
{
try
{
if (this.txtUseSSL.Checked)
{
client.Connect(smtp, WellKnownPorts.SMTP_SSL, true);
}
else
{
client.Connect(smtp, WellKnownPorts.SMTP, false);
}

client.EhloHelo(smtp);
var authhh = new AUTH_SASL_Client_Plain(username, password);
client.Auth(authhh);
//client.Authenticate(username, password);

result = client.IsAuthenticated;
}
catch (Exception ex)
{
LogHelper.Error(ex);
}
}
return result;
}

为了更有效实现账号的检测,我们使用了背景线程进行处理,以达到更好的界面体验。

private BackgroundWorker worker;

public FrmEditMailConfig()
{
InitializeComponent();

worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}

然后在实现账号检测的事件里面对背景线程进行调用,具体操作代码如下所示。

void worker_DoWork(object sender, DoWorkEventArgs e)
{
string smtp = this.txtSmtpServer.Text.Trim();
string username = this.txtLoginId.Text.Trim();
string password = this.txtPassword.Text.Trim();

bool success = CheckLogin(smtp, username, password);
e.Result = success;
}

这个线程处理后,会返回一个结果,然后我们在线程完成的操作里面对他们进行处理,即可实现把结果展示到界面上了。

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
string username = this.txtLoginId.Text.Trim();
bool success = Convert.ToBoolean(e.Result);
if (success)
{
string message = string.Format("[{0}] 测试成功", username);
MessageDxUtil.ShowTips(message);
}
else
{
string message = string.Format("[{0}] 测试失败,可能密码不正确或未开通SMTP", username);
MessageDxUtil.ShowWarning(message);
}
}

当然,这个只是对一个账号密码进行SMTP登录检测,如果需要批量检测,把思路变化一下,遍历需要检测的账号密码,对账号处理完成后,把结果显示到界面上即可。