标签存档: java

项目小结:会员俱乐部

vip.mbaobao.com 上线

一些好玩的地方:

  1.  非埋点。不埋点理由是因为vip将来会有很多小的任务,遍布麦包包网站各个角落,如果每个角落里都写段代码不说管理,自己看着也变扭。
  2. 向运营的@青木同志学到不少会员等级的东西。

一些不足之处:

  1. 老问题,项目有延期。原计划6月18日上线。一直拖到7月3日。虽然其中更重要的事情填入,但超时已经是事实。
  2. 较大失误一枚。用户等级数据重新刷了2遍。虽然没有损失,但毕竟客户资料比较重要。事先可以想的更齐全一点。
  3. 用户签到增加M值,存在bug。已修正
有成长,有未来

Java RSA加密

	private String encrypt(String encodeString) {
		//空字符串不加密
		if (StringUtil.isEmpty(encodeString)) {
			return "";
		}
		try {
			byte[] modulusBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.modulus"));
			byte[] exponentBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.exponent"));
			BigInteger modulus = new BigInteger(1, modulusBytes);
			BigInteger exponent = new BigInteger(1, exponentBytes);

			RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
			KeyFactory fact = KeyFactory.getInstance("RSA");
			PublicKey pubKey = fact.generatePublic(rsaPubKey);

			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, pubKey);

			byte[] bytes = encodeString.getBytes();
			byte[] encodedByteArray = new byte[] {};
			for (int i = 0; i < bytes.length; i += 102) {
				byte[] subarray = ArrayUtils.subarray(bytes, i, i + 102);
				byte[] doFinal = cipher.doFinal(subarray);
				encodedByteArray = ArrayUtils.addAll(encodedByteArray, doFinal);
			}

			return new BASE64Encoder().encode(encodedByteArray);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

 

项目小结:权限管理平台

今天终于把AC权限管理系统“上线”了

内部项目,取名AC,即access control 一个权限授权及管理服务。

一些好玩的地方:
  1. RBAC模型的实践
  2. 跨域单点登录的实践
  3. 持久化session
一些不足之处:
  1. 使用了不熟练的jeasyui, 累计有1天的工作量在查阅相关文档和处理其问题. 虽然是边开发边学习,但这种方式一定程度上影响整体开发效率
  2. 过多的考虑为了兼容原有平台的权限模型,而没有考虑将来新平台的模型。这点被总监指出。有时候做的太范的确不太好。
  3. 大量的低脑力高体力的代码。估计占项目20%的代码是在写基础的mybatis配置及其接口。如果表设计合理加上一定规则。完全可以反向把表结构转化为基础的代码。更多的注意力集中在业务和高层抽象。抽时间考虑根据公司情况,开发code-to-code工具。
  4. 后期因为修改,造成设计上的出现冗余。处理多对多关联问题上,没能找到太好的解决方案。造成至少3段代码的冗余。12-15处DAO冗余。严重影响以后代码阅读者。
  5. 原权限迁移到新平台时,没有深思熟虑。导致本次权限迁移。部分员工的权限丢失。需要人工补回。
不满意的地方颇多。究其原因:
  1. 管不住自己的好奇心,为了代码而去设计。
  2. 时间不充裕。因为不是核心项目。全部编码和测试只有2周时间。存在较多bug未被发掘。

Java WebSocket 开发 Webbit

webbit是基于netty扩展的websocket工具。可以大大简化websocket开发。

项目地址:https://github.com/webbit/webbit

使用说明:https://github.com/webbit/webbit/blob/master/README.md

本文权当翻译,高手直接进上面链接

一些题外话:

  1. websocket和我一开始想象的TCP应用不同。websocket和传统意义上的socket通信不一样。本质上还是HTTP的扩展。
  2. websocket协议目前还没有定稿。目前主要有3个版本的协议在使用。且都是草案。webbit都实现了3个草案。具体参阅维基http://en.wikipedia.org/wiki/WebSocket

快速开始

Maven配置

<dependency>
	<groupId>org.webbitserver</groupId>
	<artifactId>webbit</artifactId>
	<version>0.4.7</version>
</dependency>

 配置端口8080.并配置websocket路径/socket的handler

public class WebSocketServer{

	public static void main(String[] args) {
		WebServer webServer = WebServers.createWebServer(8080)
	    .add(new StaticFileHandler("/socket"));
		webServer.start();
	}

}

 编写/socket handler

public class WebSocketHandler  extends BaseWebSocketHandler{

    private int connectionCount;

    public void onOpen(WebSocketConnection connection) {
        connection.send("Hello! There are " + connectionCount + " other connections active");
        connectionCount++;
    }

    public void onClose(WebSocketConnection connection) {
        connectionCount--;
    }

    public void onMessage(WebSocketConnection connection, String message) {
        connection.send(message.toUpperCase()); // echo back message in upper case
    }

}

到此为止。基本代码已经都好了。感觉更写个servlet一样方便。

下面是客户端代码:

<html>
  <body>

    <!-- Send text to websocket -->
    <input id="userInput" type="text">
    <button onclick="ws.send(document.getElementById('userInput').value)">Send</button>

    <!-- Results -->
    <div id="message"></div>

    <script>
      function showMessage(text) {
        document.getElementById('message').innerHTML = text;
      }

      var ws = new WebSocket('ws://' + document.location.host + '/hellowebsocket');
      showMessage('Connecting...');
      ws.onopen = function() { showMessage('Connected!'); };
      ws.onclose = function() { showMessage('Lost connection'); };
      ws.onmessage = function(msg) { showMessage(msg.data); };
    </script>
  </body>
</html>

到此为止。websocket基本功能都已经实现。

 

 

netty+flash xmlsocket 在线聊天室

这几天公司做了个简单的web im聊天室。麦包包晒包频道 右下角的“包打听”。

采用netty 做socket server + flash client. 通讯采用自定义的JSON文本。

在线IM初步的探索,现阶段做的比较简单。

目前就部署1台服务器。没有考虑多机通信。没有涉及通信队列。

总共代码不到1000行。netty的确强大。

关键代码分享:

无话可说的代码,netty通用设置。

public class ChatRoomNioServer {

	private static final Logger		logger		= Logger.getLogger(ChatRoomNioServer.class);

	private static final Integer	SERVER_PORT	= Integer.parseInt(PropertiesHelp
													.getProperty("chat.server.port"));
	public void start() {
		logger.info("chatroom init...");
		ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
			Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
		bootstrap.setPipelineFactory(new ChatRoomServerPipelineFactory());

		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.setOption("reuseAddress", true);
		bootstrap.bind(new InetSocketAddress(SERVER_PORT));
		logger.info("chatroom running...");
	}

}

由于采用JSON协议。

上行通道:

  1. DelimiterBasedFrameDecoder 读取缓存时,已\0\r\n 为中止符
  2. StringDecoder 二进制转字符串进行UTF-8解码
  3. StringEncoder 字符串UTF-8编码

下行通道:

  1. MessageHandler 业务逻辑处理
  2. MessageEncoder JSON转字节流打入下行通道
public class ChatRoomServerPipelineFactory implements ChannelPipelineFactory {

	@Override
	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline pipeline = Channels.pipeline();
		pipeline
			.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.zeroDelimiter()));
		pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
		pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
		pipeline.addLast("messageHandler", new MessageHandler());
		pipeline.addLast("encoder", new MessageEncoder());
		return pipeline;
	}

}

