koa2.0.0 webpack 结合 动态调试代码

koa2.0.0 的路由和视图渲染完之后的一个问题就是,如何高效的开发前端代码。

因为我之前的前端代码是用webpack进行打包,然后打包过程中,使用babel实现了,ES6语法的转换,这行我就不能太干脆的丢弃webpack,然后里面使用了react+react-router+reflux,整个开发体系还是比较可以的。为了整合之前的代码,需要将之前的connect框架改为koa2.0.0,毕竟koa的实现方式还有与express,connect等这样的框架,实现方式不太一样,所以,稍微还有有点小困难,不过我这里记录下,也就不困难了。

从webpack-dev-server说起,我们使用webpack-dev-server是为了能够时时监控我们的代码改变,然后用它自己的socket-io去刷新我们的页面,实现了代码修改即页面重载,当然只是在开发环境。

webpack-dev-server并没有多大的改变,只是将原来的一个proxy的操作去掉了,然后整合到koa的启动文件里面了。

const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const config = require('./webpack.config.development');
const debug = require('debug');
new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
  hot: true,
  noInfo: false,
  historyApiFallback: true,
  stats: { colors: true },
  headers: { 'Access-Control-Allow-Origin': '*' }
}).listen(8080, '127.0.0.1', function(err,result) {
  if (err) {
    console.log(err);
  }
  console.log('Webpack Listening at 127.0.0.1:8080');
});

关于webpack的config文件的代码是不需要改变的,继续使用。

下面就是在koa的启动文件里面加入启动需要的proxy处理。

//解析 开发环境 development
app.use(convert(proxy({
  host:'http://127.0.0.1:8080/js',
  match: /^\/js\//
})));

就这么简单,这里使用了两个中间件:

koa-proxy,koa-convert。

解释一下好了,就是将访问的path中有带/js/部分的路径,进行重新proxy一下就好,这样就将文件转向到了webpack-dev-server这边的对应的文件了。相关的其他的问题可以参考我之前的几篇文章

ubuntu上安装淘宝的开源Web服务器Tengine

Tengine官网:http://tengine.taobao.org/
一、什么是Tengine
Tengine 是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大 型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。
从2011年12月开始,Tengine成为一个开源项目。现在,它由Tengine团队开发和维护。Tengine团队的核心成员来自于淘宝、搜狗等互联网企业。
二、特性
继承Nginx-1.2.5的所有特性,100%兼容Nginx的配置;
动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine;
输入过滤器机制支持。通过使用这种机制Web应用防火墙的编写更为方便;
动态脚本语言Lua支持。扩展功能非常高效简单;
支持管道(pipe)和syslog(本地和远端)形式的日志以及日志抽样;
组合多个CSS、JavaScript文件的访问请求变成一个请求;
可以对后端的服务器进行主动健康检查,根据服务器状态自动上线下线;
自动根据CPU数目设置进程个数和绑定CPU亲缘性;
监控系统的负载和资源占用从而对系统进行保护;
显示对运维人员更友好的出错信息,便于定位出错机器;
更强大的防攻击(访问速度限制)模块;
更方便的命令行参数,如列出编译的模块列表、支持的指令等;
可以根据访问文件类型设置过期时间;

 

三、ubuntu下安装Tengine
1、下载:
http://tengine.taobao.org/download/tengine-1.4.2.tar.gz
2、解压:

代码:
tar -xvf tengine-1.4.2.tar.gz

3、安装依赖:

代码:
sudo apt-get install libpcre++-dev libssl-dev zlib1g-dev

注:pcre依赖是rewrite模块必选;ssl是https访问的模块;zlib是启用网页gzip压缩用,必选。
4、编译安装:

代码:
cd tengine-1.4.2
./configure –prefix=/usr –conf-path=/etc/nginx/nginx.conf –pid-path=/var/run/nginx.pid –lock-path=/var/lock/nginx.lock –http-client-body-temp-path=/var/tmp/nginx/client –http-proxy-temp-path=/var/tmp/nginx/proxy –http-fastcgi-temp-path=/var/tmp/nginx/fastcgi –http-scgi-temp-path=/var/tmp/nginx/scgi –http-uwsgi-temp-path=/var/tmp/nginx/uwsgi
make
sudo make install
mkdir /var/tmp/nginx

注:没有直接使用默认配置,按照ubuntu下apt-get安装nginx的规范来配置的。
另:如果无法make,则需要安装gcc、make包:

代码:
sudo apt-get install gcc make

5、配置文件及启动:
配置文件:/etc/nginx/nginx.conf
程序启动、停止:/usr/sbin/nginx -s {start/stop/reload}
版本信息(nginx -v):Tengine version: Tengine/1.4.2 (nginx/1.2.5)

6、附nginx的服务,可以拷贝到/etc/init.d/下,就可以直接用service nginx {start/stop/restart},还可以直接添加启动服务。

