[转载]C# Winform 窗体传值 利用委托 子窗体传值给父窗体 - xiaocong_soft - 博客园

mikel阅读(1306)

[转载]C# Winform 窗体传值 利用委托 子窗体传值给父窗体 – xiaocong_soft – 博客园.

常用的Winform窗体传值有两种方式。

1.更改Form.designer.cs文件,将控件的设为Public,供子窗体访问。

在designer.cs文件的最后,找到你的控件声明。

private System.Windows.Forms.TextBox textBox1;

更改Private为public,保存即可。

2.利用委托进行窗体传值。

  父窗体:Form1

子窗体:Form2

点击Form1,弹出Form2,点击按钮返回值给Form1

  

首先在Form2中定义委托和事件:

复制代码
//声明委托 和 事件
public delegate void TransfDelegate(String value);
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

public event TransfDelegate TransfEvent;
private void button1_Click(object sender, EventArgs e)
{
//触发事件
TransfEvent(textBox1.Text);
this.Close();
}
}

然后在Form1中进行调用:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

}

private void button1_Click(object sender, EventArgs e)
{
Form2 frm = new Form2();
//注册事件
frm.TransfEvent += frm_TransfEvent;
frm.ShowDialog();
}

//事件处理方法
void frm_TransfEvent(string value)
{
textBox1.Text = value;
}
}

以上代码出自@WaitingEver

代码下载:WinformTransferValue.rar

[转载]人在囧途——Java程序猿学习Python - 左潇龙 - 博客园

mikel阅读(988)

[转载]人在囧途——Java程序猿学习Python – 左潇龙 – 博客园.

引言

 

  LZ之前其实一直对python都很好奇,只是苦于平时没有时间去了解它,因 此趁着51假期这个机会,便迫不及待的开始了自己的探索。作为一个标准的Java程序猿,在了解python的过程当中,LZ遇到了很多囧事,接下来LZ 就一一给大家说道说道。本文纯属看个乐子,非python教学。

 

囧事一:eclipse插件安装篇

 

  由于LZ习惯了使用eclipse进行开发,因此对python的研究,还是希望可以在eclipse上进行试验。那么第一件事,自然是安装python的eclipse插件,于是百度、google各种搜索引擎开始进入LZ的脑海。

  看着搜索结果,LZ不禁感叹大神们的厉害,原来如此简单,只需要像下面这样就可以。

  好吧,于是LZ开始等待…慢慢的,十分钟过去了,它依然没有反应…又十分钟过去了,是的,它依然没有反应,囧。

  半个小时之后,LZ终于忍不住了,开始选择第二种办法,离线安装。下载一个ZIP包,直接解压到eclipse的相应文件夹,于是pydev终于出现了。

  功夫不负有心人,它终于出现了,有点找到失散多年的妹妹的感觉。接下来的过程还比较顺利,下载python运行环境,将python.exe配置到相应的interpreter上面去即可。

  一个小时的折腾,LZ终于成功运行了那个具有划时代意义的程序,它叫“Hello World!”。

  

囧事二:变量声明篇

 

  既然“Hello World”已经成功,那么接下来的过程应该是非常顺利的。不过没想到的是,接下来发生的事,让LZ更加抓狂。最根本的原因就是LZ写下来了这样一个程序。

  它竟然报错了!大概意思LZ倒是看明白了,说字符串不能和int类型比较。于是LZ便将程序改成以下这样。

  它竟然编译错误了!最可悲的是,这提示有点牛B,LZ只能说“!&……#&!……@&#*……!@&#”。于是牛B的LZ想到了无敌的一招,就是下面这招。

  它竟然又报错了!强制类型转换都不好使了,这python还真不好搞。不过这时LZ灵机一动,看到了input()的用法,便比葫芦画瓢改了一下程序,结果它终于成功了。

  无敌的python果然与众不同,习惯与Java完全不同,当初研究C++的时候不能说是顺风顺水,但也算是手到擒来,因为两者在某种程度上还是比较相似的。不过这python就不一样了,很多用法都已经截然不同。

 

囧事三:连接mySQL数据库

 

  完成了上面那个高端大气上档次的程序,LZ开始向新的目标前进。于是不自然的想到了数据库,LZ平时自己写Java程序都是使用mySQL,于是也想使用python操作一下数据库。回想起Java操作mysql的方式,想必python也需要下载一个p包吧。

  不过结果有点出乎LZ的预料,竟然需要安装一个叫mysqldb的东西。这是什么玩意?于是LZ开始抱着必胜的决心寻找它,结果终于在某USA的网站找到了它,本来以为是个exe的安装包,结果却是一大堆文件,看起来还需要自己build的节奏,这让LZ有点慌了神。

  不要慌,下载下来再说。于是得到了下面这样一个东西。

  幸好这玩意看起来还不是那么的难理解,想必应该是需要使用python解释器去执行setup.py就可以。最后发现,还需要加入一个build的参数。于是LZ便开始执行命令,结果得到了下面的错误。

  其实在得到这个错误之前,LZ已经得到了很多错误,一开始是一个编译错误,LZ修改了setup_windows.py文件。后来又是一个导入module的错误,LZ又修改了setup_common.py文件。

  直到得到这个错误以后,无论LZ如何猜测,都没有解决这个问题。因为它报错的 地方是在python的lib文件当中,而不是mysqldb的文件。一时之间,LZ有点慌乱了,开始求助最后的大神google。不过结果依旧失败,往 往在最后关头能帮助LZ的google,今天看来也不好使了。

  这可怎么办?LZ猜测这种错误很可能是版本引起的问题,因为LZ隐约记得LZ下载的叫mysqldb2,可是LZ使用的是python3啊。会不会跟这个有关系呢?

  说干就干,在百度输入mysql python3的关键字,最终还是让LZ找到了一丝蛛丝马迹,有一个地址叫mysql for python3。这是一个github的地址,LZ果断用git将这个下载下来。仔细一看,它的目录与刚才那个mysqldb2如出一辙,但其实仔细看, 也有少许不同。

  不管它了,管它哪里不同呢,继续执行刚才的命令。果然,这一次虽然有很多警告,但是build成功了。接着就是install,不出所料,依然还是成功,这让LZ欣喜万分。浪费了差不多一个小时,终于搞定了。

  于是迫不及待的在eclipse当中输入一个标准的程序,来验证一下LZ的mysqldb是否安装成功了。可是没想到的是,它依然报错了。

  从错误提示上来看,就像Java一样,是刚才安装的mysqldb没有被成功引入。于是LZ想到了在配置python解释器的时候,有一大堆东西让LZ选择,而刚才install的时候,就是在Lib下面安装了一个mysqlXXX的东西。于是LZ就找到了这里。

  这下LZ的程序终于编译通过了,怀着激动的心情,LZ选择了python run,结果令人振奋。

  终于打印出来了,这里面的数据就是LZ自己的mysql数据库当中存储的。到此,LZ短期对python的研究就暂时告一段落了,以后如果有机会的话,LZ会用python写一些实用的小工具,以便于自己在平时的工作当中使用。

 

小结

 

  现在说来简单,其实在真正尝试的过程当中,还是遇到了不少问题。主要原因也和LZ想在windows下使用python有关系,这个时候就体现出linux的优势了,在linux上安装一些东西,总是那么的轻松。

  这本来只是一篇流水文,不过LZ在这里还是想说,很多猿友在研究一些东西的时 候,遇到困难就退缩了。到了最后,大部分人都会情不自禁的打开11GAME的快捷方式麻痹自己,从此无法自拔。但其实有的时候,只要你再往前一小步,就可 以看到胜利了。最重要的是,这可以锻炼你解决问题的能力,因为在工作当中,很多事情都是你克服了无数奇葩的问题才得以解决的。

版权声明

 


作者:zuoxiaolong(左潇龙)

出处:博客园左潇龙的技术博客–http://www.cnblogs.com/zuoxiaolong

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]VPS CentOS-6 下 LNMP HTTP服务器的搭建 - wid - 博客园

mikel阅读(804)

[转载]VPS CentOS-6 下 LNMP HTTP服务器的搭建 – wid – 博客园.

前言

恢复更新后的第一篇博文, 前段时间由于各种理由, 把博客更新给宕掉了, 个人独立博客的开发也搁浅了, 现在随着工作的逐步稳定, 决心把博客重新恢复更新, 继续记录着代码路上的一点一滴。

在上篇博文中提到, 笔者将把工作和学习环境全面转移到Linux, 转移也没什么目的, Windows用久了, 换个口味。目前使用的Linux环境是 Ubuntu 14.04, 该版本是一个长期支持版本, 对于想试一试Linux系统的朋友, 也是值得推荐的。

笔者于昨天新入手了一个 VPS, 来作为个人博客wid实验室(widlabs.com)开发的实验环境。所以在这篇博文中, 将介绍 CentOS 6 下 LNMP HTTP 环境的搭建, 从使用 ssh 登录VPS讲起, 一直到将域名解析到服务器IP上这一完整的网站搭建流程。

新入手的VPS基本配置如下:

  • 虚拟化技术: OpenVZ
  • 操作系统: CentOS-6 x86_64 Base
  • CPU: Intel(R) Xeon(R) CPU E3-1240 V2 @ 3.40GHz
  • 内存: 2GB
  • 硬盘: 50GB HDD

一、准备工作 ssh登录VPS、scp上传文件

ssh 登录VPS