netty没有提供\0截取。不过重写部分代码即可。

public class Delimiters {

	public static ChannelBuffer[] zeroDelimiter() {
		return new ChannelBuffer[] { ChannelBuffers.wrappedBuffer(new byte[] { '\0' }),
				ChannelBuffers.wrappedBuffer(new byte[] { '\r', '\n' }) };
	}

	private Delimiters() {

	}
}

逻辑处理:

  1. 处理flash 的policy file request
  2. 处理业务逻辑
public class MessageHandler extends SimpleChannelUpstreamHandler {

	private static final Logger	logger			= Logger.getLogger(MessageHandler.class);

	private static final String	POLICY_REQUEST	= "<policy-file-request/>";

	private static final String	POLICY_XML		= "<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"master-only\"/><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>";

	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.initConnection(e.getChannel());
		logger.info("one user connection server, left " + ChatUserService.getOnlineUserCount()
					+ " users");
	}

	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.closedConnection(e.getChannel());
		logger
			.info("one user left server, left " + ChatUserService.getOnlineUserCount() + " users");
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		String msg = (String) e.getMessage();
		if (msg.equalsIgnoreCase(POLICY_REQUEST)) {
			e.getChannel().write(POLICY_XML + "\0");
			e.getChannel().close();
		} else {
			try {
				MessageDispatchHandler messageDispatchHandler = new MessageDispatchHandler(e);
				messageDispatchHandler.setListener(new ChatRoomListenerImpl());
				messageDispatchHandler.dispatch();
			} catch (Exception ex) {
				logger.error("message handler error", ex);
			}
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		logger.warn("Unexpected exception from downstream.", e.getCause());
		e.getChannel().close();
	}
}

