使用ConstraintLayout(约束布局)构建响应式UI

ConstraintLayout(约束布局)是Google IO 2016推出的Android新布局方式。

以下内容翻自官方文档。

ConstraintLayout允许您使用扁平的层级(不用嵌套View Group)创建大型复杂的布局。与RelativeLayout类似,它通过相邻的view和父layout的相对关系来确定位置,但比RelativeLayout更加灵活,更容易通过Android Studio的布局编辑器实现想要的效果。

Continue reading ‘使用ConstraintLayout(约束布局)构建响应式UI’ »

Angular2 Material Design开发环境搭建

Angular2和Angular1有什么区别?就是Java和Javascript,雷锋和雷锋塔的区别。刚会用Angular1做些小项目,发现Angular2来了,据说性能上提升还是蛮多,所以还是从0开始吧。Angular2虽然还是支持用es5代码开发,但连官方教程都已经用TypeScript,对于之前没有接触过的人来说,学习还是有些成本的。

作为一个新手,搭建环境还是折腾了很久,所以记录下来方便其他新手。

流程:

  1. 使用angular-cli创建项目
  2. 集成Angular Material

angular-cli和Angular Material目前都还在beta阶段,毕竟Angular2正式发布也没有很久,用于生产请自己斟酌。

创建Angular2项目

  1. 安装angular-cli
    npm install -g angular-cli
    
  2. 在适当的位置创建项目
    ng new angular2Demo
    cd angular2Demo
    ng serve
    

创建过程会自动使用npm下载依赖,ng serve即启动内置的服务器,默认端口4200。使用浏览器打开 http://localhost:4200,可以看到项目已经启起来了。

此时打开chrome的network,可以发现有个vendor.bundle.js特别大,有2M多,图上4M多是因为我已经加了Material库。这是webpack把所有依赖的库都打包在一起了,而且没有任何压缩。

image

显然这么大的文件放到生产环境中是不合适的。这是因为默认情况下,启动的是开发模式。

生成生产环境代码:

ng build --prod --env=prod

执行后,会在项目根目录下生成dist目录,所有优化后的代码

以生产模式启动服务器

ng serve --prod

再看network,发现所有资源自动打上了版本号vendor.xxx.bundle.js也被压到了200多k。

集成Material

  1. 安装

Material是以组件的形式开发的,所以安装也使用npm

npm install --save @angular/material

2.修改app.module.ts

打开src/app/app.module.ts,引入material模块

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { MaterialModule } from '@angular/material';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    MaterialModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  1. 引入css样式

在src目录下新建material-design.scss:

@import '~@angular/material/core/theming/all-theme';

// NOTE: Theming is currently experimental and not yet publically released!
@include md-core();
$primary: md-palette($md-deep-purple);
$accent:  md-palette($md-amber, A200, A100, A400);
$theme: md-light-theme($primary, $accent);
@include angular-material-theme($theme);
.m2app-dark {
  $dark-primary: md-palette($md-pink, 700, 500, 900);
  $dark-accent:  md-palette($md-blue-grey, A200, A100, A400);
  $dark-warn:    md-palette($md-deep-orange);
  $dark-theme: md-dark-theme($dark-primary, $dark-accent, $dark-warn);
  @include angular-material-theme($dark-theme);
}

打开根目录下的angular-cli.json,styles配置增加刚刚的文件

"apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.json",
      "prefix": "app",
      "mobile": false,
      "styles": [
        "styles.css",
        "material-design.scss"
      ],
      "scripts": [],
      "environments": {
        "source": "environments/environment.ts",
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  1. 配置MaterialDesign字体

index.html增加:

    <link href="//fonts.lug.ustc.edu.cn/icon?family=Material+Icons" rel="stylesheet">

使用了中科大的google font镜像

  1. 如果需要用到md-slider(可拖动的进度条)和md-slide-toggle(带滑动动画的开关),还需要安装HammerJS,否则到此环境搭建已完成。
    
    npm install hammerjs  --save 
    npm install @types/hammerjs --save-dev 
    

修改src/app/app,module.ts,引入hammerjs

import 'hammerjs';

修改src/tsconfig.json,配置types增加hammerjs

{
  "compilerOptions": {
    "baseUrl": "",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es6", "dom"],
    "mapRoot": "./",
    "module": "es6",
    "moduleResolution": "node",
    "outDir": "../dist/out-tsc",
    "sourceMap": true,
    "target": "es5",
    "typeRoots": [
      "../node_modules/@types"
    ],
    "types": [
      "hammerjs"
    ]
  }
}

所有配置到此结束,我们写一段简单的代码测试一下。
编辑src/app/app.component.html:

<md-progress-spinner mode="indeterminate" ></md-progress-spinner>

启动服务器,如果所有配置没有错,页面上应该显示旋转的进度圈。

使用SSH实现内网穿透

前面写过一篇使用ngrok实现外网访问内网的文章,但实现起来稍复杂,而且我在实际使用过程中也发现经常断线,ngrok+nginx 实现内网穿透 共用80端口,ngrok的优点在于独立于任何其他服务,需要转发什么域名直接改客户端配置。

今天介绍的是通过SSH穿透内网

SSH内网穿透优点

  1. 无需额外安装服务端客户端(ssh你机器上肯定有)
  2. 可以转发到所以你的机器能直接访问的机器上(你可以给在同一局域网的前端妹子配个外网可访问域名,不用在她的电脑上配置,如果觉得浪费了搭讪机会,可以在她电脑上随便敲点啥)

缺点

如果要将这个功能开放给别人,要注意配置ssh账号权限,自己用无所谓。

一条命令实现

ssh -CfnNT -R 6666:192.168.10.120:8080 fff@server1.pocketdigi.com -p 4356
  • 6666:外网服务器的端口号,外网服务器所有6666的请求会转发
  • 192.168.10.120 转发目标机器,本机就是localhost
  • fff ssh账号
  • server1.pocketdigi.com 外网服务器域名
  • 4356 外网服务器的ssh端口号,默认是22,总有一班人天天扫,所以改了

此时所有访问server1.pocketdigi.com:6666的请求会转发到192.168.10.120:8080,可在外网服务器上执行

curl http://localhost:6666

查看,如果192.168.10.120:8080启了http服务,应该能看到.
鉴于ssh会超时断开,建议使用autossh,断开会自动重连

配置nginx

参考 ngrok+nginx 实现内网穿透 共用80端口

开机自启

  1. 增加启动文件 /usr/local/bin/nat_forward

    !/bin/bash

    autossh -M 5678 -CfnNT -R 6666:localhost:8080 fff@server1.pocketdigi.com -p 4356

2.增加执行权限

sudo chmod +x /usr/local/bin/nat_forward

3.增加启动项

偏好设置--用户与群组--登录项,添加/usr/local/bin/nat_forward

阿里云oss grunt上传插件

最近在研究前端工程化,准备把项目里的静态文件全传到阿里云 oss上,构建工具我们选的是grunt,npm里也有两三个oss上传插件,但是更新时间都比较久了,一一试过后都不能用,于是自己写了个插件。托管在 https://github.com/pocketdigi/grunt-aliyun-oss , 欢迎体验。
支持ContentType自动识别,Cache Control设置,上传路径设置。

grunt-aliyun-oss

aliyun oss plugin for grunt

Getting Started

This plugin requires Grunt ~0.4.5

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-aliyun-oss --save-dev

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-aliyun-oss');

The "aliyun_oss" task

Overview

In your project's Gruntfile, add a section named aliyun_oss to the data object passed into grunt.initConfig().

grunt.initConfig({
  aliyun_oss: {
    options: {
      accessKeyId: 'accessKeyId',
      secretAccessKey: 'secretAccessKey',
      endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
      bucketName:'bucket',
      cacheControl:'no-cache'
    },
    your_target: {
      // Target-specific file lists and/or options go here.
    },
  },
});

AliYun OSS Api doc

Options

options.accessKeyId

Type: String

AliYun OSS accessKeyId

options.secretAccessKey

Type: String

AliYun OSS secretAccessKey

options.endpoint

Type: String

AliYun OSS endpoint

options.bucketName

Type: String

AliYun OSS bucketName

options.bucketName

Type: String Default value:no-cache

Cache Control in the Http Header,default value is no-cache

See Http Cache

Usage Examples

Default Options

In this example, the default options are used to upload all file expect html file in dist directory to oss bucket1 bucket,and the oss root path is static/

grunt.initConfig({
   aliyun_oss: {
              default_options: {
                  options: {
                      accessKeyId: 'xxxx',
                      secretAccessKey: 'xxxx',
                      endpoint: 'http://oss-cn-hangzhou.aliyuncs.com',
                      bucketName:'bucket1',
                      cacheControl:'no-cache'
                  },
                  files: [
                      {
                          expand: true,
                          cwd: 'dist',
                          src: ['**/*','!**/*.html'],
                          dest:'static/'
                      }
                  ]
              }
          }
});

Release History

  • v0.2.0 fix upload bug,support ContentType,cache control.
  • v0.1.0 first version

AngularJs表单形式发送POST请求

AngularJS 的POST请求 默认Content-Type是"application/json",并不是表单形式的"application/x-www-form-urlencoded"提交,服务端接收到的是对象,而不是字段,当然对于RESTful接口来说,正好需要对象,SpringMVC通过@RequestBody注解就可以得到相应对象,很是方便。

如果需要使用传统方式,可以设置Content-Type

    $http.post('/login/in', $.param({username: $scope.username, password: $scope.password}),
        {headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}})
        .then(function (response) {
            if (response.data.success) {
                console.log("登录成功");
            } else {
                console.log(response.data.msg);
            }
        });