代码:
#!/bin/sh### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFOPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/nginx
NAME=nginx
DESC=nginx

# Include nginx defaults if available
if [ -f /etc/default/nginx ]; then
. /etc/default/nginx
fi

test -x $DAEMON || exit 0

set -e

. /lib/lsb/init-functions

test_nginx_config() {
if $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1; then
return 0
else
$DAEMON -t $DAEMON_OPTS
return $?
fi
}

case “$1” in
start)
echo -n “Starting $DESC: ”
test_nginx_config
# Check if the ULIMIT is set in /etc/default/nginx
if [ -n “$ULIMIT” ]; then
# Set the ulimits
ulimit $ULIMIT
fi
start-stop-daemon –start –quiet –pidfile /var/run/$NAME.pid \
–exec $DAEMON — $DAEMON_OPTS || true
echo “$NAME.”
;;

stop)
echo -n “Stopping $DESC: ”
start-stop-daemon –stop –quiet –pidfile /var/run/$NAME.pid \
–exec $DAEMON || true
echo “$NAME.”
;;

restart|force-reload)
echo -n “Restarting $DESC: ”
start-stop-daemon –stop –quiet –pidfile \
/var/run/$NAME.pid –exec $DAEMON || true
sleep 1
test_nginx_config
# Check if the ULIMIT is set in /etc/default/nginx
if [ -n “$ULIMIT” ]; then
# Set the ulimits
ulimit $ULIMIT
fi
start-stop-daemon –start –quiet –pidfile \
/var/run/$NAME.pid –exec $DAEMON — $DAEMON_OPTS || true
echo “$NAME.”
;;

reload)
echo -n “Reloading $DESC configuration: ”
test_nginx_config
start-stop-daemon –stop –signal HUP –quiet –pidfile /var/run/$NAME.pid \
–exec $DAEMON || true
echo “$NAME.”
;;

configtest|testconfig)
echo -n “Testing $DESC configuration: ”
if test_nginx_config; then
echo “$NAME.”
else
exit $?
fi
;;

status)
status_of_proc -p /var/run/$NAME.pid “$DAEMON” nginx && exit 0 || exit $?
;;
*)
echo “Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}” >&2
exit 1
;;
esac

exit 0

ReactNative 代码智能提醒(webstorm)

ReactNative 代码智能提醒 (Webstrom live template)

git clone https://github.com/virtoolswebplayer/ReactNative-LiveTemplate

说明

ReactNative的代码模板,包括:

  1. 组件名称
  2. Api 名称
  3. 所有StyleSheets属性
  4. React组件

安装

方法一

file -> import settings -> ReactNative.jar

方法二

Mac下安装

ReactNative.xml复制到 ~/Library/Preferences/WebStorm11/templates
重启 WebStrom

使用方法

通用方法

直接输入组件Api 名称的首字母, 比如想要 View ,只要输入 V自动提示代码里就会看到 View

STYLESHEET属性提示

首先 按下 command + J , 然后输入属性名的 首字母

如: 输入 f ,会自动提示 fontSize,fontFamily,fontStyle…等等

在react-native中加入socket.io

想要在react-native框架中实现长链接就得用上socket,react-native自带了一个webSocket,使用方法很简单,我们服务器端使用的是koa框架,配上socket.io简直叼炸天,网上也查过一些资料,都说到socket.io是支持webSocket的,于是乎我就兴高采烈地在react-native上写上了webSocket连接后端的代码,但是一运行。。。尼玛,报错啊,大致的意思就是连不上服务器啊,然后我就用egret引擎中的webSocket在web上也实现了一个客户端,结果也是无法连接。。。太坑爹啊。继续查资料,发现socket.io不支持ws协议。。。好吧,那么我们就在react-native上也使用socket.io吧,这会没问题了吧。

首先安装socket.io,因为react-native作为客户端,所以只要socket.io-client就行了。

执行安装命令:

npm install socket.io-client --save

然后写上代码:

import io from "socket.io-client/socket.io";
// 初始化socket
socket = io('http://77.100.10.19:3001', {jsonp: false});
// 发送消息
socket.emit("test", {"hello": "world"});
// 接收消息
socket.on('test', function (data) {
    console.log("收到消息:", data);
});

嗯,react-native客户端红屏报错了,报错的地方是node_modules\socket.io-client\socket.io.js的2985行,原因是socket.io都是用在web上的,而在移动端上navigator.userAgent是个null,那么改一下代码:

// var isAndroid = navigator.userAgent.match(/Android/i);
var isAndroid = /Android/i.test(navigator.userAgent);

然后reloadJS运行一下,嘿,没问题了,能收发消息。。。

win上搭建react-native android环境

  1. 安装JDK

既然是android开发,那么java肯定逃不掉了。从Java官网下载安装包吧。我的电脑是64位的,所以我选择下载64位的安装包。

  1. 安装Android SDK