具体业务在    MessageDispatchHandler 处理一些业务逻辑即可。

public class MessageDispatchHandler {

	private static final Logger	logger	= Logger.getLogger(MessageDispatchHandler.class);

	private ChatData			chatData;

	private ChatConnection		connection;

	//事件监听器
	private ChatRoomListener	listener;

	public MessageDispatchHandler(ChatConnection connection, Object message) {
		this.chatData = ChatPackageUtil.unpackage(message);
		this.connection = connection;
	}

	public void dispatch() {
		logger.debug("current chatdata :" + chatData);
		if (listener == null) {
			throw new RuntimeException("监听器不能为空!");
		}
		try {
			switch (chatData.getMethod()) {
				case C_CONN:
					listener.connChatRoom(connection, chatData);
					break;
				case C_JOIN:
					listener.loginChatRoom(connection, chatData);
					break;
				case C_PUBLISH_MSG:
					listener.broadcastMsg(connection, chatData);
					break;
				case C_SEND_ADMIN:
					listener.sendAdminMsg(connection, chatData);
					break;
				case C_SEND_NORMAL:
					listener.sendNormalMsg(connection, chatData);
					break;
				case C_GET_USERS:
					listener.getOnlineUserList(connection, chatData);
					break;
				default:
					throw new BusinessException("错误指令:" + chatData.getMethod());
			}
		} catch (BusinessException e) {
			logger.info("error msg:" + e.getMessage());
			ChatService.pushErrorMsg(e.getCode(), e.getMessage(), connection);
		}
	}

	public void setListener(ChatRoomListener listener) {
		this.listener = listener;
	}

}

 

总结下遇到的问题:

  1. flash socket通讯格式。每个协议请求都会\0作为消息结尾。因此netty接收消息也必须以\0为截止符读取消息。
  2. flash 安全策略。flash夸域访问服务器时,会自动发<policy-file-request/> ,服务端收到消息后,必须响应对应策略文件。
    <cross-domain-policy>
      <allow-access-from domain="*" to-ports="80-9000" />
    </cross-domain-policy>
  3. SQL注入。Socket和Http相比。只是通讯的层次发生了改变。应用本身的漏洞仍旧没变。处理SQL的时候需要特别注意。不过用一些成熟的ORM框架可以很好的避免这类问题。
    注:这里使用的是比较低级的JSON字符串作为消息载体,较为普遍和靠谱的做法是采用head+body的方式,但前提需要对格式有严格的约定。JSON灵活性较大,因此采用。

zookeeper资料汇集

这几天看了下zookeeper的东西。收获多多。感谢国人的分享精神。使得这一产品能被更多人了解。

zookeeper基础配置与入门:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/

agapple学习笔记系列:http://agapple.iteye.com/blog/1292473

taobao通用产品团队:http://rdc.taobao.com/team/jm/archives/448

Paxos算法:http://zh.wikipedia.org/zh/Paxos%E7%AE%97%E6%B3%95