$.param是JQuery方法,将js对象转成username=username&password=password形式

ngrok+nginx 实现内网穿透 共用80端口

内网穿透能干嘛:

穿透后,你的机器指定端口就直接暴露在外网上,3389,vnc,web服务等等任何服务都可以支持。微信开发需要回调web服务器接口,如果开发机器在内网,就无法调试。

原理

外网服务器A,内网服务器B,通过某种协议连接,将域名解析到A,A将所有用户请求数据转发到B的指定端口,B返回的数据通过A转给用户,等于在B和用户之间加了代理A

解决方案

内网穿透有现成的商业解决方案,比如说花生壳,但是免费用户只给1g每月的流量,还要花几块钱认证。
网上也有免费的ngrok服务,直接下载对方提供的客户端就可以,但免费的总是不稳定,也有一些限制,需要的同学可以自己搜。

今天我们讨论的是在自己的VPS上架设ngrok服务,访问内网的web服务,场景是这台vps上同时开了着web服务(nginx),所以不能直接将80端口作为ngrok的转发端口, ngrok配置成功后,需要在nginx上作一次转发,才能使用80端口。

机器是centos 6.7。

1、安装最新版git

当前yum安装的git是1.7.1,这个版本太老,编译ngrok时会有问题,需要下载源码编译安装。

源码:https://github.com/git/git

安装方法:

$ tar -zxf git-xxx.tar.gz
$ cd git-xxx
$ make prefix=/usr/local all
$ sudo make prefix=/usr/local install
2、安装编译依赖
yum install zlib-devel openssl-devel perl hg cpio expat-devel gettext-devel curl curl-devel perl-ExtUtils-MakeMaker hg wget gcc gcc-c++ golang
3、域名解析

ngrok的客户端在连接时可以自己指定子域名前缀,所以需要将域名泛解析到外网服务器。
假设子域名形式为 aaa.dev.pocketdigi.com,需要在DNS设置中添加 dev.pocketdigi.com A记录到外网服务器,*.dev.pocketdigi.com cname到 dev.pocketdigi.com

4、安装ngrok
git clone https://github.com/inconshreveable/ngrok.git
cd ngrok

NGROK_DOMAIN="dev.pocketdigi.com"

openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt

cp base.pem assets/client/tls/ngrokroot.crt

make release-server

生成的服务端在ngrok/bin/ngrokd

5、生成客户端