先下载Android Studio,如果翻墙不方便的话,可以从AndroidDevTools上下载,我是翻墙从官网下载的。安装过程中会提示选择SDK的目录,默认是在系统盘,我选择的是F/android/sdk。安装完成后,先要配置一下sdk Manager的网络设置,否则在不翻墙的情况下安装sdk会很慢很慢。腾讯Bugly的镜像就挺好的,查看说明。 安装以下项目:

  • Tools/Android SDK Tools (24.4.1)
  • Tools/Android SDK Platform-tools (23.0.1)
  • Tools/Android SDK Build-tools (23.0.2)
  • Android 6.0 (API 23)/SDK Platform (1)
  • Extras/Android Support Library(23.1)

最后在系统环境变量中加入ANDROID_HOME,路径为sdk目录。

  1. 安装Git

下载Git,记得把git.exe的路径写入系统环境变量,因为在执行react-native init命名时会调用git去下载react-native的源码。

  1. 安装Node.js

官网下载最新版的安装包安装即可。npm(node package manager)是随着node.js就安装好的,为了加速安装其他的package,在cmd里输入以下命令:

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist
  1. 安装react-native命令行工具
npm install -g react-native-cli
  1. 创建react-native android项目
react-native init MyProject

这一步要等上很长时间,原因不明。不想等待的话可以在这里下载项目文件,然后将android目录下的gradle.properties文件中的sdk.dir设为本机上sdk的目录。

sdk.dir=F:\\android\\sdk
  1. 启动react-native服务

进入项目文件夹,输入启动命令

react-native start

在浏览器中访问地址:http://localhost:8081/index.android.bundle?platform=android 第一次访问需要骚等一会,这是在生成android的bundle文件。cmd窗口别关,一直保持开启状态

  1. 安装并启动android模拟器

推荐逍遥模拟器,安装好并启动。试用了很多模拟器,有些模拟器adb无法连接,有些模拟器adb连上了,但是在却没有“摇一摇”或者菜单键,非常蛋疼。终于让我发现了逍遥模拟器,嗯,很不错。启动模拟器,在命令行中输入

adb devices

看看adb是否和设备连接上了,如果没有连接上,那么需要手动连接,连接设备需要一个端口号,那么这个端口号是多少呢?聪明的我通过查找模拟器log找到了答案。打开模拟器安装目录,然后找到MEmu\MemuHyperv VMs\MEmu\Logs下的log文件,打开此log,通过查找关键词“ port ”(注意port两边都带上空格),可以发现:

00:00:00.277099 NAT: set redirect TCP host port 21505 => guest port 21505 @ 10.0.2.15
00:00:00.278027 NAT: set redirect TCP host port 21504 => guest port 21504 @ 10.0.2.15
00:00:00.278123 NAT: set redirect TCP host port 21501 => guest port 21501 @ 10.0.2.15
00:00:00.278199 NAT: set redirect TCP host port 21500 => guest port 21500 @ 10.0.2.15
00:00:00.278274 NAT: set redirect TCP host port 21502 => guest port 21502 @ 10.0.2.15
00:00:00.278342 NAT: set redirect TCP host port 21503 => guest port 5555 @ 10.0.2.15

诶,找到了,端口号是21503,然后回到命令行,输入:

adb connect 127.0.0.1:21503

然后再一次输入

adb devices

adb是不是连上模拟器了?那么接下去就是在模拟器中运行我们的小demo了。

  1. 在模拟器中运行

进入项目目录,输入命令:

react-native run-android

第一次运行时会需要下载一些东西,等待就行。 build成功后便会在模拟器上自动运行了 react-native的hello world如果是连真机的话,很大可能看到的结果是一片白啊一片白。经查,发现是安全中心中的“悬浮窗”权限并没有对我们的这个新app开放,那么将权限开放,重启app,啊呀,一片红啊一片红。。。长按物理菜单键或者死命摇一摇手机,会弹出一个小窗口,选择“Dev Settings”,然后选择“Debug server host for device”,会弹出一个输入框,输入电脑ip地址和默认的8081端口,再次重新app,

学习 Android MVP

一、概述

对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让Model和View完全解耦”等等。本篇博文仅是为了做下记录,提出一些自己的看法,和帮助大家如何针对一个Activity页面去编写针对MVP风格的代码。

对于MVP,我的内心有一个问题:

为何这个模式出来后,就能被广大的Android的程序员接受呢?

问了些程序员,他们对于MVP的普遍的认识是:“代码很清晰,不过增加了很多类”。我在第一次看到MVP的时候,看了一个demo,看完以后觉得非常nice(但是回过头来,自己想个例子写,就头疼写不出来,当然这在后文会说)。nice的原因还是因为,这个模式的确让代码的清晰度有了很大的提升。

那么,提升一般都是对比出来的,回顾下,没有应用MVP的代码结构。很多人说明显是MVC么:

  • View:对应于布局文件
  • Model:业务逻辑和实体模型
  • Controllor:对应于Activity