工具:https://github.com/killme2008/node-zk-browser 淘宝团队开发的一个zookeeper节点管理工具。nodejs开发。功能简单。基本实现了zookeeper命令行的功能。

文档是最佳的知识沉淀呀~

apache zookeeper

zookeeper

 

 

jmock2学习笔记-续

总结下这几天初步学习mock的心得. 文中大部分内容来自官网文档.

创建Mock对象

Mockery context = new Mockery();
Turtle turtle = context.mock(Turtle.class);
Turtle turtle2 = context.mock(Turtle.class, "turtle2");

创建Mock Expections

... create mock objects ...
public void testSomeAction() {
    ... set up ...
    context.checking(new Expectations() {{
        ... <expections block> ...
    }});
   ... call code being tested ...
}

expections 这里指的是jmock在不同情况下的预期值。expections 是jmcok的最重要部分之一。可以模拟用户在不同情况下返回不同的结果。

<expections block>这里是可以设置多个expections 。

Expectations

expections的结构如下:

invocation-count (mock-object).method(argument-constraints);
    inSequence(sequence-name);
    when(state-machine.is(state-name));
    will(action);
    then(state-machine.is(new-state-name));

上面是一个通用的结构。所有expecions都采用相同或更简洁的结构。

invocation-count mock调用频率。oneOf表示调用一次后这个expections即失效。
mock-object mock对象。即上面Mockery创建的对象。
method 用mock代替实现接口的一个方法。所以mock必须使用接口编程。
argument-constraints mock参数条件。即mock指定方法的参数。
when … will…then … 类似于if .. else… 这样的语句。简单的条件表达式
action mock符合条件后,执行操作。一般是返回结果。或者抛出异常
state-machine 条件表达式。

下面详细讲解下expectations各个部分。

Invocation Count

oneOf 只执行一次
exactly(n).of 执行指定n次
atLeast(n).of 至少执行n次
atMost(n).of 最多执行n次
between(min,max).of 可以执行min-max次
allowing 允许执行,不受限制
ignoring 功能同allowing类似。这里主要字面上区分allowing
never 不允许执行

Actions

will(returnValue(v)) 返回一个值。Object类型任意。集合类型不建议这里返回。虽然也可以
will(returnIterator(c)) 返回一个集合类型的值。
will(returnIterator(v1, v2, …, vn)) 返回一个集合类型的值。可以用多个,来分隔。
will(throwException(e)) 抛出一个异常。
will(doAll(a1, a2, …, an)) 嵌套执行多个actoin。不常用。

Sequences

设定mock方法调用的顺序。简单的看下面代码片段。
定义了先save后countUser。调用的时候也必须找这个顺序。否则用例失败。

        public void testCountUserSequences() {
                final Sequence testSequence = mockery.sequence("testSequence");

                final User _user = genUser();

                mockery.checking(new Expectations() {
                        {
                                oneOf(userDao).save(_user);
                                inSequence(testSequence);
                                will(returnValue(_user));

                                oneOf(userDao).countUser();
                                inSequence(testSequence);
                                will(returnValue(1));
                        }
                });

                assertEquals(userService.save(_user).getUsername(), "fengsage");
                assertEquals(userService.countUser(), 1);
        }

jmock2学习笔记

目前晚上学习jmock。

Issue: spring+annotations使用jmock

遇到了这个场景。stackoverflow上有人问过这个问题,这里贴一下。

http://stackoverflow.com/questions/1638911/mock-object-and-spring-annotations

关键是使用ReflectionTestUtils的方法。这个方法在spring-test工程的相关jar包。

    ...
    @Autowired
    private UserService	userService;
    private UserDao	userDao	= null;
    ...
    ReflectionTestUtils.setField(userService, "userDao", userDao, UserDao.class);
    ...

Issue:入参对象时。mock表达式传入对象必须和实际传入对象为同一句柄。

       
    final User _user = genUser();

    mockery.checking(new Expectations() {
        {
            one(userDao).queryUser(_user);
            List<User> userList = new ArrayList<User>();
            userList.add(genUser());
            userList.add(genUser());
            will(returnValue(userList));
        }
    });

    List<User> users = userService.queryUser(_user);