客户端如果跟编译的机器系统不同,编译前需要加参数,比如编译mac使用的客户端

GOOS=darwin GOARCH=amd64 make release-client

生成的客户端在ngrok/bin/darwin_amd64/ngrok

6、启动服务端

假设ngrok目录在/usr/local/ngrok

/usr/local/ngrok/bin/ngrokd -tlsKey=/usr/local/ngrok/server.key -tlsCrt=/usr/local/ngrok/server.crt -domain="dev.pocketdigi.com" -httpAddr=":8888" -httpsAddr=":444" > /home/logs/ngrok.log &

监听http 8888端口,https 444端口,所有访问到外网服务器8888和444端口的数据都会经过ngrok处理转发。
可以把上面的命令加到/etc/rc.local,实现开机自启动

#####7、启动客户端
将第5步生成的客户端下载到mac电脑上,假设放到/Users/xxx/ngrok目录下.
在该目录新建一个配置文件 ngrok.cfg,内容如下:

server_addr: "dev.pocketdigi.com:4443"
trust_host_root_certs: false

假设我们要将aaa.dev.pocketdigi.com映射到本地的8080端口,新建aaa.dev.pocketdigi.com.sh(可以放到任意位置,文件名也只能方便识别),内容如下:

#!/bin/sh
cd ~/ngrok
nohup ./ngrok -subdomain aaa -config=ngrok.cfg 8080 &

加上执行权限:

chmod +x aaa.dev.pocketdigi.com.sh

在终端中执行 aaa.dev.pocketdigi.com.sh,看到Tunel Status online时,表示连接服务器成功,一台机器可以绑多个域名,重复上面的步骤即可.

现在,启动本地的tomcat,端口8080,可以通过http://aaa.dev.pocketdigi.com:8888访问。

但带着端口号始终不方便,我们可以在运行在服务器80端口上的nginx上做个转发,将 *.dev.pocketdigi.com 转发到 *.dev.pocketdigi.com:8888

添加vhost:

server {
    listen       80;
    server_name  *.dev.pocketdigi.com;
    location / {
             proxy_pass http://$host:8888;
             proxy_redirect off;
             client_max_body_size 10m;   
             client_body_buffer_size 128k; 
             proxy_connect_timeout 90; 
             proxy_read_timeout 90;
             proxy_buffer_size 4k;       
             proxy_buffers 6 128k;        
             proxy_busy_buffers_size 256k;
             proxy_temp_file_write_size 256k; 
        }
    location ~ .*\.(gif|jpg|png|bmp|swf)$ {
                 expires 30d;
        }
    location ~ .*\.(js|css)?$ {
                expires 1d;
    }
}

在http{}里,添加DNS:

resolver      8.8.8.8;

重启nginx,现在可以用http://aaa.dev.pocketdigi.com访问本地8080端口。

所有请求都会走服务器流量,如果服务器同时开着其他服务,速度可能会受影响

Ionic:Tab保持在底部

Ionic的官方tab应用模板,在不同的设备上,tab的显示位置可能是在顶部,也可能是在底部,不统一,需要通过$ionicConfigProvider 强制指定显示位置。

修改app.js 的config方法

.config(function($stateProvider, $urlRouterProvider,$ionicConfigProvider) {
        //tab强制在底部
        $ionicConfigProvider.tabs.position('bottom');
        xxxxxxxxxx 原来的其他配置

Android开发:Parcelable和Serializable序列化的区别

Android在Intent,Bundle传递数据时,都需要对象对应的类实现Parcelable或Serializable接口,即序列化。都是序列化,这两者有什么区别呢?
Serializable是Java定义的序列化接口,底层用反射实现,使用时io开销比较大,适用于将对象存储到文件或网络传输。
而Parcelable是Android定义的接口,使用起来相对复杂些,需要写一堆的方法(同是避免了使用反射),不过,在AndroidStudio里,可以用一个叫Android Parcelable code generator的小插件快速生成代码。
Parcelable的优势在于,效率高,在内存中传输时,建议使用Parcelable。有评测显示Parcelable性能比Serializable高十倍以上。