看起来的确像那么回事,但是细细的想想这个View对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller(当然了Data-Binder的出现,可能会让View更像View吧)。这可能也就是为何,在该文中有一句这样的话:

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

而当将架构改为MVP以后,Presenter的出现,将Actvity视为View层,Presenter负责完成View层与Model层的交互。现在是这样的:

  • View 对应于Activity,负责View的绘制以及与用户交互
  • Model 依然是业务逻辑和实体模型
  • Presenter 负责完成View于Model间的交互

ok,先简单了解下,文中会有例子到时候可以直观的感受下。

小总结下,也就是说,之所以让人觉得耳目一新,是因为这次的跳跃是从并不标准的MVCMVP的一个转变,减少了Activity的职责,简化了Activity中的代码,将复杂的逻辑代码提取到了Presenter中进行处理。与之对应的好处就是,耦合度更低,更方便的进行测试。借用两张图(出自:该文),代表上述的转变:

转变为:

二、MVP 与 MVC 区别

ok,上面说了一堆理论,下面我们还是需要看一看MVC与MVP的一个区别,请看下图(来自:本文):

其实最明显的区别就是,MVC中是允许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是通过接口的(代码中会体现)。

还有一堆概念性的东西,以及优点就略了,有兴趣自行百度。下面还是通过一些简单的需求来展示如何编写MVP的demo。

三、Simple Login Demo

效果图:

看到这样的效果,先看下完工后的项目结构:

ok,接下来开始一步一步的编写思路。

(一)Model

首先实体类User不用考虑这个肯定有,其次从效果图可以看到至少有一个业务方法login(),这两点没什么难度,我们首先完成:

package com.zhy.blogcodes.mvp.bean;

/**
 * Created by zhy on 15/6/18.
 */
public class User
{
    private String username ;
    private String password ;

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }
}


package com.zhy.blogcodes.mvp.biz;

/**
 * Created by zhy on 15/6/19.
 */
public interface IUserBiz
{
    public void login(String username, String password, OnLoginListener loginListener);
}
package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public class UserBiz implements IUserBiz
{

    @Override
    public void login(final String username, final String password, final OnLoginListener loginListener)
    {
        //模拟子线程耗时操作
        new Thread()
        {
            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(2000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                //模拟登录成功
                if ("zhy".equals(username) && "123".equals(password))
                {
                    User user = new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    loginListener.loginSuccess(user);
                } else
                {
                    loginListener.loginFailed();
                }
            }
        }.start();
    }
}
package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public interface OnLoginListener
{
    void loginSuccess(User user);

    void loginFailed();
}

实体类不用说,至于业务类,我们抽取了一个接口,一个实现类这也很常见~~login方法,一般肯定是连接服务器的,是个耗时操作,所以我们开辟了子线程,Thread.sleep(2000)模拟了耗时,由于是耗时操作,所以我们通过一个回调接口来通知登录的状态。

其实这里还是比较好写的,因为和传统写法没区别。

(二) View

上面我们说过,Presenter与View交互是通过接口。所以我们这里需要定义一个ILoginView,难点就在于应该有哪些方法,我们看一眼效果图:

可以看到我们有两个按钮,一个是login,一个是clear;

login说明了要有用户名、密码,那么对应两个方法:


    String getUserName();

    String getPassword();

再者login是个耗时操作,我们需要给用户一个友好的提示,一般就是操作ProgressBar,所以再两个:

    void showLoading();

    void hideLoading();

login当然存在登录成功与失败的处理,我们主要看成功我们是跳转Activity,而失败可能是去给个提醒:

    void toMainActivity(User user);

    void showFailedError();

ok,login这个方法我们分析完了~~还剩个clear那就简单了:

    void clearUserName();

    void clearPassword();

综上,接口完整为:

package com.zhy.blogcodes.mvp.view;

import com.zhy.blogcodes.mvp.bean.User;

/**
 * Created by zhy on 15/6/19.
 */
public interface IUserLoginView
{
    String getUserName();

    String getPassword();

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void toMainActivity(User user);

    void showFailedError();

}

有了接口,实现就太好写了~~~

总结下,对于View的接口,去观察功能上的操作,然后考虑:

  • 该操作需要什么?(getUserName, getPassword)
  • 该操作的结果,对应的反馈?(toMainActivity, showFailedError)
  • 该操作过程中对应的友好的交互?(showLoading, hideLoading)

下面贴一下我们的View的实现类,哈,其实就是Activity,文章开始就说过,MVP中的View其实就是Activity。