共用一个_user句柄。否则报错。

Issues:jmock一个异常错误

will(throwException(new RuntimeException("test exception")));

推荐链接:http://superleo.iteye.com/blog/143493

相关:jmock2学习笔记-续

android自定义日期组件(拨动效果)

android自带的日期组件是通过TimeDialog,让用户上下调整来输入.但这样势必让用户需要点击某个按钮,弹出对话框后在选择.感觉不是非常的友好.

自己写了一个日期组件,可以通过手势在时间上拨动,达到调整数字的目的.

代码如下:

package com.zjhcsoft.mobi.android.widget;

import java.util.Calendar;

import java.util.GregorianCalendar;

import com.zjhcsoft.mobi.android.ui.R;

import android.app.DatePickerDialog;

import android.app.Dialog;

import android.content.Context;

import android.content.res.TypedArray;

import android.util.AttributeSet;

import android.util.Log;

import android.view.GestureDetector;

import android.view.MotionEvent;

import android.view.View;

import android.widget.DatePicker;

import android.widget.TextView;

/**

* 日期滚动组件

* @author Fred

*

*/

public class RotateTimeInput extends TextView {

    private static final String TAG = "RotateTimeInput";

    public static final String UP = "up";

    public static final String DOWN = "down";

    private int year;

    private int month;

    private int days;

    private Calendar currentDate;//当前时间

    private float fontSize = 13L;

    private GestureDetector ges;//手势接口实现

    private RotateTimeListener rotateTimeListener;

    private Dialog dataDialog;//日期选择器

    private boolean isMove = false;

    private boolean isFling = false;

    private boolean isScroll = false;

    //细粒度属性设置

    private float longClickFix = 0;//防止滑动和长按有冲突

    private float longClickNorm = 50;//区分滑动和长按标准差值,差值大于改值时,放弃长按事件

    private int scrollNorm;//设置scroll因子 ,scroll数大于默认值时,时间触发

    public RotateTimeInput(Context context, AttributeSet attrs) {

        super(context, attrs);

        initRotateWidget(context,attrs);

    }

    /**

    * 初始化控件时间

    * @param year 年

    * @param month 月-1

    * @param days 日

    */

    public void initByGivenDate(int year,int month,int days){

        this.year = year;

        this.month = month-1;

        this.days = days;

        currentDate = null;

        this.setText(formatTime());//设置默认值显示值

    }

    /**

    * 初始化RotateTime组件

    * @param context 应用上下文

    * @param attrs 配置属性

    */

    private void initRotateWidget(Context context,AttributeSet attrs){

        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.RotateTimeInput);

        scrollNorm = a.getInt(R.styleable.RotateTimeInput_scrollNorm,8);

        a.recycle();

        //设置字体大小

        setTextSize(fontSize);

        //初始化当前时间

        this.setText(formatTime());//设置默认值显示值

        //增加轨迹事件

        ges = new GestureDetector(context,new TouchGesture(context));

        //鼠标手势触发