在Linux下, 要远程管理一台服务器, 是非常轻松愉快的一件事, ssh 命令用来登录远程主机, 登录后进入shell命令行模式, 然后就像在终端操作自己机器的一样来操作远程主机就可以了, 唯一需要注意的就是不要把命令写错了, 如  rm -rf ./*  写成  rm -rf /* 。

ssh 命令的一般用法为:

ssh 用户名@主机名 -p 主机ssh服务端口号

ssh服务的默认端口号为22, 当使用默认端口时, -p选项是可选的, 第一次远程连接某个主机时, 会提示主机真实性不能确认, 询问是否继续(yes/no), 输入”yes”继续即可连接, 以登录 198.98.117.120 为例:

carey@E530:~$ ssh root@198.98.117.120 -p 22

scp 上传文件

scp命令可实现从本机与远程主机间文件的相互复制, 基于ssh服务, 一般用法如下:

从本机复制文件到远程主机:

scp -P ssh服务端口号 本地文件路径 目标主机用户名@主机名:存放路径

从远程主机复制文件到本地:

scp -P ssh服务端口号 目标主机用户名@主机名:文件路径 本机存放路径

 

以上传本次配置LNMP环境所需的文件为例, Nginx的源码包 nginx-1.6.0.tar.gz, PHP的源码包 php-5.5.10.tar.gz, 新起一个终端:

carey@E530:~/download$ scp -P 22 nginx-1.6.0.tar.gz root@198.98.117.120:/home/upload
carey@E530:~/download$ scp -P 22 php-5.5.10.tar.gz root@198.98.117.120:/home/upload

二、编译安装 Nginx、PHP

首先查看下VPS的CentOS系统中预装的一些软件包:

yum list installed

清理掉自带的Apache、PHP、MySQL服务(如果存在), 执行命令:

yum remove httpd mysql-server mysql php

检查是否已安装GCC, 执行命令 gcc -v 未安装则执行yum方式安装:

yum install gcc

安装GCC是必要的, 因为下面我们将对Nginx、PHP进行编译方式安装。

安装 Nginx

创建Nginx、PHP专用用户与用户组

[root@widlabs ~]# groupadd www
[root@widlabs ~]# useradd -s /sbin/nologin -M -g www nginx

安装Nginx所需依赖

[root@widlabs ~]# yum install pcre-devel
[root@widlabs ~]# yum install zlib-devel

进行nginx编译安装

[root@widlabs ~]# cd /home/upload #进入nginx源码包所在目录
[root@widlabs upload]# tar tar zxvf nginx-1.6.0.tar.gz
[root@widlabs upload]# cd nginx-1.6.0
[root@widlabs nginx-1.6.0]# ./configure --prefix=/usr/local/nginx --user=nginx --group=www --with-pcre
[root@widlabs nginx-1.6.0]# make
[root@widlabs nginx-1.6.0]# make install

安装 PHP

安装 PHP 所需依赖

[root@widlabs nginx-1.6.0]# cd /home/upload #进入php源码包所在目录
#若 wget 命令可用, 则执行:
[root@widlabs upload]# wget ftp://mcrypt.hellug.gr/pub/crypto/mcrypt/libmcrypt/libmcrypt-2.5.6.tar.gz
#注: wget不可用时, 可先 yum install wget, 或到 ftp://mcrypt.hellug.gr/pub/crypto/mcrypt/libmcrypt/ 下载libmcrypt-2.5.6.tar.gz源码包, 再使用scp命令上传到VPS;
[root@widlabs upload]# tar zvxf libmcrypt-2.5.6.tar.gz
[root@widlabs upload]# cd libmcrypt-2.5.6
[root@widlabs libmcrypt-2.5.6]# ./configure --prefix=/usr/local/libmcrypt
[root@widlabs libmcrypt-2.5.6]# make
[root@widlabs libmcrypt-2.5.6]# make install
[root@widlabs libmcrypt-2.5.6]# cd ..
[root@widlabs upload]# yum install libxml2-devel

进行PHP编译安装

[root@widlabs upload]# tar zxvf php-5.5.10.tar.gz
[root@widlabs upload]# cd php-5.5.10
[root@widlabs php-5.5.10]# ./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php --with-mysql --with-mysqli --with-pdo-mysql --enable-opcache --enable-mbstring --enable-mbregex --with-mcrypt=/usr/local/libmcrypt --with-mhash --enable-cgi --enable-fpm #可根据自身需要增加相应编译选项
[root@widlabs php-5.5.10]# make
[root@widlabs php-5.5.10]# make install
[root@widlabs php-5.5.10]# cp php.ini-development /usr/local/php/php.ini
[root@widlabs php-5.5.10]# cd /usr/local/php/etc
[root@widlabs etc]# cp php-fpm.conf.default php-fpm.conf

三、安装MySQL

MySQL直接通过yum方式安装即可:

[root@widlabs ~]# yum install mysql mysql-server mysql-devel

四、配置 Nginx、PHP
Nginx 的配置

[root@widlabs ~]# cd /usr/local/nginx/conf
[root@widlabs conf]# cp nginx.conf nginx.conf.bak
[root@widlabs conf]# vi nginx.conf

server {
listen 80;
server_name widlabs.com www.widlabs.com; #网站域名

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm index.php; #添加 index.php
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
# 去掉这几行的注释并小做修改
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 改为
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

PHP 的配置

[root@widlabs ~]# vi /usr/local/php/php.ini

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = PRC

五、测试配置是否正确

[root@widlabs ~]# cd /usr/local/nginx/html      #/usr/local/nginx/html 即为网站默认工作目录
[root@widlabs html]# echo '<!--?php echo phpinfo(); ?-->' &gt; test.php #新建 test.php 作为php执行测试
[root@widlabs html]# service mysqld start #启动MySQL服务
[root@widlabs html]# /usr/local/php/sbin/php-fpm #启动PHP FastCGI管理器
[root@widlabs html]# /usr/local/nginx/sbin/nginx #启动Nginx

通过浏览器访问主机IP, 以及 test.php, 判断Nginx是否已正常工作。

六、Nginx 网站服务的开机自启动

[root@widlabs ~]# vi /etc/rc.local

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local
#添加
/sbin/service mysqld start
/usr/local/php/sbin/php-fpm
/usr/local/nginx/sbin/nginx

重启VPS判断开机自启动是否正确:

[root@widlabs ~]# reboot now

七、解析域名到服务器IP

 

登录域名服务商提供的管理面板, 选择域名解析, 类型选择A记录, 记录值为指向的主机IP。

到这里, 整个LNMP的HTTP基础环境就算搭建完成了。

[转载]利用BusyBox ~私人定制 My LINUX~ - 姜名则 - 博客园

mikel阅读(868)

转载利用BusyBox ~私人定制 My LINUX~ – 姜名则 – 博客园.

前言
我在今天在这里跟大家详细地探讨一下Linux系统的定制过程和实现例如、用户能够远程登录;和Nginx能够稳定地运行在我们私人定制的LINUX系统上、一步一步从头开始定制属于我们自己的系统。
正文
首先我们先来简单的介绍一下我们这里定制属于自己的Linux系统的基本元素.

一个定制的linux内核+一个定制的busybox就可以定制一个小型的Linux操作系统了,安装Dropbear和Nginx,Linux的组成 部分包括内核空间和用户空间、而用户空间其实就是根文件系统、用户空间中又包括有shell和init,busybox他能够模拟数百个我们系统上带用的 命令、当然包括我们所需要用到的shell、init、getty、login、那定制一个Linux操作系统我们必须要了解的就是系统启动流程:
POST –> Boot Sequence(MBR) –> BootLoader –> kernel –>/sbin/init
POST:加电自检
Boot Sequence:读到磁盘中的第一个扇区(446)的MBR。
BootLoader:找到MBR中的BootLoader引导加载器、bootloader会找到我们选定的操作系统或内核去加载对应的内核、而这个Bootloader通常是grub。
kernel:加 载内核、内核要完成初始化、bootloader负责把内核读到内存中、内核又通常中压缩的、所以通常内核在内存中展开、而bootloader引导内核 启动起来、让内核从他的程序入口处开始执行、所以接下来内核要完成自身的初始化操作、或硬件探测、包括自身执行环境的准备等等都在这里完成。
内核完成初始化的第一步就要去装载用户空间了、有时候为了让内核做得足够小、很有可能内核中并不具备真正的根文件系统所在设备的驱动、因此我们要借助于 initramfs(CentOS5上被称为initrd)来完成去装载真正根文件系统所在的根文件系统真正的程序、但是这个initramfs可 initrf(initramfs:这是一个文件系统、CentOS6上就这么称呼了,initrd:ram disk 这是一个磁盘设备)是个虚拟的根文件系统、是个虚根、他不是我们系统真正工作起来所使用的根、所以称这个为虚根、内核借助于这个虚根装载驱动之后就要去挂 载真正的根设备了、kernel会自动挂载到这个跟文件下、内核会挂载真正的根到这个initramfs虚根的某个目录下、比如说挂载到/mnt /sysroot的目录下、而后再完成根切换、而后我们的真正的根就可以加载了、那我们的内核怎么知道挂载的根文件系统是什么呢、那我们的grub向内核 传递参数root等于什么那就是告诉系统根文件系统所在的设备。

/sbin/init:他通常加载四个设备、/sbin/init、/bin/bash、如果内核找不到就去找根下的init、再找不着就去找/bin /sh、/bin/bash、按照这个顺序去找、找到一个就可以启动系统、所以系统启动的第一个进程就启动了、init负责去启动用户空间中真正工作的进 程、init本身本身只是负责去生成这些正正工作的进程和回收这些进程的、是内核的第一个、最顶级的管理进程、但不负责具体的工作、虽然init不负责具 体工作、但他需要把一个用户空间启动为一个真正完整意义上的用户空间、所以init要结合他的配置文件inittab完成所谓系统初始化的。
在CentOS6上这个文件inittab之所以保留下来是为了跟CentOS5兼容的、其实我们用不着的、因为init大多数配置文件都位于/etc/init/*.conf目录下所有以.conf结尾的文件、是用于各子系统之间协调的。

/sbin/init作用包含以下几步:
1、设定默认运行级别:runlevel -v:查看运行级别
2、系统空间中的进一行初始化、这个要依赖于一个系统初始化脚本来完成的、这个脚本叫rc.sysinit、在有些系统上可能叫rcS、其实都是一个概 念、这个初始化包含键盘键映射、初始化没被挂载的文件系统等等。那/etc/rc.sysinit要进行哪些工作、seLinux、udev、键映射、交 换分区的激活、挂载额外文件系统、重新以读写方式挂载根文件系统等等。
3、启动指定级别下的服务、后台的守护进程、每个级别都有一个在/etcg下rcN.d的文件、启动这个文件中所有以S开头的脚本服务、关闭所有以K开 头的服务脚本、/etc/rc.local其中以S开头的最后一个服务S99包含rc.local、有些我们不便于自己写脚本定义的服务可以写到这里来、 但是启动之后不会关闭、所以只是执行一些命令我们可以在这里执行、服务还是建议使用服务脚本。
4、设定键映射
5、启动虚拟终端、启动这个终端会调用一个叫login的程序、在虚拟终端上打印一个登录提示符让我们输入
6、如果设备默认级别为5的话还可以启动图形终端
init在早期是个串型的init、或者说传统意义是的init、sysV风格的、这种程序启动任何服务、运行任何程序完成系统初始化时统统以串型模式进行的、所以速度非常慢、以至于后来有了并型运行的init、启动速度也比较快。

准备工作:
前面的系统裁减我们说过、为了可以让定制好的系统可以放到别的机器上动行、我们要把他做到一个独立的硬盘上去、所以首先我们在宿主机上添加一块SCSI 的硬盘、这些步骤参考前面的博文,总结之:CentOS 6.4系统裁减详解及装载网卡步骤http://tanxw.blog.51cto.com/4309543/1368801、这里不再详细说明、给出命 令、添加好后进行分区格式化操作。

# fdisk /dev/sdb
n p 1 +50M n p 2 +512M n p 3 +256M t 3 82 w
# mke2fs -t etx4 /dev/sdb1
# mke2fs -t etx4 /dev/sdb2
# mkswap /dev/sdb3
# mkdir -pv /mnt/{boot,sysroot}
# mount /dev/sdb1 /mnt/boot
# mount /dev/sdb2 /mnt/sysroot

第一步:编译内核或定制内核

1、获取内核程序包,可以到官网下载:https://www.kernel.org/、这里我们使用的内核版本是3.13.6的版本:
解压内核文件、这里我们以make allnoconfig来编译安装、自己选择要编译的功能(让确保系统上的编译环境)

# tar xf linux-3.13.6.tar.xz -C /usr/src/
# cd /usr/src
# ln -sv linux-3.13.6 linux
# cd linux
# make allnoconfig
# make menuconfig 把我们所需要的内容整合进来

我们把这些内容全部编译进内核、不编译成模块、*号表示编译进内核

[*]64-bit kernel :64位操作系统的内核
General setup --&gt; Local version --&gt; -MyLinux: 给内核定一个自己的版本
Processor type and features --&gt; Processor family --&gt; (X)Generic-x86-64:这里是CPU类型、这个是通用x86-64
[*]Symmetric multi-processing support:选择CPU支持多核心处理
[*]Enable loadable module support:选择内核动态模块加载
Bus options (PCI etc.) --&gt; [*]PCI suppor:支持PCI总线
Device Drivers --&gt; &lt;*&gt;SCSI device support --&gt;[*]SCSI disk support要支持SCSI硬盘
Device Drivers --&gt;Fusion MPT device support (NEW) --&gt; &lt;*&gt;Fusion MPT ScsiHost drivers for SPI、&lt;*&gt;Fusion MPT misc device (ioctl) driver、[*]Fusion MPT logging facility:支持对硬盘的驱动
File systems --&gt; &lt;*&gt;The Extended 4 (ext4) filesystem:支持文件系统
Executable file formats / Emulations --&gt; [*]Kernel support for ELF binaries、[*]Write ELF core dumps with partial segments (NEW)、 &lt;*&gt;Kernel support for scripts starting with #!:可执行文件的格式
Device Drivers --&gt;Input device support --&gt; [*]Keyboards -&gt; &lt;*&gt;AT keyboard (NEW) [*]Mice:支持输入输出设备、比如健盘
Device Drivers &gt; USB support &gt; &lt;*&gt;UHCI HCD (most Intel and VIA) support、&lt;*&gt;OHCI HCD (USB 1.1) support、&lt;*&gt;EHCI HCD (USB 2.0) support:USB设备驱动
Device Drivers &gt; Generic Driver Options &gt; [*] Maintain a devtmpfs filesystem to mount at /dev [*]Automount devtmpfs at /dev, after the kernel mounted the rootfs
Networking support &gt; Networking options[*] TCP/IP networking [*]IP:multicasting [*]IP: advanced router[*]IP: policy routing [*]IP: verbose route monitoring[*] IP: kernel level autoconfiguration[*]IP: DHCP support[*]IP: BOOTP support[*] IP: RARP support&lt;*&gt;IP: tunneling &lt;*&gt;Unix domain sockets&lt;*&gt; UNIX: socket monitoring interface:支持协议
Device Drivers &gt; Network device support &gt; Ethernet driver support[*]Intel devices (NEW)&lt;*&gt;Intel(R) PRO/1000 Gigabit Ethernet support&lt;*&gt;Intel(R) PRO/1000 PCI-Express Gigabit Ethernet support:只要Intel的、其他的他都去掉、这是选择网卡设备的驱动程序
[ ]Wireless:这个无线网、我们去掉去、用不着

OK、到这里保存退出、把配置文件复制一份出来、前面磁盘格式化而且都挂载好了、这里我们就把grub安装到boot下::

# cp .config /root/config-3.13.6-x86_64
# make bzImage
# cp arch/x86/boot/bzImage /mnt/boot/
# grub-install --root-directory=/mnt /dev/sdb

BusyBox 的诞生

BusyBox 最初是由 Bruce Perens 在 1996 年为 Debian GNU/Linux 安装盘编写的。其目标是在一张软盘上创建一个可引导的 GNU/Linux 系统,这可以用作安装盘和急救盘。一张软盘可以保存大约 1.4-1.7MB 的内容,因此这里没有多少空间留给 Linux 内核以及相关的用户应用程序使用。

BusyBox 揭露了这样一个事实:很多标准 Linux 工具都可以共享很多共同的元素。例如,很多基于文件的工具(比如 grep 和 find) 都需要在目录中搜索文件的代码。当这些工具被合并到一个可执行程序中时,它们就可以共享这些相同的元素,这样可以产生更小的可执行程序。实际 上,BusyBox 可以将大约 3.5MB 的工具包装成大约 200KB 大小。这就为可引导的磁盘和使用 Linux 的嵌入式设备提供了更多功能。我们可以对 2.4 和 2.6 版本的 Linux 内核使用 BusyBox。

BusyBox 编译选项

BusyBox 包括了几个编译选项,可以帮助为我们编译和调试正确的 BusyBox。

表 1. 为 BusyBox 提供的几个 make 选项
make 目标 说明
help 显示 make 选项的完整列表
defconfig 启用默认的(通用)配置
allnoconfig 禁用所有的应用程序(空配置)
allyesconfig 启用所有的应用程序(完整配置)
allbareconfig 启用所有的应用程序,但是不包括子特性
config 基于文本的配置工具
menuconfig N-curses(基于菜单的)配置工具
all 编译 BusyBox 二进制文件和文档(./docs)
busybox 编译 BusyBox 二进制文件
clean 清除源代码树
distclean 彻底清除源代码树
sizes 显示所启用的应用程序的文本/数据大小

在定义配置时,我们只需要输入 make 就可以真正编译 BusyBox 二进制文件。例如,要为所有的应用程序编译 BusyBox,我们可以执行下面的命令:

$ make allyesconfig
$ make

编译Busybox、让busybox提供一个sh程序、busybox可以模拟n种sh、可以模拟bash的特性、上面我们有介绍过他了。
到busybox官网下载busybox程序包:http://www.busybox.net、我们这里用busybox-1.22.1版本的、要先安装上glibc-static、这里我的附件提供有哦。
解压到当前目录下就可以了:

# yum -y install libmcrypt-devel
# rpm --install glibc-static-2.12-1.132.el6.x86_64.rpm
# tar xf busybox-1.22.1.tar.bz2
# cd busybox-1.22.1
# make menuconfig
# make
# make install

   make menuconfig时就选择这项就可以了、其他的都使用默认的吧:
Busybox Settings –>Build Options –>[*] Build BusyBox as a static binary (no shared libs):把busybox编译也静态二进制、不用共享库    wKiom1M-W1TzkK0pAAKOiImqTAM554.jpg

# cp -a _install/* /mnt/sysroot/
# cd /mnt/sysroot/
# mkdir -pv /etc/rc.d var/log root home lib lib64 dev proc sys boot mnt media tmp srv
给grub提供一个配置文件
# vim /mnt/boot/grub/grub.conf
default=0
timeout=5
title Mini Linux (3.13.6-MyLinux)
root (hd0,0)
kernel /bzImage ro root=/dev/sda2 init=/sbin/init

给小系统提供一个rc.sysinit、/mnt/sysroot/etc/rc.d/rc.sysinit、如果没有这个目录就自己创建、再提供一个/mnt/sysroot/etc/fstab文件

# vim /mnt/sysroot/etc/fstab
/dev/sda1 /boot ext4 defaults 0 0
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
/dev/sda2 / ext4 defaults 0 0
/dev/sda3 swap swap defaults 0 0

# mkdir -p /mnt/sysroot/etc/rc.d/rc.sysinit

#!/bin/sh
#
打印系统启动时的欢迎信息
echo -e "\tWecome to \033[34mMini\033[0m Linux"
判断/etc/sysroot/network是否存在并且可读、条件成立就source进来
[ -r /etc/sysconfig/network ] &amp;&amp; . /etc/sysconfig/network
判断$HOSTNAME是否为空并且$HOSTNAME==NONE、如果为空就给个默认值
[ -z "$HOSTNAME" -o "$HOSTNAME" == "(none)" ] &amp;&amp; HOSTNAME=localhost
给HOSTNAME赋值为定义好的HOSTNAME的值
/bin/hostname $HOSTNAME
挂载文件系统
echo "mounting proc filesystem..."
mount -t proc proc /proc
echo "mounting sysfs filesystem..."
mount -t sysfs sysfs /sys
自动探测各硬件设备、并且能够探测到的都装载设备文件
mdev -s
给系统配置一个IP地址
ifconfig lo 127.0.0.1
ifconfig eth0 172.16.254.188
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts

# vim /mnt/sysroot/etc/inittab

::sysinit:/etc/rc.d/rc.sysinit 明确指定要找/etc/rc.d/rc.sysinit文件
::respawn:/sbin/getty 19200 tty1
::respawn:/sbin/getty 19200 tty2
::respawn:/sbin/getty 19200 tty3
::respawn:/sbin/getty 19200 tty4
::respawn:/sbin/getty 19200 tty5
::respawn:/sbin/getty 19200 tty6 :19200表示每秒钟传输的速率、getty是一个串型终端、他会调用一个登录界面让用户输入登录信息进行验证
tty6::askfirst:/bin/sh 启用6个虚拟终端
console::respawn:-/bin/sh 启用一个终端,如果出错重新启动respawn、-表示敲一回车才可以启用sh
::ctrlaltdel:/sbin/reboot 定义组合键、按ctrl+alt+del就reboot
::shutdown:/bin/umount -a -r 如果要要关机就卸载已挂载的设备、然后关机

提供系统用户登录帐号文件:

# useradd busybox
# passwd busybox
密码也设置为:busybox
# head -1 /etc/passwd &gt; /mnt/sysroot/etc/passwd
# tail -1 /etc/passwd &gt;&gt; /mnt/sysroot/etc/passwd
# vim /mnt/sysroot/etc/passwd
root:x:0:0:root:/root:/bin/sh
busybox:x:502:503::/home/busybox:/bin/sh
# head -1 /etc/group &gt; /mnt/sysroot/etc/group
# tail -1 /etc/group &gt;&gt; /mnt/sysroot/etc/group
# head -1 /etc/shadow &gt; /mnt/sysroot/etc/shadow
# tail -1 /etc/shadow &gt;&gt; /mnt/sysroot/etc/shadow
# cat /mnt/sysroot/etc/shadow
提供一个登录欢迎信息、创建一个issue文件
# vim /mnt/sysroot/etc/issue
MyLinux is CentOS release 6.5
Kernel \r on an \m
提供主机名
# mkdir /mnt/sysroot/etc/sysconfig
# vim /mnt/sysroot/etc/sysconfig/network
HOSTNAME=mylinux.tanxw.com
提供命令提示符和环境变量
# vim /mnt/sysroot/etc/profile
export PS1='[\u@\h \A \W]\$ '
PATH="/usr/local/sbin:/usr/local/bin:/sbin/:/bin:/usr/sbin:/usr/bin:$PATH"
# chmod +x /mnt/sysroot/etc/rc.d/rc.sysinit
# sync 同步数据到磁盘中去

到这里我们的配置算是有个大概了、那就把宿主机挂起、创建一个自定义虚拟机、把我们制作有系统的那张硬盘放到自定义的虚拟机上运行:
wKioL1M-XobC4l3eAAUFyd8qnoU936.jpg

如果第一个终端登录不了可以切换到其他终端上登录,按ctrl+alt+F2(F3\F4\F5\F6)可以切换到不同的终端上去登录的、这里我们登录成功了:

wKioL1M-XuSQxTjpAAG9drsjG4Q282.jpg

wKiom1M-Xw6BgVnIAAN2Mt2ZE6I490.jpg
=================================远程登录和ngix访问==============================

OK、到这里我们自己定制的Linux可以跑起来了、接下来我们来实现远程登录和Nginx的访问:
切换到宿主主上、下载dropbear-2013.58.tar.bz2和Nginx到本地:

1、解压安装dropbear

# tar xf dropbear-2013.58.tar.bz2
# cd dropbear-2013.58
# ./configure
# make
# make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" install
# mkdir /etc/dropbear
生成两个密钥文件
# dropbearkey -t rsa -s 1024 -f /etc/dropbear/dropbear_rsa_host_key
# dropbearkey -t dss -f /etc/dropbear/dropbear_dss_host_key
验证一下生的这两个文件
# ls /etc/dropbear

2、服务脚本/etc/rc.d/init.d/dropbear

#!/bin/bash
#
# description: dropbear ssh daemon
# chkconfig: 2345 66 33
#
dsskey=/etc/dropbear/dropbear_dss_host_key
rsakey=/etc/dropbear/dropbear_rsa_host_key
lockfile=/var/lock/subsys/dropbear
pidfile=/var/run/dropbear.pid
dropbear=/usr/local/sbin/dropbear
dropbearkey=/usr/local/bin/dropbearkey
[ -r /etc/rc.d/init.d/functions ] &amp;&amp; . /etc/rc.d/init.d/functions
[ -r /etc/sysconfig/dropbear ] &amp;&amp; . /etc/sysconfig/dropbear
keysize=${keysize:-1024}
port=${port:-22}
gendsskey() {
[ -d /etc/dropbear ] || mkdir /etc/dropbear
echo -n "Starting generate the dss key: "
$dropbearkey -t dss -f $dsskey &amp;&gt; /dev/null
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
success
echo
return 0
else
failure
echo
return 1
fi
}
genrsakey() {
[ -d /etc/dropbear ] || mkdir /etc/dropbear
echo -n "Starting generate the rsa key: "
$dropbearkey -t rsa -s $keysize -f $rsakey &amp;&gt; /dev/null
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
success
echo
return 0
else
failure
echo
return 1
fi
}
start() {
[ -e $dsskey ] || gendsskey
[ -e $rsakey ] || genrsakey
if [ -e $lockfile ]; then
echo -n "dropbear daemon is already running: "
success
echo
exit 0
fi
echo -n "Starting dropbear: "
daemon --pidfile="$pidfile" $dropbear -p $port -d $dsskey -r $rsakey
RETVAL=$?
echo
if [ $RETVAL -eq 0 ]; then
touch $lockfile
return 0
else
rm -f $lockfile $pidfile
return 1
fi
}
stop() {
if [ ! -e $lockfile ]; then
echo -n "dropbear service is stopped: "
success
echo
exit 1
fi
echo -n "Stopping dropbear daemon: "
killproc dropbear
RETVAL=$?
echo
if [ $RETVAL -eq 0 ]; then
rm -f $lockfile $pidfile
return 0
else
return 1
fi
}
status() {
if [ -e $lockfile ]; then
echo "dropbear is running..."
else
echo "dropbear is stopped..."
fi
}
usage() {
echo "Usage: dropbear {start|stop|restart|status|gendsskey|genrsakey}"
}
case $1 in
start)
start ;;
stop)
stop ;;
restart)
stop
start
;;
status)
status
;;
gendsskey)
gendsskey
;;
genrsakey)
genrsakey
;;
*)
usage
;;
esac

3、脚本配置文件/etc/sysconfig/dropbear

keysize=2048
port=22022
# chmod +x /etc/rc.d/init.d/dropbear
# chkconfig --add dropbear
# vim /etc/profile.d/dropbear.sh
export PATH=/usr/local/sbin:$PATH
启动服务并查看端口22022是否已经处于监听状态、并在本地尝试连接登录:
# service dropbear start
# ss -tnl

OK了、dropbear在本机安装成功并且成功启动连接上去了、

wKiom1M-Ynyga0ifAAGFaoOo9hQ896.jpg

那我们就移植到我们的定制系统上去吧、之前我们写过一个脚本用来移植命令的:

#!/bin/bash
#
dirPath=/mnt/sysroot
clearcmd(){
if which $1 &amp;&gt; /dev/null;then
cmdPath=`which --skip-alias $1`
else
echo "Command not exist!"
return 4
fi
}
cpCmd(){
dirName=`dirname $1`
[ -d ${dirPath}${dirName} ] || mkdir -p ${dirPath}${dirName}
[ -f ${dirPath}${cmdPath} ] || cp $1 ${dirPath}${dirName}
}
cpLib(){
for i in `ldd $1 | grep -o "/[^[:space:]]\{1,\}"`;do
dirLib=`dirname $i`
[ -d ${dirPath}${dirLib} ] || mkdir -p ${dirPath}${dirLib}
[ -f ${dirPath}$i ] || cp $i ${dirPath}${dirLib}
done
}
while true;do
read -p "Enter a command:" cmd
if [ "$cmd" == 'quit' ];then
echo "quit!"
exit 0
fi
clearcmd $cmd
[ $? -eq 4 ] &amp;&amp; continue
cpCmd $cmdPath
cpLib $cmdPath
done

  把这三个命令移植过去就可以了:dropbear、dropbearkey、scp、dbclient、bash、复制完后就去创建这个目录/mnt/sysroot/etc/dropbear、而后为移植过去的dropbear生成两个密钥文件:

wKioL1M-Yoei_oTqAABV1dSQn-c398.jpg

# mkdir /mnt/sysroot/etc/dropbear
# dropbearkey -t rsa -s 1024 -f /mnt/sysroot/etc/dropbear/dropbear_rsa_host_key
# dropbearkey -t dss -f /mnt/sysroot/etc/dropbear/dropbear_dss_host_key

dropbear要认证用户、而认证用户要用到名称解析、这就意味着libnss库要复制过去、libnss库框架、而后给nss提供配置文件:

# cp -d /lib64/libnss_files* /mnt/sysroot/lib64
# cp -d /usr/lib64/libnss3.so /mnt/sysroot/usr/lib64/
# cp -d /usr/lib64/libnss_files.so /mnt/sysroot/usr/lib64/
# cp /etc/nsswitch.conf /mnt/sysroot/etc/
# vim /mnt/sysroot/etc/nsswitch.conf
只保留以下几项、其他的都不需要:
passwd: files
shadow: files
group: files
hosts: files dns

在用户登录时dropbear认为用户的默认shell并不在/etc/shells下所在的用户shell中的话、他是不允许登录的、那我们就得给dropbear提供一个安全shell的配置文件了:

# vim /etc/shells
/bin/sh
/bin/hush
/bin/ash
/sbin/nologin
/bin/bash

要知道、dropbear启动时在/var/run/下会生成一个pid文件、而这个目录我们还没有创建、
# mkdir /mnt/sysroot/var/run

到这里我们定制的系统还不能远程登录、当你远程登录时、所登录的终端都是远程/dev/pts的伪终端、这个伪终端是个伪文件系统、只要你的内核编译时支持这个文件系统、他就可以使用、当然、我们的目标机上还没有:

# vim /mnt/sysroot/etc/fstab
加一行
devpts /dev/pts devpts defaults 0 0

再创建/dev/pts这个目录:
# mkdri /mnt/sysroot/etc/profile.d
# cp /etc/profile.d/dropbear.sh /mnt/sysroot/etc/profile.d/
# mkdir /mnt/sysroot/dev/pts
# sync

   OK、我们先来测试一下dropbear远程登录看可不可以登录得上去、把宿主机挂起或关机、启动我们的目标机、由于我们的/dev/pts启动目标机 时可以会重新挂载的问题、会把/dev/pts下的目录给覆盖掉、所以我们进入目标机后可以在/dev/下创建pts这个目录、IP要设置在同一个网段 内:
# mkdir /dev/pts
# mount -a

wKiom1M-YvuzKyfOAAHAlcw4eZo106.jpg

wKioL1M-ZErxsvFvAAMwMg8Cg6w580.jpg

实现页面Nginx访问
这里我们用的版本是Nginx-1.4.2、这里我们以最简单的方式进行安装并运行服务起来:
解决依赖关系:

# yum -y install pcre-devel
# tar xf nginx-1.4.2.tar.bz
# cd nginx-1.4.2
# ./configure --prefix=/usr/local --conf-path=/etc/nginx/nginx.cnf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --user=nginx --group=nginx --without-pcre --without-http_rewrite_module --without-http_geo_module --without-http_uwsgi_module --without-http_fastcgi_module --without-http_scgi_module --without-http_memcached_module
--prefix=/usr/local:指定第三方软件安装目录
--conf-path=/etc/nginx/nginx.cnf:指定主配置文件的路径
--error-log-path=/var/log/nginx/error.log:指定错误日志存放路径
--http-log-path=/var/log/nginx/access.log:指定访问日志
--group=nginx:以nginx用户身份运行、反正不要以管理员的身份去运行
--group=nginx:nginx用户组
其他的选项都是nginx默认选项、我们都去掉
# make
# make install
# useradd nginx
# cd /usr/local/sbin
# ./nginx
# ss -tnl 查看80端口是否已经处于监听状态

wKioL1M-Zs2SLNHIAAGnVyXisjY011.jpg

 

 

接着移植nginx到目标机上去、用上面的那个复制命令的脚本:

# ./cpCommad.sh
Enter a command:nginx
Enter a command:consoletype
Enter a command:quit
quit!

复制nginx下的配置文件到目标机上、而启动nginx需要nginx用户、所以用户也得追加到passwd文件中去、而后再给nginx添加一个测试页面就OK了:

# cp /etc/nginx/ /mnt/sysroot/etc/ -r
# grep "^nginx" /etc/passwd &gt;&gt; /mnt/sysroot/etc/passwd
# grep "^nginx" /etc/group &gt;&gt; /mnt/sysroot/etc/group
# grep "^nginx" /etc/shadow &gt;&gt; /mnt/sysroot/etc/shadow
# mkdir /mnt/sysroot/usr/local/html
# vim /usr/loca/html/index.html
<h2>Wecome to Nginx</h2>
<h3>This is MyLinux</h3>

给nginx提供一个服务脚本
# vim /mnt/sysroot/etc/rc.d/init.d/nginx

#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig: - 85 15
# description: Nginx is an HTTP(S) server, HTTP(S) reverse \
# proxy and IMAP/POP3 proxy server
# processname: nginx
# config: /etc/nginx/nginx.conf
# config: /etc/sysconfig/nginx
# pidfile: /var/run/nginx.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] &amp;&amp; exit 0
nginx="/usr/local/sbin/nginx"
prog=$(basename $nginx)
NGINX_CONF_FILE="/etc/nginx/nginx.conf"
[ -f /etc/sysconfig/nginx ] &amp;&amp; . /etc/sysconfig/nginx
lockfile=/var/lock/subsys/nginx
make_dirs() {
# make required directories
user=`nginx -V 2&gt;&amp;1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
options=`$nginx -V 2&gt;&amp;1 | grep 'configure arguments:'`
for opt in $options; do
if [ `echo $opt | grep '.*-temp-path'` ]; then
value=`echo $opt | cut -d "=" -f 2`
if [ ! -d "$value" ]; then
# echo "creating" $value
mkdir -p $value &amp;&amp; chown -R $user $value
fi
fi
done
}
start() {
[ -x $nginx ] || exit 5
[ -f $NGINX_CONF_FILE ] || exit 6
make_dirs
echo -n $"Starting $prog: "
daemon $nginx -c $NGINX_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] &amp;&amp; touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] &amp;&amp; rm -f $lockfile
return $retval
}
restart() {
configtest || return $?
stop
sleep 1
start
}
reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $nginx -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
configtest() {
$nginx -t -c $NGINX_CONF_FILE
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status &gt;/dev/null 2&gt;&amp;1
}
case "$1" in
start)
rh_status_q &amp;&amp; exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
exit 2
esac

而在nginx启动时需要依赖于日志目录和pid文件目录、所以我们要事先给nginx创建这两目录先、还给创建锁文件:

# mkdir /mnt/sysroot/var/log/nginx
# mkdir /mnt/sysroot/usr/local/logs
# mkdir -pv /mnt/sysroot/var/lock/subsys
# chmod +x /mnt/sysroot/var/log/nginx
# chmod +x /mnt/sysroot/tmp/
# sync

   OK、我们启动定制的目标系统、再启动dropbear和nginx、而服务也可以正常启动了、测试我们的nginx吧、如果服务脚本使用不了那就是里 面可以有一些依赖函数或一些命令没有全都移植到目标机上、不过不要紧、我们可以手动启动服务器、这个问题都不大、最后测试完没什么就poweroff关机 吧、如果nginx启动不了就重新把文件系统挂载为可读写就OK了:# mount -o remonut,rw /wKioL1M-ZxKDrj4yAAQWzC3HPbg791.jpg
wKioL1M-ZzjzbRmCAADsps5dhXY318.jpg

结束:
到此我们的定制过程就全都完成了、后续如果我们还需要添加什么功能的话可以自己慢慢添加上去的哦、如果我们有个树梅派的话可以把我们的小系统放到树梅派 上去跑起来的哦、那也成了一以linux电脑了、是不是很爽呀、好吧、就这样吧、如果大神有什么指出的话欢迎留言交流、谢谢关注了!

【安卓教程】(超详细)手把手教!替换APK程序图标+修改APK程序名称+所需工具!

mikel阅读(2276)

.

    前言我就不废话了。直接正题↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

首先:
1,Android Res Edit-电脑端(绿色版,解压后直接使用,注意,使用此工具前,电脑上要安装Microsoft .n et Framework V2.0.exe)
Android Res Edit v1.5.rar (1.17 MB, 下载次数: 138)
2,APK签名器-手机端安卓使用(这个是中文破解的)
APK编辑器xx中文版.zip (400.56 KB, 下载次数: 102)

3.APK签名器-手机端安卓使用(这个是英文原版的,我就是用这个为例子)
APK编辑器 V1.50.zip (167.81 KB, 下载次数: 55)
在电脑上,用“360手机助手”、豌豆夹等这些工具打开,会看到APK程序的图标和名称,这样就不必在手机安装才能看到了!

操作开始,先教大家替换APK程序图标!
1,既然是替换图标,当然需要先准备图标了,图标的文件格式是.png(也就是背景透明的图片格式),我以“手电筒”工具为例吧!
先准备一张分辨率不小于50×50大小的.png格式图片,为什么是不小于50×50,基于安卓手机的分辨率大小不等,这个数值应该是最小的,个人建议要大于这个数值,以免在高分辨率手机中,图标会被放大模糊!
制作png图表的工具最有名的当然是PS,但它太专业,很多人难得上手,我推荐一个非专业的:Adobe Fireworks CS3
至于这个东东怎么用,这里就不罗嗦了,自己研究吧,网上教程多得是啊!

2,将制作好的.png图片改名为你要替换的图片名称,一般是“icon.png”,也有不同的(MboPlayer播放器的就是moboplayer.png),到底是什么名称,到要替换的时候再看也来得及!

3,图标准备好后,现在准备开始替换;在电脑上,用WinRARA工具打开APK文件,只是打开,不是解压

打开后就是这个界面,跟打开普通的rar和zip压缩包一样的,apk文件似乎也是一种压缩包,现在不探讨这个!


4,APK程序的图标文件,在res\***文件夹中,如果你不知道它到底在哪,可以在上面的窗口中,直接把res文件夹拖拽解压出来单独看!此窗口先别关,等会替换图标要用的!

打开res文件夹后,用“略缩图”模式查看,有图标的文件夹就会显现图 片,打开这些含图标的文件夹,看看APK程序图标的文件名称是什么,然后把准备替换的图标改成相同名字!我们看到,不止一个文件夹有程序图标,这里至少有 3个程序图标,有的是负责安装时显示,有的是负责在菜单中显示…….建议全部换到,你不想同一个程序的图标,在不同地方显示不一样吧?!记住这些 含程序图标的文件夹,后面按照路径替换图标!

5,回到RAR窗口中,打开图标所在的文件夹,将新图标从电脑上用鼠标直接 拖拽到RAR窗口中覆盖原图标,这时,会弹出对话框,因为是往压缩包中添加文件,会询问你的“压缩方式”,我试过三种压缩方式:储存(不压缩保存)、标准 (压缩率一般)和最好(压缩率最高),不管哪种压缩方式,最后都能签名并安装使用,不过为了保险起见,我建议选择“储存”,即不压缩保存进去!然后“确 定”,就可以关闭WINRAR窗口了!

6,最后一步,签名!论坛中有很多签名的教程,大致意思是,将要签名的APK程序用WINRAR打开,将META-INF文件夹中的“MANIFEST.MF”文件保留,其他的两个删除,然后再签名!
不过呢,可能是我当初看这个教程时,或许没看到这一步,又或许是忘了,根本什么都没删除,直接就签名、安装使用了,并没有发现什么不对的地方!
仁者见仁智者见智,你如果想删除其他两个文件后再签名,我也不拦着你,反正不管怎么做,结果似乎都一样!
将替换图标的APK程序复制到手机中,打开“APK签名器”,开始签名!
看图操作,就三步:
a,点击“Choose In/Out…”(签名程序位置和签名后保存位置);
b,找到需要签名的APK程序,选择它;
c,点击“Sign The File”(签名此文件)

当窗口中出现这样的提示时,说明签名成功(一共会有两条提示)

打开手机文件管理,找到签名后的APK程序,会看到文件名为“***-signed.apk”(已签名的apk程序),现在可以删除那个没签名的APK程序,安装新程序使用吧!

修改APK程序名称需要用到“Android Res Edit ”工具(附件在一楼),这里同样以“手电筒.apk”程序为例!

7,在电脑上,用WINRAR工具打开“手电筒.apk”,将根目录中的“resources.arsc”文件拖拽到电脑中保存!此窗口不要关闭,等会还要用的!

8,打开“Android Res Edit v1.5”,点击:文件-打开文件,找到“resources.arsc”文件导入!

9,如果你不知道APK程序安装后的文件名是什么,可以用360手机助手或豌豆夹等工具先打开查看一下,方法参考一楼“查看APK程序图标”!
这里,我已经知道这个手电筒工具的名称为“手电筒”,如果我想改成“电筒”,就用鼠标双击原名称,在弹出的窗口中,写上新名称,然后点“OK”!

10,点窗口右下角的“保存文件”,会弹出保存文件的储存路径,这时,直接可以覆盖原文件保存就行了!

11,回到WINRAR窗口,将新保存的“resources.arsc”文件,从电脑中拖拽到“resources.arsc”文件所在的WINRAR窗口中,覆盖原文件,在弹出的对话框中,“压缩方式”的

选择,参考一楼的“压缩方式”!然后确定,关闭WINRAR窗口!


12,最后,将修改过名称的APK程序,复制到手机中,用“APK签名器”签名

特别提醒:在第三步更改程序名称时,只需要改动原程序中通过360手机助手或豌豆夹等工具能看到的程序名,也就是只改那一个就行了,千万别把看起来差不多的其他名称也改了,那样做的后果很严重,虽然可以正常签名、正常安装,但,无法运行!
刚才我就犯了这样的错误,在对“Angry Birds”(愤怒的小鸟)改名时,不但更改了显示名称“Angry Birds”,也将另一个名为“Angrybirds”(中间无空格)改了…….等到最后安装完打算运行游戏时,才发现根本打不开游戏!
切记、切记!!!

Android 下 APK 捆绑器的实现

mikel阅读(1555)

.

Android 下 APK 捆绑器的实现
                   作者: 海东青
利用捆绑器向正常程序捆绑病毒、木马等恶意程序,以达到隐蔽安装、运行的目的,这
在 Windows 平台下是一种很常规的攻击手段。那么,在智能终端十分流行的今天,如何实
现针对手机应用的捆绑器呢?对此,本文针对 Android 平台的应用程序 APK 文件,给出了
类似 Windows 下捆绑器的实现方案。
原理与基础
对任意的两个 APK 应用程序 A 和 B 进行捆绑,并且在手机上安装、运行捆绑生成的
APK 程序 C 后,仍然具备和 A 一样的运行效果,要实现这一目的,捆绑过程可以从两个思
路去实现。
1)对 Android 应用程序 A 进行反编译,通过修改反编译生成的 smali 代码,控制执行
流程,使其具备安装和执行应用程序 B 的功能,最后再打包生成捆绑后的应用 C。此方法
和早期的 Windows 环境下的 PE 病毒类似,插入额外的功能代码,修改入口点改变程序执行
流程,最后再回到原有流程执行 A 的功能。
2)考虑到 A、B 的任意性,以及生成程序 C 的稳定性,在这里重点介绍另外一种通过
宿主程序实现释放、安装、运行 A 和 B 的方法。其思路亦来自 Windows 下的捆绑器,即专
门写一个 host 程序作为宿主,其中应用程序 A、B 作为 host 的资源文件,运行 host 时可以
释放、安装和运行 A 和 B 的功能。此外,若考虑到安全性、免杀性,可对 A 和 B 进行加密、
编码。

                 因此,根据方法 2,可得出 PC端和Android手机端的软件工作流程如图 1和图 2所示

               

 

功能实现
通过上面的介绍,可以知道总共需要实现两个程序,即作为宿主程序的 Android 应用
host.apk,以及作为捆绑器的 Windows 应用程序“捆绑器”,下面将详细介绍这两个程序内
部原理和实现方法。
1.宿主应用程序
1)工作流程
对于宿主程序 host.apk,要实现上文所定义的功能需求,则其内部的工作流程可设计如
成如图 3 所示的结构。在宿主程序初始化时,会调用MainActity 的 onCreate 函数。在 onCreate
函数中,通常用来初始化 MainActivity,但是考虑到最终呈现给用户的界面(即 Activity)
为 A 的界面,所以宿主程序的 onCreate 函数中无需处理自身界面,只需想办法启动 A 的
Activity 即可。
                                                   
                             释放和安装指定的应用程序通常需要较长时间(与应用程序的大小相关),若所有流程
均在 onCreate 函数流程中实现,则会导致宿主程序界面卡顿或假死,故可通过在 onCreate
函数中创建子线程的方式避免该问题,在子线程中实现相关功能,其中主要有释放和安装应
用程序,以及启动主应用程序这两部分。
2)释放和安装应用程序
①释放指定文件
对于 Android 应用程序,需要捆绑的资源文件可以直接放置到 assets 目录下。在宿主程
序中,共计需要释放三个文件,即配置文件 config、主程序 update.res 和捆绑进去的程序
data.res,释放方式如下。当程序安装后,需要释放时可通过如下方式实现。
//dstFilePath 为释放出来的文件路径,resFileName 为需要释放的文件路径
private void dropFile(String dstFilePath, StringresFilename){
if(isFileExist(dstFilePath)) return;
InputStream inputFS;
try { inputFS = this.getResources().getAssets().open(resFilename);
} catch (IOException e1) {return; }
File outFile = new File(dstFilePath);

 

byte buf[] = new byte[1024];   int len;

 

try {
OutputStream outputFS = new FileOutputStream(outFile);

 

while((len = inputFS.read(buf))>0) {  outputFS.write(buf,0,len);}

 

outputFS.close();  inputFS.close();

 

} catch (IOException e) { return ; }

 

return;
}
②安装应用程序
文件释放后,接下来就要根据需要进行安装程序。对用户而言,已经安装程序了,故接
下来安装程序需要在后台完成,即不引起用户察觉,这就需要使用 shell 下的 pm 命令(Package
Manage),而 pm 命令需要 system 权限,故执行此操作时需要确保可以获取 root 权限。安装
一个指定程序可通过“pm installtarget_apk”实现。而执行 shell 命令利用 runtime.exec 即可
实现,可将其简单封装为 runCommand(String str)。
private void runCommand(String strCommand){
Runtime runtime = Runtime.getRuntime();
try { runtime.exec(strCommand);

 

} catch (IOException e) {e.printStackTrace(); }

 

}

 

综上,释放、安装的功能实现代码如下。
//Install the specified apk
public String installAPK(String apkFileName)


{


//获取 assets 中文件安装后的实际路径


String tmpDir =this.getApplicationContext().getFilesDir()+”/”;
String apk_path = tmpDir+apkFileName;
//释放文件
dropFile(apk_path, apkFileName);
//安装的 apk 文件需要读写权限,故赋予读写权限
runCommand(“su -c chmod 666 “+apk_path);
//使用 pm 安装文件
runCommand(“su -c pm install -t “+apk_path);
return apk_path;
}
3)启动应用程序
启动应用程序可通过切换 Activity 实现,即调用主程序 A 的 MainActivity 就可实现启动
A 程序并切换界面为 A,该过程可通过如下几步实现。
①获取需要启动的 apk 的信息
具体包括 apk 的packagename 和MainActivity 名。对于宿主程序而言,由于捆绑器已成
功获取并将此信息写入了 config 文件,故只需要从 config 中分别读取这两个字符串即可。
②新建 intent
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
③切换 Activity,启动程序
ComponentName cn = new ComponentName(main_app,main_app_Acitivty);
intent.setComponent(cn);
startActivity(intent);
2.捆绑器程序
运行在 Windows 下的捆绑器程序主要是为宿主程序做准备:获取主程序 A 的
packagename 和MainActivity 信息,获取捆绑进去的程序 B 的 packagename;修改宿主程序
的图标,使其和程序 A 的一致;将准备好的宿主程序打包、签名等。下面将详述每个部分。
1)提取信息
需要提取的信息为应用程序的 Packagename 和 MainActivity 名,这些信息可以从 manifest
中解析,但更简单的办法就是使用现有工具 apktool。该工具可以对 apk 程序进行解包、打
包、提取信息等。具体的,提取信息仅需命令“aapt.exedump badging apk_name”即可。为
了方便应用,我们可以写一个专门提取信息的批处理脚本 aapt_apkinfo.bat,具体如下:
set PATH=%CD%;%PATH%;
“%~dp0\aapt.exe” dump badging %1 > %2
该批处理以“aapt_apkinfo.bata.apk info.txt”的方式调用,其中第一个参数为 apk 路径,
第二个参数为信息存储文件。在程序中则可以如下调用。
ShellExecute(NULL, TEXT(“open”),szAaptCommand,szParameter, NULL, SW_HIDE);
其中,szAaptCommand 指向aapt_apkinfo.bat,szParameter 为参数 1 和参数 2 构成的字
符串,如“a.apkinfo.txt”。
由于此时获取到的信息如图 4 所示,内容太过冗余,我们需要从中解析出packagename
和 MainActivity。易知,此时得到的信息是按行存储的,即 info.txt 中的每行为一类或一条信
息 , 故 解 析 时 逐 行 判 断 即 可 。 其 中packagename 通 常 以 此 种 格 式 出 现 “ package:
name=’com.tencent.mobileqq’ ”, 可 以 认 为 package: name 之 后 的 单 引 号 内 的 内 容 为
packagename。mainactivity 的信息如图 5 所示,故同样易得其信息。相关文本搜索、字符串
判断等相关代码不再罗列。
                                                  
图 4 利用 apt 从 apk 中提取的原始信息
                                                           
图 5 info.txt 中 mainactivity 的格式
最后,通过上述方法将提取到的信息存入 assets 目录下的 config 文件中,供宿主程序使
用。
2)替换图标和标签
①获取图标和标签信息
考虑到安全性,需要对 host 程序进行必要的伪装,即重新打包后的 host 程序,安装后
在应用列表看上去要和 A 程序的一致,故还需要替换图标和标签。同理,通过上述方法,
可从 info.txt 中获得 label 和 icon 的信息,如图 6 所示。
                                             图 6 info.txt 中的 label 和 icon 信息
②替换图标和标签信息
在获得 icon 路径和 label 之后,接下来需要替换 host.apk 中的这些信息。
首先需要对主程序 A.apk 进行解包,以获得图标文件。Apk 解包同样使用 apktool。直
接使用 apktool 解包的命令为“java –jar apktool.jar –d apkpathunpackeddir”,其中 apktool 支
持很多参数,实现其他功能,方便起见将其封装为 bat 文件 unpack_apk.bat,内容如下。
set PATH=%CD%;%PATH%;
java -jar “%~dp0\apktool.jar” d %1 %2
该批处理的第一个参数为 apk 路径,第二个参数为解包后的目录路径。在捆绑器程序中,
可通过如下命令调用该批处理,实现相应的解包功能。
ShellExecute(NULL, TEXT(“open”), szBatfilePath,szParameters, NULL, SW_HIDE);
其中,szParameters 即为批处理文件对应的两个参数。
在成功解包之后,替换图标就很简单了,只需要用上述 icon 路径对应的文件替换宿主
程序 host.apk 中的“\res\drawable-mdpi\ic_launcher.png”、 \res\drawable-hdpi\ic_launcher.png”、
“\res\drawable-ldpi\ic_launcher.png”和“\res\drawable-xhdpi\ic_launcher.png”文件。
宿主程序的标签信息位于“\res\values\strings.xml”中,具体如图 7 所示。
只需将其中的app_name 对应的值替换为前面获取到的 label 的值即可。
                                                       图 7 宿主程序的 strings 文件内容
3)打包、签名
通过前面的步骤,我们已经成功修改好了宿主程序 host.apk,接下来需要对其进行打包、
签名。
①打包
对 APK 的打包,仍旧采用 apktool,将其封装成apk_apk.bat,内容如下。
                              set PATH=%CD%;%PATH%;
java -jar “%~dp0\apktool.jar” b %1
其所需的唯一一个参数即为需要打包成 apk 的目录。类似的,程序中的调用方法依旧使
用“ShellExecute(NULL,TEXT(“open”), szPackBatfile, m_szFinalApkDir, m_pctApkToolDir,
SW_HIDE);”。
②签名
对 apk 的签名,可通过使用工具 autosign 实现。其调用方式为“java -jar signapk.jar
testkey.x509.pem testkey.pk8 unsigined.apk signed.apk”,需要的两个参数为签名的文件路径和
成功签名后的文件路径。将其写为 sign.bat,内容为:“java -jar signapk.jar testkey.x509.pem
testkey.pk8 %1 %2”。在捆绑器程序中相应的调用方式为“ShellExecute(NULL, TEXT(“open”),
szSignBatfile, szParameters, m_pctAutoSignDir, SW_HIDE);”,最终即可获得捆绑后的 apk 程
序。
总结
通过前面的步骤,最终生成了一套由“apktool”、“auto-sign”、“host.apk”和“捆绑器”
组成的 apk 捆绑器工具,如图 8 所示。
图 8 捆绑器工具组成(左)和捆绑器运行界面(右)
最终捆绑之后的 apk 应用程序具备和正常安装的 apk 一样的运行效果,但却能隐蔽的安
装所指定的任意 apk 文件,达到预期的目的和功能效果。
同时,在上述实现过程中仍存在一定的不足,如在安装捆绑之后的 apk 应用时,所显示
的权限和非正常安装的程序(主程序 A.apk)的权限不一致。一种更完美的方法则可以考虑
在修改 host.apk 文件时,同时将主程序 A.apk 的 manifest 中的权限也复制到 host.apk 的
manifest 中,有兴趣的读者可以自行尝试。

[转载]我也要谈谈大型网站架构之系列(2)——纵观历史演变(下) - 一线码农 - 博客园

mikel阅读(1051)

[转载]我也要谈谈大型网站架构之系列(2)——纵观历史演变(下) – 一线码农 – 博客园.

这篇文章本来准备前几天就得写的,谁也没想到这段时间公司的RC太多了,含酸苦逼的加班,加班。。。所以在大一点的公司上班,

写代码的责任心一定要强,或许就因为你的一些小bug,给公司带来不少损失。。。这在以前公司真的没多大体会的。

好了,继续说说架构的演变,从第四代架构中可以看到,我们通过做应用程序层的负载均衡可以比较完美的解决了在整个架构中让应

用程序层不再成为瓶颈,通过A10,我们可以让用户的访问请求分发到集群中的任何一台服务器上,当访问量继续膨胀的时候,我们就可

以继续在集群中增加服务器来解决负载的压力,达到系统的可伸缩性,现在我们的业务规模像滚雪球一样越来越大,用户数暴增。。。这

时候我们缓存中的数据也越来越多,虽然我们用了缓存,但是大量的“缓存过期重新读取”和“缓存不命中“,导致数据库压力非常大,这时候

数据库的压力成为了我们架构中的瓶颈。

 

五: 第五代架构

既然数据库成为了我们第四代架构的瓶颈,这时候必须解决数据库的压力问题,最常见的做法也就是“读写分离”,将写和读的库进行拆

分来缓解数据库的压力。

现在我们做了多个库,写的时候进主库,然后数据库分发到从库中,然后应用程序在从库中读取,这里为了让数据库对应用程序更加

透明,我们通常加一个“数据访问层”,在携程里面就是在企业库上进行了一层封装以及安全性采用了all in one 模式,可以看到第五代

架构对数据库的压力有了很大的缓解。

经过几个月业务喷井式的发展之后,我们会发现数据库检索越来越慢,单表数据量已经差不多爆炸了。。。已经严重影响到系统性能,

用户抱怨不断,这时候“数据检索”成为了我们系统的严重瓶颈。

 

六:第六代架构

既然检索成了瓶颈,我们必须对数据库进行拆分,尽可能的减少检索中的数据量规模以及尽可能的优化算法。

1:业务分库

我们将不同的业务分摊到不同的业务服务器上,而不是将其耦合在一个数据库里面,从而建立起数据库集群,分流应用层对数

据库的压力。

2:分表

可以采用时间划分,将三个月之后的数据放入到历史表里面,当前表只保存三个月之内的数据,而从极大提供单表的检索能力。

3:采用noSQL

noSQL就是为了web而生,分词,系统日志等等,一样都让不少nosql,而且nosql有其天生的负载均衡。

4:优化算法

栈,队列,二叉树,哈希 等等变换和非变换的数据结构在这种大数据的场景下可以得到灵活运用,这也是区分高级程序员和低等

码农的一条参考标准。

当你的架构到这个程度的时候,差不到公司的人数也过千了,这时候我们的业务会分成很多产品线的,比如:机票事业部,酒店

事业部,旅游度假事业部,攻略社区事业部,每个事业部只会负责自己的产品架构,从而将我们的架构再次细分,从技术角度看,这些

事业部又可以提炼出公共的部门,比如登录模块,订单处理等等这些可复用的模块,可以相应的成立公共平台事业部和框架架构部,当

这个架构继续往下发展的话,就有了现在的各种云,也就成了各种变钱的工具了,就比如现在的博客园托管在阿里云之上。。。

 

终于在今天,结束了高层重视的IVR项目的所有事情,最后祭奠一下,自从猪猪侠拿到那些所谓的数据,导致我们连续加班的日日夜夜。

[转载]网站性能优化:动态缩略图技术实现思路 - 旺 旺 - 博客园

mikel阅读(1081)

[转载]网站性能优化:动态缩略图技术实现思路 – 旺 旺 – 博客园.

网站开发过程中,大家都是如何解决多尺寸图片缩略图问题的呢?犹为典型的是电商网站,据了解,淘宝的图片缩略图是直接存储多张缩略图的方式, 以满足各种情况下使用,因为它有牛逼的开源+自主开发的海量图片存储架构作支撑。但是,我们在做网站时,并不可能直接搬牛逼的架构过来,就可以达到预期的 效果,况且各种成本投入也是有限的。所以一般性能优化的原则大都是这样:先考虑软件的优化,再考虑硬件的升级,当然土豪客户则除外。

很多网站可能没有对图片进行缩略图处理,上传时图片可能几百KB,在页面也是直接加载几百KB的图片大小,这样极为占用带宽,影响网站加载速 度。也有很多网站的做法可能也是直接根据前端页面所需求图片的尺寸,在上传时就处理生成相应尺寸的缩略图,但如果前端页面布局进行调整时,可能就得调整缩 略图生成的尺寸,之前生成的图片也有可能需要重新生成。之前我在一个网站项目时就遇到这样的问题,经过一系列地验证,最终是采用动态缩略图技术解决的,现 在整理出来给大家分享分享。

其实,原理很简单,通过高性能的图片压缩算法,在一般处理程序(HttpHandler)对图片进行压缩处理,图片路径则直接指向HttpHandler,将图片路径、需要压缩的宽高等参数传进去,实现动态压缩。

在网站目录下新建 ResizeImage.ashx 文件,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing;

namespace www.ideek.cn
{
    /// <summary>
    /// 动态缩略图处理程序
    /// 调用示例: <img runat="server" src="~/ResizeImage.ashx?src=/Upload/20140428/www_ideek_cn.jpg&width=128&height=128" />
    /// </summary>
    public class ResizeImageHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            string fileName = context.Server.UrlDecode(context.Request["src"]);
            if (string.IsNullOrEmpty(fileName))
            {
                context.Response.Write("缺少参数src.");
                return;
            }
            fileName = Server.MapPath("~/" + fileName);

            Stream fileStream = null;
            try
            {
                string wStr = context.Request["width"];
                string hStr = context.Request["height"];
                int width = 0, height = 0;
                if (!string.IsNullOrEmpty(wStr) && !string.IsNullOrEmpty(hStr))
                {
                    int.TryParse(wStr, out width);
                    int.TryParse(hStr, out height);
                }

                FileInfo fi = new FileInfo(fileName);
                if (!fi.Exists)
                {
                    context.Response.Write("图片不存在.");
                    return;
                }
                string contentType = getContentType(fi.Extension);
                context.Response.ContentType = contentType;

                //只能处理jpg及png图片格式,没有宽高参数不进行缩放处理
                if (width > 0 && height > 0 && (contentType.Contains("jpeg") || contentType.Contains("png")))
                {
                    Image image = Image.FromFile(fi.FullName);
                    int sWidth = image.Width, sHeight = image.Height;
                    int nWidth = 0, nHeight = 0;
                    if (sWidth > width || sHeight > height)
                    {
                        if (((double)sWidth / (double)sHeight) > ((double)width / (double)height))
                        {
                            //以宽度为基准缩小
                            if (sWidth > width)
                            {
                                nWidth = width;
                                nHeight = (int)(width * sHeight / (double)sWidth);
                            }
                            else
                            {
                                nWidth = sWidth;
                                nHeight = sHeight;
                            }
                        }
                        else
                        {
                            //以高度为基准缩小
                            if (sHeight > height)
                            {
                                nWidth = (int)(height * sWidth / (double)sHeight);
                                nHeight = height;
                            }
                            else
                            {
                                nWidth = sWidth;
                                nHeight = sHeight;
                            }
                        }

                        Bitmap bitmap = new Bitmap(nWidth, nHeight, PixelFormat.Format32bppArgb);
                        Graphics graphics = Graphics.FromImage(bitmap);
                        graphics.Clear(Color.White);
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;  //平滑处理
                        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;  //缩放质量
                        graphics.DrawImage(image, new Rectangle(0, 0, nWidth, nHeight));
                        image.Dispose();

                        EncoderParameters parameters = new EncoderParameters(1);
                        parameters.Param[0] = new EncoderParameter(Encoder.Quality, ((long)80));  //图片质量参数

                        fileStream = new MemoryStream();
                        bitmap.Save(fileStream, GetImageCodecInfo(contentType), parameters);
                        using (MemoryStream ms = new MemoryStream())
                        {
                            bitmap.Save(ms, GetImageCodecInfo(contentType), parameters);
                            context.Response.OutputStream.Write(ms.GetBuffer(), 0, (int)ms.Length);
                        }
                        parameters.Dispose();
                        bitmap.Dispose();
                        return;
                    }
                    if (image != null)
                        image.Dispose();
                }
                else
                {
                    fileStream = new FileStream(fi.FullName, FileMode.Open);
                    byte[] bytes = new byte[(int)fileStream.Length];
                    fileStream.Read(bytes, 0, bytes.Length);
                    fileStream.Close();
                    context.Response.BinaryWrite(bytes);
                }
            }
            catch (Exception ex)
            {
                context.Response.Write(ex.Message);
            }
            finally
            {
                if (fileStream != null)
                {
                    fileStream.Close();
                    fileStream.Dispose();
                }
            }
            System.GC.Collect();
        }


        /// <summary>
        /// 获取文件下载类型
        /// </summary>
        /// <param name="extension"></param>
        /// <returns></returns>
        private string getContentType(string extension)
        {
            string ct = string.Empty;
            switch (extension.ToLower())
            {
                case ".jpg":
                    ct = "image/jpeg";
                    break;
                case ".png":
                    ct = "image/png";
                    break;
                case ".gif":
                    ct = "image/gif";
                    break;
                case ".bmp":
                    ct = "application/x-bmp";
                    break;
                default:
                    ct = "image/jpeg";
                    break;
            }
            return ct;
        }

        //获得包含有关内置图像编码解码器的信息的ImageCodecInfo 对象.
        private ImageCodecInfo GetImageCodecInfo(string contentType)
        {
            ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders();
            ImageCodecInfo jpegICI = null;
            for (int x = 0; x < arrayICI.Length; x++)
            {
                if (arrayICI[x].MimeType.Equals(contentType))
                {
                    jpegICI = arrayICI[x];
                    //设置JPEG编码
                    break;
                }
            }
            return jpegICI;
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

按 Ctrl+C 复制代码

  图片压缩算法中,有几个参数可以根据自己的需求进行调整,比如:SmoothingMode、InterpolationMode、Encoder.Quality,反正图片的大小与图片质量成正比,也跟性能成反比,具体参数用法,请移步MSDN查看,因此大家根据实际需求进行取舍了。

  在页面需要调用地方,将img的src设置为ResizeImage.ashx,这跟图片验证码处理方式一样,如:

 <img runat="server" src="~/ResizeImage.ashx?src=/Upload/20140428/www_ideek_cn.jpg&width=128&height=128" />

  这样,一张图片可以在网站任意地方使用,图片经过压缩后传输,通过Google或Firefox浏览器开发者工具可以很明显地看出图片大小效果和加载速度都有非常明显的提升。

  当然,可能很多人会问频繁的计算对服务器会产生性能影响,所以在实际使用过程中,可以根据自己的需求采用一些手段进行规避,比如引入缓存机制等,这些优化将会在后续文章中讲解。

[转载]android解锁界面开发--分分钟教你做锁屏软件 - 写代码的卓克 - 博客园

mikel阅读(1245)

[转载]android解锁界面开发–分分钟教你做锁屏软件 – 写代码的卓克 – 博客园.

想做个锁屏界面很久了,最近一周,历经千辛万苦,越过种种挫折,终于完美实现了这一要求,在此将锁屏思路分享出来。

注意:这不是什么一键锁屏,是类似“go锁屏”那样的锁屏界面。

准备:本程序共需要

两个activity:home、main。

一个service:myService

一个receiver:bootReceiver

一个layout:layout

其中home作为屏幕home键专用的activity,main则是主要的展示锁屏界面的activity。

service用于接收锁屏/解锁广播,layout则是main所需要展示的界面。

思路:

!注意:以下代码没有顺序联系,具体请参考源码!

1,给程序添加服务,当此服务接收到 锁屏/解锁广播 时,关闭系统锁屏界面,打开自己的锁屏界面。

关键代码:

/onReceive中:

keyguardManager = (KeyguardManager)context.getSystemService(context.KEYGUARD_SERVICE);
keyguardLock = keyguardManager.newKeyguardLock("");
keyguardLock.disableKeyguard();//解锁系统锁屏
startActivity(toMainIntent);//跳转到主界面

注意,上面的代码需要注册权限:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

另外,为了防止主界面被重复调用,我们在设置intent时还要加上一些filter:

//设置myservice中intent的filter
toMainIntent = new Intent(myService.this, Main.class);
toMainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//这个flags表示如果已经有这个activity,则将已有的提到栈顶,否则新建一个activity。
<br />//在manifest中讲主界面的启动模式更改为:singleTask,功能和上面的“Intent.FLAG_ACTIVITY_NEW_TASK”类似<br /><br />

当然,我们还可以在onDestroy中设置服务重启,以保证此服务一直在后台运行

<br />@Override<br />public void onDestroy() {<br />super.onDestroy();<br />unregisterReceiver(screenReceiver);<br />//重启此服务<br />startActivity(new Intent(myService.this,myService.class));<br />}<br />

将一些其他的事项,如注册服务、在主界面中启动服务等设置完成后,我们可以run一遍了~

如果没出错,那么解锁后首先打开的将是我们的锁屏界面。

2,实现了锁屏,但是还有一个问题,当按返回键或者home键的时候,我们的界面就轻易被KO了。别担心,咱们一一屏蔽他们。

首先,拿返回键开刀:只需要在主界面中添加如下代码即可:

<br />@Override<br />public boolean onKeyDown(int keyCode, KeyEvent event) {<br />switch (keyCode) {<br />case KeyEvent.KEYCODE_BACK:<br />return true;<br />}<br />return super.onKeyDown(keyCode, event);<br />}<br />

可是,home键并不能通过此方法屏蔽或者捕捉。这对程序猿来说是个很头疼的问题,在网上也有各种解决的办法,但是笔者参考总结,决定用“GO锁屏”的方式来实现:

这种办法的思路是:把自己的程序设置为系统主屏幕!这样,本来按home键是要转到主屏幕的,设置好后,按home键则直接跳转到我们的界面里来了!

好了,让我们新建一个activity,命名为“home”,用来抢夺home键。然后在manifest中设置如下:

<br /><br />

这样,当按home键的时候,如果是在main界面按的,系统不做反应。如果是在其他时候按下home键,就会跳转到这个activity中来。

(首次运行时,会让你选择按home键跳转到“主屏幕”或者“test1(咱们自己写的程序的appName)”)

也就是说,我们已经实现了锁屏界面对home的屏蔽。如果是在非锁屏状态下按home,就会调用home界面。我们只需在home界面里如此设置即可:

<br />onCreate(){<br />跳转到系统主屏幕;//具体代码见下文<br />finish();//结束这个activity<br />}<br />

所以,这个activity没有必要加载界面。故此,我们要将这个activity的theme设置为“不显示”,这样,不但避免浪费,更可以避免在跳转的时候屏幕闪一下,影响用户体验

<br />//manifest设置home的theme为noDisplay<br /><br />

我们可以在home的oncreate里打这样一行代码:

Log.e(“”, “home is called”);finish();//调用finish以防止程序卡在一个不展示的activity

然后运行一遍试试效果,就可以清楚的发现,当我们何时按home键时,会调用这一activity。

3,home键已经抢过来了。那么接下来,我们要做的是让home界面跳转到主界面:

首先,我们要获取一个列表:列出所以可以作为主屏幕的程序:

<br />List pkgNamesT = new ArrayList();<br />List actNamesT = new ArrayList();<br />List resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);<br />for (int i = 0; i &amp;lt; resolveInfos.size(); i++) {<br />String string = resolveInfos.get(i).activityInfo.packageName;<br />if (!string.equals(context.getPackageName())) {//排除自己的包名<br />pkgNamesT.add(string);<br />string = resolveInfos.get(i).activityInfo.name;<br />actNamesT.add(string);<br />}<br />}<br />

然后,可以用alertDialog的方式让用户选择要跳到哪个主屏幕,并用sharedPreferences记录用户的选择。

<br />new AlertDialog.Builder(context).setTitle("请选择解锁后的屏幕").setCancelable(false).setSingleChoiceItems(names, 0, new DialogInterface.OnClickListener() {<br />@Override<br />public void onClick(DialogInterface dialog, int which) {<br />editor.putString(packageName, pkgNames.get(which));<br />editor.putString(activityName, actNames.get(which));<br />editor.commit();<br />originalHome();<br />dialog.dismiss();<br />}<br />}).show();<br />

这样,下次的时候就可以根据包名和类名,直接跳转到用户所设置的主屏幕了:

<br />String pkgName = sharedPreferences.getString(packageName, null);<br />String actName = sharedPreferences.getString(activityName, null);<br />ComponentName componentName = new ComponentName(pkgName, actName);<br />Intent intent = new Intent();<br />intent.setComponent(componentName);<br />context.startActivity(intent);<br />((Activity) context).finish();<br />

4,当然,最后,我们也希望可以将其设置为开机启动:

<br />public class BootReceiver extends BroadcastReceiver{<br />String myPkgName = "com.example.screenlocker";//#包名<br />String myActName = "com.example.acts.myService";//#类名<br /><br />@Override<br />public void onReceive(Context context, Intent intent) {<br />//启动监听服务<br />Intent myIntent=new Intent();<br />myIntent.setAction(myPkgName+"."+myActName);<br />context.startService(myIntent);<br />}<br />}<br />

注意:别忘了在manifest中添加开机启动的权限哦:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

不过我更推荐在home中加这么一行代码:

startService(new Intent(Home.this, ScreenReceiver.class));

这样,每次打开主屏幕都能打开这个后台服务,哈哈哈……

下载源码

[转载]C#开发微信门户及应用(4)--关注用户列表及详细信息管理 - 伍华聪 - 博客园

mikel阅读(1126)

[转载]C#开发微信门户及应用(4)–关注用户列表及详细信息管理 – 伍华聪 – 博客园.

在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起来,专心做好底层的技术开发。

微信的很重要的一个特点就是能够利用其平台庞大的用户群体,因此很容易整合在CRM(客户关系管理)系统里面,服务号和订阅好都能够向关注者推送相 关的产品消息,还能和48小时内响应消息和事件的活跃用户进行交互对话,因此用户信息是微信API里面非常重要的一环,本随笔主要介绍获取关注用户、查看 用户信息、分组管理等方面的开发应用。

1、关注用户列表及用户分组信息

在微信的管理平台上,我们可以看到自己账号的关注者用户,以及用户分组信息,如下所示。

上面的管理界面,能看到关注者用户的基础信息,但是使用微信API获取到的是一个称之为OpenID的列表,我们先了解这个东西是什么?微信API的说明给出下面的解析:

关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)组成。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。

上面的解析意思很清楚了,就是一个用户关注我们的公众号,那么不管他是第几次关注,对我们公众号来说,都是一个确定的值;但是,一个用户对其他公众号,却有着其他不同的OpenID。

微信提供了为数不多的几个关键字信息,用来记录用户的相关内容,根据用户的相关定义,我们定义一个实体类,用来放置获取回来的用户信息。

///
/// 高级接口获取的用户信息。
/// 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID
/// (加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。
/// 公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
///

public class UserJson : BaseJsonResult
{
///
/// 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
///

public int subscribe { get; set; }

///
/// 用户的标识,对当前公众号唯一
///

public string openid { get; set; }

///
/// 用户的昵称
///

public string nickname { get; set; }

///
/// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
///

public int sex { get; set; }

///
/// 用户的语言,简体中文为zh_CN
///

public string language { get; set; }

///
/// 用户所在城市
///

public string city { get; set; }

///
/// 用户所在省份
///

public string province { get; set; }

///
/// 用户所在国家
///

public string country { get; set; }

///
/// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
///

public string headimgurl { get; set; }

///
/// 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
///

public long subscribe_time { get; set; }
}

根据分组信息定义,我们定义一个分组的实体类信息。

///
/// 分组信息
///

public class GroupJson : BaseJsonResult
{
///
/// 分组id,由微信分配
///

public int id { get; set; }

///
/// 分组名字,UTF8编码
///

public string name { get; set; }
}

2、获取AIP调用者的的Token

在做微信API的开发,很多时候,我们都需要传入一个AccessToken,这个就是区分调用者和记录会话信息的字符串,因此,在学习所有API开发之前,我们需要很好理解这个访问控制参数。

这个对象的定义,我们可以从微信的API说明中了解。

access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。由于获取access_token的api调用次数非常有限,建议开发者全局存储与更新access_token,频繁刷新access_token会导致api调用受限,影响自身业务。

根据上面的说明定义,我们可以看到,它是一个和身份,以及会话时间有关的一个参数,而且它的产生次数有限制,因此要求我们需要对它进行缓存并重复利用,在会话到期之前,我们应该尽可能重用这个参数,避免反复请求,增加服务器压力,以及调用的时间。

我定义了一个方法,用来构造生成相关的Access Token,而且它具有缓存的功能,但具体如何缓存及使用,对我API的调用是透明的,我们只要用的时候,就对它调用就是了。

        /// 获取凭证接口
        /// </summary>
        /// <param name="appid">第三方用户唯一凭证</param>
        /// <param name="secret">第三方用户唯一凭证密钥,既appsecret</param>
        string GetAccessToken(string appid, string secret);

缓存主要是基于.NET4增加的类库MemoryCache,这个是一个非常不错的缓存类。

我的获取AccessToken的操作实现代码如下所示。

///
/// 获取每次操作微信API的Token访问令牌
///

///应用ID ///开发者凭据 ///
public string GetAccessToken(string appid, string secret)
{
//正常情况下access_token有效期为7200秒,这里使用缓存设置短于这个时间即可
string access_token = MemoryCacheHelper.GetCacheItem("access_token", delegate()
{
string grant_type = "client_credential";
var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&amp;appid={1}&amp;secret={2}",
grant_type, appid, secret);

HttpHelper helper = new HttpHelper();
string result = helper.GetHtml(url);
string regex = "\"access_token\":\"(?.*?)\"";
string token = CRegex.GetText(result, regex, "token");
return token;
},
new TimeSpan(0, 0, 7000)//7000秒过期
);

return access_token;
}

由于我们知道,AccessToken默认是7200秒过期,因此在这个时间段里面,我们尽可能使用缓存来记录它的值,如果超过了这个时间,我们调用这个方法的时候,它会自动重新获取一个新的值给我们了。

 

3、获取关注用户列表

获取关注用户列表,一次拉取API调用,最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。微信的接口定义如下所示。

http请求方式: GET(请使用https协议)
https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID

这个接口返回的数据是

{"total":2,"count":2,"data":{"openid":["","OPENID1","OPENID2"]},"next_openid":"NEXT_OPENID"}

根据返回的Json数据定义,我们还需要定义两个实体类,用来存放返回的结果。

///
/// 获取关注用户列表的Json结果
///

public class UserListJsonResult : BaseJsonResult
{
///
/// 关注该公众账号的总用户数
///

public int total { get; set; }

///
/// 拉取的OPENID个数,最大值为10000
///

public int count { get; set; }

///
/// 列表数据,OPENID的列表
///

public OpenIdListData data { get; set; }

///
/// 拉取列表的后一个用户的OPENID
///

public string next_openid { get; set; }
}

///
/// 列表数据,OPENID的列表
///

public class OpenIdListData
{
///
/// OPENID的列表
///

public List openid { get; set; }
}

为了获取相关的用户信息,我定义了一个接口,用来获取用户的信息,接口定义如下所示。

///
/// 微信用户管理的API接口
///

public interface IUserApi
{
///
/// 获取关注用户列表
///

///调用接口凭证 ///第一个拉取的OPENID,不填默认从头开始拉取 ///
List GetUserList(string accessToken, string nextOpenId = null);

///
/// 获取用户基本信息
///

///调用接口凭证 ///普通用户的标识,对当前公众号唯一 ///返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 UserJson GetUserDetail(string accessToken, string openId, Language lang = Language.zh_CN);

然后在实现类里面,我们分别对上面两个接口进行实现,获取用户列表信息如下所示。

///
/// 获取关注用户列表
///

///调用接口凭证 ///第一个拉取的OPENID,不填默认从头开始拉取 ///
public List GetUserList(string accessToken, string nextOpenId = null)
{
List list = new List();

string url = string.Format("https://api.weixin.qq.com/cgi-bin/user/get?access_token={0}", accessToken);
if (!string.IsNullOrEmpty(nextOpenId))
{
url += "&amp;next_openid=" + nextOpenId;
}

UserListJsonResult result = JsonHelper.ConvertJson(url);
if (result != null &amp;&amp; result.data != null)
{
list.AddRange(result.data.openid);
}

return list;
}

我们看到,转换的逻辑已经放到了JsonHelper里面去了,这个辅助类里面分别对数值进行了获取内容,验证返回值,然后转换正确实体类几个部分的操作。

获取内容,通过辅助类HttpHelper进行,这个在我的公用类库里面,里面的逻辑主要就是通过HttpRequest进行数据的获取操作,不在赘述

HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url);

由于返回的内容,我们需要判断它是否正确返回所需的结果,如果没有,抛出自定义的相关异常,方便处理,具体如下所示。

///
/// 检查返回的记录,如果返回没有错误,或者结果提示成功,则不抛出异常
///

///返回的结果 ///
private static bool VerifyErrorCode(string content)
{
if (content.Contains("errcode"))
{
ErrorJsonResult errorResult = JsonConvert.DeserializeObject(content);
//非成功操作才记录异常,因为有些操作是返回正常的结果({"errcode": 0, "errmsg": "ok"})
if (errorResult != null &amp;&amp; errorResult.errcode != ReturnCode.请求成功)
{
string error = string.Format("微信请求发生错误!错误代码:{0},说明:{1}", (int)errorResult.errcode, errorResult.errmsg);
LogTextHelper.Error(errorResult);

throw new WeixinException(error);//抛出错误
}
}
return true;
}

然后转换为相应的格式,就是通过Json.NET的类库进行转换。

            T result = JsonConvert.DeserializeObject<T>(content);
            return result;

这样我们就可以在ConvertJson函数实体里面,完整的进行处理和转换了,转换完整的函数代码如下所示。

///
/// Json字符串操作辅助类
///

public class JsonHelper where T : class, new()
{
///
/// 检查返回的记录,如果返回没有错误,或者结果提示成功,则不抛出异常
///

///返回的结果 ///
private static bool VerifyErrorCode(string content)
{
if (content.Contains("errcode"))
{
ErrorJsonResult errorResult = JsonConvert.DeserializeObject(content);
//非成功操作才记录异常,因为有些操作是返回正常的结果({"errcode": 0, "errmsg": "ok"})
if (errorResult != null &amp;&amp; errorResult.errcode != ReturnCode.请求成功)
{
string error = string.Format("微信请求发生错误!错误代码:{0},说明:{1}", (int)errorResult.errcode, errorResult.errmsg);
LogTextHelper.Error(errorResult);

throw new WeixinException(error);//抛出错误
}
}
return true;
}

///
/// 转换Json字符串到具体的对象
///

///返回Json数据的链接地址 ///
public static T ConvertJson(string url)
{
HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url);
VerifyErrorCode(content);

T result = JsonConvert.DeserializeObject(content);
return result;
}
}

调用这个API的界面层代码如下所示(测试代码)

            IUserApi userBLL = new UserApi();
            List<string> userList = userBLL.GetUserList(token)

 

4、获取用户详细信息

上面的获取列表操作,相对比较简单,而且不用POST任何数据,因此通过Get协议就能获取到所需的数据。

本小节继续介绍获取用户详细信息的操作,这个操作也是通过GET协议就可以完成的。

这个API的调用定义如下所示:

http请求方式: GET
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

通过传入一个OpenId,我们就能很好获取到用户的相关信息了。

前面小节我们已经定义了它的接口,说明了传入及返回值,根据定义,它的实现函数如下所示。

///
/// 获取用户基本信息
///

///调用接口凭证 ///普通用户的标识,对当前公众号唯一 ///返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 public UserJson GetUserDetail(string accessToken, string openId, Language lang = Language.zh_CN)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/user/info?access_token={0}&amp;openid={1}&amp;lang={2}",
accessToken, openId, lang.ToString());

UserJson result = JsonHelper.ConvertJson(url);
return result;
}

最后,我们结合获取用户列表和获取用户详细信息的两个API,我们看看调用的代码(测试代码)。

private void btnGetUsers_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
List userList = userBLL.GetUserList(token);
foreach (string openId in userList)
{
UserJson userInfo = userBLL.GetUserDetail(token, openId);
if (userInfo != null)
{
string tips = string.Format("{0}:{1}", userInfo.nickname, userInfo.openid);
Console.WriteLine(tips);
}
}
}
主要研究技术:代码生成工具、Visio二次开发、客户关系管理软件、送水管理软件等共享软件开发
专注于Winform开发框架Web开发框架、WCF开发框架的研究及应用。
转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com