package com.zhy.blogcodes.mvp;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.zhy.blogcodes.R;
import com.zhy.blogcodes.mvp.bean.User;
import com.zhy.blogcodes.mvp.presenter.UserLoginPresenter;
import com.zhy.blogcodes.mvp.view.IUserLoginView;

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView
{


    private EditText mEtUsername, mEtPassword;
    private Button mBtnLogin, mBtnClear;
    private ProgressBar mPbLoading;

    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_login);

        initViews();
    }

    private void initViews()
    {
        mEtUsername = (EditText) findViewById(R.id.id_et_username);
        mEtPassword = (EditText) findViewById(R.id.id_et_password);

        mBtnClear = (Button) findViewById(R.id.id_btn_clear);
        mBtnLogin = (Button) findViewById(R.id.id_btn_login);

        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);

        mBtnLogin.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.login();
            }
        });

        mBtnClear.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.clear();
            }
        });
    }


    @Override
    public String getUserName()
    {
        return mEtUsername.getText().toString();
    }

    @Override
    public String getPassword()
    {
        return mEtPassword.getText().toString();
    }

    @Override
    public void clearUserName()
    {
        mEtUsername.setText("");
    }

    @Override
    public void clearPassword()
    {
        mEtPassword.setText("");
    }

    @Override
    public void showLoading()
    {
        mPbLoading.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading()
    {
        mPbLoading.setVisibility(View.GONE);
    }

    @Override
    public void toMainActivity(User user)
    {
        Toast.makeText(this, user.getUsername() +
                " login success , to MainActivity", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showFailedError()
    {
        Toast.makeText(this,
                "login failed", Toast.LENGTH_SHORT).show();
    }
}

对于在Activity中实现我们上述定义的接口,是一件很容易的事,毕竟接口引导我们去完成。

最后看我们的Presenter。

(三)Presenter

Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?

其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。

package com.zhy.blogcodes.mvp.presenter;

import android.os.Handler;

import com.zhy.blogcodes.mvp.bean.User;
import com.zhy.blogcodes.mvp.biz.IUserBiz;
import com.zhy.blogcodes.mvp.biz.OnLoginListener;
import com.zhy.blogcodes.mvp.biz.UserBiz;
import com.zhy.blogcodes.mvp.view.IUserLoginView;


/**
 * Created by zhy on 15/6/19.
 */
public class UserLoginPresenter
{
    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler();

    public UserLoginPresenter(IUserLoginView userLoginView)
    {
        this.userLoginView = userLoginView;
        this.userBiz = new UserBiz();
    }

    public void login()
    {
        userLoginView.showLoading();
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()
        {
            @Override
            public void loginSuccess(final User user)
            {
                //需要在UI线程执行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.toMainActivity(user);
                        userLoginView.hideLoading();
                    }
                });

            }

            @Override
            public void loginFailed()
            {
                //需要在UI线程执行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.showFailedError();
                        userLoginView.hideLoading();
                    }
                });

            }
        });
    }

    public void clear()
    {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }



}

注意上述代码,我们的presenter完成二者的交互,那么肯定需要二者的实现类。大致就是从View中获取需要的参数,交给Model去执行业务方法,执行的过程中需要的反馈,以及结果,再让View进行做对应的显示。

ok,拿到一个例子经过上述的分解基本就能完成。以上纯属个人见解,欢迎讨论和吐槽~ have a nice day ~。

源码点击下载

Linux下查看内存使用情况方法总结

Q:我想监视Linux系统的内存使用情况,在Linux下有哪些视图或者命令行工具可用呢?

在做Linux系统优化的时候,物理内存是其中最重要的一方面。自然的,Linux也提供了非常多的方法来监控宝贵的内存资源的使用情况。下面的清单详细的列出了Linux系统下通过视图工具或命令行来查看内存使用情况的各种方法。

 

1. /proc/meminfo

查看RAM使用情况最简单的方法是通过 /proc/meminfo。这个动态更新的虚拟文件实际上是许多其他内存相关工具(如:free / ps / top)等的组合显示。/proc/meminfo列出了所有你想了解的内存的使用情况。进程的内存使用信息也可以通过 /proc/<pid>/statm 和 /proc/<pid>/status 来查看。

  1. $ cat /proc/meminfo

/proc/meminfo

2. atop

atop命令是一个终端环境的监控命令。它显示的是各种系统资源(CPU, memory, network, I/O, kernel)的综合,并且在高负载的情况下进行了彩色标注。

  1. $ sudo atop

atop

3. free

free命令是一个快速查看内存使用情况的方法,它是对 /proc/meminfo 收集到的信息的一个概述。

  1. $ free -h

free

4. GNOME System Monitor

GNOME System Monitor 是一个显示最近一段时间内的CPU、内存、交换区及网络的使用情况的视图工具。它还提供了一种查看CPU及内存使用情况的方法。

  1. $ gnome-system-monitor

gnome-system-monitor

5. htop

htop命令显示了每个进程的内存实时使用率。它提供了所有进程的常驻内存大小、程序总内存大小、共享库大小等的报告。列表可以水平及垂直滚动。

  1. $ htop

$ htop

6. KDE System Monitor

功能同 4 中介绍的GENOME版本。

  1. $ ksysguard

$ ksysguard