        this.setOnTouchListener(new OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {

                if(event.getAction()==MotionEvent.ACTION_UP){

                    if(isMove&&isScroll&&!isFling){

                        longClickFix = 0;

                        rotateTimeListener();

                        isScroll = false;

                        isMove = false;

                    }

                }

                return ges.onTouchEvent(event);

            }

        });

    }

    /**

    * 时间格式化

    * @return

    */

    private String formatTime(){

        calculate();

        return new StringBuffer().append(year).append("年").append(month+1).append("月").append(days).append("日").toString();

    }

    /**

    * 日期递增

    */

    private void upDays(){

        currentDate.add(Calendar.DATE, 1);

        setText(formatTime());

    }

    /**

    * 日期减少

    */

    private void downDays(){

        currentDate.add(Calendar.DATE, -1);

        setText(formatTime());

    }

    /**

    * 调整时间

    * @param way 调整时间方向(RotateTimeInput.UP向前一天,RotateTimeInput.DOWN回退一天)

    */

    public void adjustDay(String way){

        if(way==UP){

            upDays();

            }else{

            downDays();

        }

        rotateTimeListener();

    }

    /**

    * 重新计算当前年月日天数

    */

    private void calculate(){

        if(currentDate==null){

            if(year!=0&&month!=0&&days!=0){

                this.currentDate = new GregorianCalendar(year, month, days);

                }else{

                this.currentDate = GregorianCalendar.getInstance();

            }

        }

        if (currentDate!=null){

            this.year = currentDate.get(Calendar.YEAR);

            this.month = currentDate.get(Calendar.MONTH);//Java中,月份从0开始

            this.days = currentDate.get(Calendar.DATE);

        }

    }

    /**

    * 创建Dialog对象,此处只有一个DataPick对象

    * @param context 应用上下文

    * @return

    */

    private Dialog initDialog(Context context){

        dataDialog = new DatePickerDialog(context,new DatePickerDialog.OnDateSetListener() {

            public void onDateSet(DatePicker view, int years, int monthOfYear,

            int dayOfMonth) {

                //DataPick用户锁定后

                currentDate.set(years, monthOfYear, dayOfMonth);

                setText(formatTime());

                rotateTimeListener();

            }

        },currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE));

        dataDialog.show();

        longClickFix = 0;//释放差值

        return dataDialog;

    }

    /**

    * Touch接口实现,监听用户鼠标轨迹

    * @author Fred

    *

    */

    class TouchGesture extends GestureDetector.SimpleOnGestureListener{

        private double scrollDistence=0;

        Context dialogContext;

        public TouchGesture(Context dialogContext){

            this.dialogContext = dialogContext;

        }

        @Override

        public void onLongPress(MotionEvent e) {

            if(longClickFix scrollNorm) {

                scrollDistence=0;

                if (distanceX<0){

                    upDays();

                    }else{

                    downDays();

                }

            }

            longClickFix = Math.abs(e2.getRawX() - e1.getRawX());

            return false;

        }

    }

    /**

    * 拨动事件

    * @param listener

    */

    public void setOnRotateTimeListener(RotateTimeListener listener){

        this.rotateTimeListener = listener;

    }

    private void rotateTimeListener(){

        calculate();

        if (rotateTimeListener!=null){

            rotateTimeListener.onRotateTime(this.year, this.month+1, this.days);

        }

    }

    class RotateFlyingThread extends Thread {

        private int ri;

        private int rp;

        private float af;

        private boolean stop = false;

        private int ic;

        public RotateFlyingThread(int rotateInterval, int rotatePeriod,

        float attenuateFactor, int increment) {

            ri = rotateInterval;

            rp = rotatePeriod;

            af = attenuateFactor;

            ic = increment;

        }

        public void stopRotate() {

            stop = true;

        }

        public void run() {

            int p = 0;

            Log.i("Rotating", String.format( "rotateInterval: %s, rotatePeriod: %s, attenuateFactor: %s, increment: %s,", ri, rp, af, ic));

            while ((p < rp) && !stop) {

                Log.i("Rotating", " ..." + RotateTimeInput.this.getText());

                RotateTimeInput.this.post(new Runnable() {

                    public void run() {

                        if (ic>0){

                            upDays();

                            }else{

                            downDays();

                        }

                    }

                });

                try {

                    Thread.sleep(ri);

                    } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                ri += (int) (ri * (af / 10));

                p += ri;

            }

            rotateTimeListener();

            isFling = false;

            isMove = false;

            Log.i("Rotating", "stopped");

        }

    }

}

事件接口:

package com.fengsage.mobi.android.widget;

/**

* 拨动时间空间

* @author Fred

*

*/

public interface RotateTimeListener {

/**

* 当时间停下来的时候,开始触发事件

* @param year

* @param month 1--12

* @param days

*/

public void onRotateTime(int year,int month,int days);

}

最终效果如下:

在数字上面,用手左右来回拖动,时间也会跟着变动. 如果长按,则会弹出android标准的日期控件.

在android如何使用AsyncTask异步任务

最近项目中用到异步任务功能,网上搜罗了下资料,奈何国内的质量实在太低,依靠伟大的谷歌搜到很多国外的文章,写的非常的好,更好的在于没有copy paste的成分.

本文内容转自:http://www.brighthub.com/mobile/google-android/articles/82805.aspx

英语好的朋友请直接阅读原文.小弟不才,做个苦力当翻译

介绍

我们的Android应用越来越复杂,连接服务端,和web交互数据,存储大文件在android数据库中的同时显示进度条或者在通知栏显示通知. 我们如何能在抛开UI线程的情况下,边接受处理数据边用进度条展示呢?开始之前,必须知道什么是"UI线程"?

如果你熟悉"Thread"的概念. 那么你应该很容易明白异步任务. Android应用在处理的时候只有一个main主线程在运行.这个看上去像在任何地方只有一个任务. 如果你只有一个UI线程在工作,你就不能做一些复杂的事情,比如同时存储10000条记录,这个时候应用会停住,直到1000条记录存储完毕. 这不是一个好的方式. 在android你可以在一个应用里,同一时间运行多个线程. 举例:一个后台任务可以从服务端接受数据并且存储到本地数据库中.

如何对"Thread"概念有足够的了解,那么我们继续

现在我们可以开始了吗? 开始在后台执行任务? 这里我们还有几步工作要做.

一个古老的路线方法是使用Thread线程类,使用HandlersRunnables. 但是我们有更好的方式。使用AsyncTask

AsyncTask 类

让我来看看AsyncTask类的结构


				private class myBrightHubTask extends AsyncTask

				protected void onPreExecute(){

				}

				这个方式是在执行新线程前运行. 这里没有输入和输出的参数,所以这个可以初始化一些你认为必要的参数.

				protected Z doInBackground(X...x){

				}

				这是个非常重要的方法. 你可以在这里定义你需要的在后台执行的方法.这里开始是一个独立的线程在运行,而不是main线程. 这个方法接收一组对象集合,对象可以是在前继承AsyncTask的时候定义的X类型(你看到这个类的一开始我们继承的AsyncTask类了吗?这里的X就是这里的类型),默认是Object集合.

				protected void onProgressUpdate(Y y){

				}

				这个方法是在使用publishProgress()方法时触发,这个方法常用于显示一些在主线程中执行的进度条或者一些信息展示.比如当在执行一个后台任务的时候,前台做一个滚动条在滚动 ,

				protected void onPostExecute(Z z){

				}

这个方法在后台任务完成后执行,传入一个参数Z,也是前面定义的那个,你可以使用这个参数做一些结束任务时的处理,比如跳转intent或者其他的事情.

X,Y,Z的是什么类型?

我想你也可以从上面的结构中得出结论:

X – 运行后台任务的时候,需要传入的参数类型. 可以是一个对象的集合.

Y – 当你需要使用onProgressUpdate方法时,传入的参数类型

Z – 当后台任务完成时,需要传入数据处理的数据类型

如何使用这个异步任务呢?执行要执行下面2行代码.


				myBrightHubTask brightHubTask = new myBrightHubTask();

				brightHubTask.execute(x);

这里的输入参数是前面讲的X类型

一旦我们运行异步任务.你可以使用getStatus()方法获取它的状态


				brightHubTask.getStatus();

我们可以接受下面几个状态:

RUNNING – 任务处理中

PENDING – 任务还没有完成.

FINISHED – 任务结束.

提醒

需要注意的是:

  • 不用主动去调用onPreExecute, doInBackground and onPostExecute这个三个方法.这些方法会自动执行.

  • 你不能在另外一个AsyncTask或者线程中使用异步任务. 调用异步任务必须在UI主线程中使用.

  • onPostExecute 这个方法时在UI主线程中执行.这里你可以调用另外一个异步任务.

  • 输入参数可以设置一个Object集合,这样你就可以传入任意类型的对象

更多内容: http://www.brighthub.com/mobile/google-android/articles/82805.aspx#ixzz1GkXV8IT4