7. memstat

memstat是一个有效识别 executable(s), process(es) and shared libraries使用虚拟内存情况的命令。给定一个进程ID,memstat可以列出这个进程相关的可执行文件、数据和共享库。

  1. $ memstat -p <PID>

memstat

8. nmon

nmon是一个基于ncurses的系统基准测试工具,它可以监控CPU、内存、I/O、文件系统及网络资源等的互动模式。对于内存的使用,它可以实时的显示 总/剩余内存、交换空间等信息。

  1. $ nmon

nmon

9. ps

ps命令可以实时的显示各个进程的内存使用情况。Reported memory usage information includes %MEM (percent of physical memory used), VSZ (total amount of virtual memory used), and RSS (total amount of physical memory used)。你可以使用 “–sort”选项对进程进行排序,例如按RSS进行排序:

  1. $ ps aux –sort -rss

ps

10. smem

smem命令允许你统计基于/proc信息的不同进程和用户的内存使用情况。内存使用情况的分析可以导出图表(如条形图和饼图)。

  1. $ sudo smem –pie name -c “pss”

smem

11. top

top命令提供了实时的运行中的程序的资源使用统计。你可以根据内存的使用和大小来进行排序。

  1. $ top

linux-memory-11

12. vmstat

vmstat命令显示实时的和平均的统计,覆盖CPU、内存、I/O等内容。例如内存情况,不仅显示物理内存,也统计虚拟内存。

  1. $ vmstat -s

 

 

单点登录SSO

单点登录(Single sign-on,SSO)是一种访问控制,在多个软件应用中,用户只需登录其中一个应用,就可以成功访问其他应用;同样,用户只需注销其中一个应用,就可以成功注销其他应用。

当一个公司产品线越来越复杂,做的东西越来越多,考虑到用户的便利性和业务的交集,单点登录也就变得越来越必然。譬如,阿里巴巴中的淘宝网,天猫,聚划算和一淘,考虑下面的场景:我们用户登录淘宝网购物,紧接着朋友打电话说出去玩,于是打开聚划算的时候你会发现,你已经登了聚划算!可能这些细节都被大多数人忽略了(被谁给惯坏了),但如果要让用户再次手动登录聚划算,用户体验可想而知。这种便利性就是单点登录所带来的。

在单点登录中,认证系统会为每一个应用分配一把钥匙,也就是说有了这把钥匙,账号密码的输入就可以免去了。这把钥匙就藏在浏览器的 cookie 中。应用获取钥匙有两种方法:

一,成功登录应用A 后,认证系统为应用A 分配一把钥匙;同时,应用A 凭借自己已经成功登录,帮其他应用代领钥匙。下一次访问应用B 的时候,应用B 就能成功免登录了。

二,这里认证系统的域名是应用A 的子域名,即如果应用A 是 example.com,认证系统可能是个 passport.example.com。当成功登录应用A 后,认证系统为应用A 分配一把钥匙;下一次访问应用B 的时候,web 页面被重定向到认证系统,因为认证系统的域名是应用A 的子域名,所以应用A 的钥匙,即 cookie 被带上,从而用户的访问得到了信任,认证系统为应用B 分配钥匙,页面被重定向到应用B。

接下来会对淘宝网和京东商城的网站进行单点登录实例分析。

淘宝网的单点登录策略

来看看淘宝网做法。

登录了 taobao.com 后,下面是所产生的 cookie,也就是说认证系统已经为应用taobao.com 分配了钥匙,但这里并没有 etao.com 或者 tmall.com 的 cookie,认证系统还未为他们分配钥匙。

taobao_cookie

下一步我们尝试去访问 etao.com:

etao_network

etao.com 被重定向到了 www.etao.com;访问 www.etao.com 被重定向到 http://jump.taobao.com/,下面是 response HTTP:

HTTP/1.1 302 Moved Temporarily
Server: Tengine
Date: Sat, 29 Mar 2014 14:29:41 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Location: http://jump.taobao.com/jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329

浏览器会继续访问 http://jump.taobao.com/jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329,下面是 request HTTP,这里访问带上了应用taobao.com 的钥匙,即 cookie,网站后台会验证应用taobao.com 的钥匙:

GET /jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329 HTTP/1.1
Host: jump.taobao.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: ...

认证成功,看来钥匙是有效的,又产生一个重定向,下面是和上面对应的 response HTTP:

HTTP/1.1 302 Found
Date: Sat, 29 Mar 2014 14:29:41 GMT
Content-Type: text/html
Content-Length: 260
Connection: close
Set-Cookie: _tb_token_=AtWQpv7iedma;domain=.taobao.com;Path=/;HttpOnly
P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR'
Location: http://pass.etao.com/add?uc3=nk2=Dkjr489jrew%3D;id2=Vyu3rko%2FyYdG;vt3=F8dHqR4J6Q2W7ouKdi8%3D;lg2=WqG3DMC9VAQiUQ%3D%3D&lgc=XXXXXX&tracknick=XXXXXX&_l_g_=Ug%3D%3D&cookie1=U7lSAZ5WionEzYGt3e34IvM%2BhHRTBL5Y%2BYTf7E22Ixo%3D&cookie2=007485c12ac6179b824c7656627e806c&cookie17=Vyu3rko%2FyYdG&t=4376b50842a95fbd0464fc1d58fe84c5&unb=479940688&_nk_=XXXXXX&uc1=cookie15=V32FPkk%2Fw0dUvg%3D%3D&_tb_token_=AtWQpv7iedma&target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329&pacc=_7LwGS9DwiA3O_Iq8iAaMQ==&opi=183.62.180.11&tmsc=1396103381332051

接下来浏览器访问 http://pass.etao.com/add?…….,下面 response HTTP:

HTTP/1.1 302 Found
Date: Sat, 29 Mar 2014 14:29:41 GMT
Content-Type: text/html
Content-Length: 260
Connection: close
P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR'
Set-Cookie: uc3=nk2=Dkjr489jrew%3D&id2=Vyu3rko%2FyYdG&vt3=F8dHqR4J6Q2W7ouKdi8%3D&lg2=WqG3DMC9VAQiUQ%3D%3D;domain=.etao.com;Path=/
Set-Cookie: ...;domain=.etao.com;Path=/
Set-Cookie: ...;domain=.etao.com;Path=/
Set-Cookie: ...;domain=.etao.com;Path=/
Set-Cookie: ...
Set-Cookie: ...
Set-Cookie: ...
...
Location: http://www.etao.com/?tbpm=20140329

「Set-Cookie」意味着应用etao.com 拿到了认证系统的钥匙,耶斯!

etao_cookie

京东商城单点登录策略

来看看京东商城的做法

jd_cookie

登录 jd.com 后发现,它已经给所有的应用代领了钥匙!这里利用了前端里面的 jsonp,对于跨域的问题,jsonp 驾轻就熟。

成功登录 jd.com,会跳转到 jd.com,里面有一小段 js 代码发起了 jsonp 请求:

$.ajax({
    url: ("https:" == document.location.protocol ? "https://" : "http://") + "passport." + pageConfig.FN_getDomain() + "/new/helloService.ashx?m=ls",
    dataType: "jsonp",
    scriptCharset: "gb2312",
    success: function(a) {
            a && a.info && $("#loginbar").html(a.info), a && a.sso && $.each(a.sso, function() {
                $.getJSON(this)
            })
    }
})

ajax get 到数据自动调用预设值的回调函数。jsonp 返回的数据是:

jsonp1396084410330({"sso":[
     "http://sso.jd.com/setCookie?t=sso.360buy.com&callback=?",
     "http://sso.jd.com/setCookie?t=sso.wangyin.com&callback=?",
     "http://sso.jd.com/setCookie?t=sso.360top.com&callback=?",
     "http://sso.jd.com/setCookie?t=sso.minitiao.com&callback=?",
     "http://sso.jd.com/setCookie?t=sso.jcloud.com&callback=?"],
     "info":"您好,买东西别坑爹!<a href="http://passport.jd.com/uc/login?ltype=logout">[退出]</a>"})

回调函数对 a.sso 中每一个连接执行 getJSON()。注:使用 jquery 的 getJSON() 进行跨域读取数据,实际上 getJSON() 方式的根本原理和 ajax 使用 jsonp 的方式一样。

jquery jsonp 请求

jquery jsonp 请求

jquery jsonp 请求产生的重定向

jquery jsonp 请求产生的重定向

以第一个参数为例,所产生的 request HTTP:

GET /setCookie?t=sso.360buy.com&callback=jsonp1396084410335&_=1396084411190 HTTP/1.1
Host: sso.jd.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
Referer: http://www.jd.com/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: ...

结果产生一个重定向到 http://sso.360buy.com 的响应,与上面对应的 response HTTP:

HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Sat, 29 Mar 2014 09:13:26 GMT
pragma: no-cache
cache-control: max-age=86400
Location: http://sso.360buy.com/sign?c=0f5882246bae6fc065e387995680f4d73a03c448a244d27e328ae7abcd3c7fd7fa629605ac516c1903e014b45e906a7bc7bf5d7c237752f85709d12fd9cbe5a81c2602f684d352311de39612555d2fc2b4aa6fffca26d5a7e37e61e9a1b9e7e184cf8b335982c36b39944faf29cb1b61dd6b6a8461000c7661fc881a5daa89ddaaecf062d2e84b96e958534f5a407524596dcce400299487e87060d4ac55d454c36804deca3fd670adca3902d7e05d778fbc135c9a6e491b44ed5954337ac38f5b61e99934a0411b9af695ebbb7f75a4e69da4c24f1b3137ef6cc916af3c62238ee2b3c90f762d9b162239d634928f027585ab13c1e031861fb41a6a83ec2d1fec8b00389b91e5ab5c5db3961f60949c230d5280898d62c9f66f76058c35af78b1781d266ee548bd&callback=jsonp1396084410335&_=1396084411190&t=1396084406861&pin=XXXXXX&unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9
Content-Length: 0
Expires: Sun, 30 Mar 2014 09:13:26 GMT
Connection: Keep-alive
Keep-Alive: timeout=15, max=100

再次产生新的请求,请求目标为 http://sso.360buy.com:

GET /sign?c=0f5882246bae6fc065e387995680f4d73a03c448a244d27e328ae7abcd3c7fd7fa629605ac516c1903e014b45e906a7bc7bf5d7c237752f85709d12fd9cbe5a81c2602f684d352311de39612555d2fc2b4aa6fffca26d5a7e37e61e9a1b9e7e184cf8b335982c36b39944faf29cb1b61dd6b6a8461000c7661fc881a5daa89ddaaecf062d2e84b96e958534f5a407524596dcce400299487e87060d4ac55d454c36804deca3fd670adca3902d7e05d778fbc135c9a6e491b44ed5954337ac38f5b61e99934a0411b9af695ebbb7f75a4e69da4c24f1b3137ef6cc916af3c62238ee2b3c90f762d9b162239d634928f027585ab13c1e031861fb41a6a83ec2d1fec8b00389b91e5ab5c5db3961f60949c230d5280898d62c9f66f76058c35af78b1781d266ee548bd&callback=jsonp1396084410335&_=1396084411190&t=1396084406861&pin=XXXXXX&unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9 HTTP/1.1
Host: sso.360buy.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
Referer: http://www.jd.com/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

response HTTP 中有 「Set-Cookie」说明已经拿到认证系统的钥匙了:

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 29 Mar 2014 09:13:27 GMT
Content-Type: text/javascript;charset=UTF-8
pragma: no-cache
cache-control: max-age=86400
P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"
SET-COOKIE: ceshi3.com=EA0B41EDE28DC567542D0191B48C74F35CD76AAEBADA867F4879AA3F02B4EA559D6E537ED0BEB4CCD417D4A434A7CC86C30CBA8775BCA96C36AB067034D0E057F9ED0297AFA566954A10E03DFF292089736A8B79554C32CB8FCFAA2F042F547DACCD30BAC81A9815083C9F1071B5B8382968A5A7E82EC0172E6AEE15930A6AA47C6875A1CDEE90118515ED0022909DAE;path=/;domain=.360buy.com;httponly
Set-Cookie: pin=XXXXXX; Domain=.360buy.com; Path=/
Set-Cookie: unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9; Domain=.360buy.com; Path=/
Content-Length: 43
Expires: Sun, 30 Mar 2014 09:13:27 GMT
Connection: Keep-alive
Keep-Alive: timeout=15, max=100

其他应用类似,这里只举例 360buy.com.接着我试图访问 http://360buy.com:

GET / HTTP/1.1
Host: 360buy.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
// 下面提交的 cookie,即是 360buy.com 的钥匙
Cookie: ceshi3.com=EA0B41EDE28DC567542D0191B48C74F35CD76AAEBADA867F4879AA3F02B4EA559D6E537ED0BEB4CCD417D4A434A7CC86C30CBA8775BCA96C36AB067034D0E057F9ED0297AFA566954A10E03DFF292089736A8B79554C32CB8FCFAA2F042F547DACCD30BAC81A9815083C9F1071B5B8382968A5A7E82EC0172E6AEE15930A6AA47C6875A1CDEE90118515ED0022909DAE; pin=XXXXXX; unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9

HTTP/1.1 200 OK
Server: JDWS/1.0.0
Date: Sat, 29 Mar 2014 11:24:37 GMT
Content-Type: text/html
Last-Modified: Wed, 26 Mar 2014 11:54:10 GMT
Transfer-Encoding: chunked
Vary: Accept-Encoding
Expires: Sat, 29 Mar 2014 11:24:37 GMT
Cache-Control: max-age=0
Content-Encoding: gzip
Connection: Keep-alive
Keep-Alive: timeout=15, max=100

可见访问 http://360buy.com 的时候,并没有特地跑去认证系统索要钥匙,只凭借之前访问 jd.com 时 getJSON() 留下的 cookie。

360buy_com_network

关于单点登录的问题,还有待更深入讨论。我只是用一些抓包工具和网站的前端代码来猜测单点登录策略如何,后台会是更复杂的技术,譬如分布式存储等。对于上面的两个案例,如果你了解淘宝或者京东商城的单点登录的具体方法,不吝赐教。

上面所说,单点登录所带来的用户体验可能被用户忽略了。说到底用户都是给惯坏了,微信5 出来后被骂几条街,还不是习惯了之前的操作,又要去探索新版本的用法,太懒;再说到底,用户是上帝啊,就这么惯着吧!