华为HG526电信猫破解教程

把宜兴家里的电信从旧的4M套餐改为新的4M套餐,每月费用省了40多块,还送了个华为的HG526电信猫和iTV。拿到HG526后发现它带有无线路由功能,想着是不是可以让它自动拨号,以替换原来的ADSL猫和无线路由器。网上搜索一下,发现有很多教程,参考着资料经过一番实践,终于把ADSL猫和无线路由器换下来了。

这里做个笔记,把实践中碰到的问题和疑惑记录下来,分享给大家。

破解HG526的核心是获得超级用户telecomadmin的密码。

1、准备U盘,在根目录下建立一个文件夹e8_Config_backup,插入华为HG526;

2、用HG526的默认帐号(用户名useradmin)登录路由器192.168.1.1;

3、在浏览器地址栏里输入http://192.168.1.1/html/ehomeclient/cfgUSBRestore.cgi?coverusbpath=usb1_1,提示备份成功!注意大小写;

4、把U盘插入电脑,e8_Config_backup文件夹下多了一个文件ctce8_HG526.cfg;

5、用附件中的工具把配置文件解密,找到telecomadmin帐号的密码。

6、用超级用户telecomadmin登录,把远程管理关闭(不然密码就会被电信更改的)。

路由拨号配置

登录后,进入“网络”->“宽带设置”,在“连接名称”右边,选择“INTERNET_R_8_35”,按照下图配置,修改自己的上网账号和密码。

这里有个问题,某些地方的VPI/VCI值是不一样的,宜兴市区是0/100,所以上面应该选“INTERNET_B_0_100”。其它地方如果刷了不能用建议咨询下电信维护人员是多少。

将破解的配置文件恢复到HG526中

在U盘的e8_Config_backup文件夹里放入破解修改后的ctce8_HG526.cfg文件,插入HG526。

用超级用户登录,在“管理”->“设备管理”页面中,在“启用USB自动恢复配置文件”后面打上钩。然后点击“设备重启”。注意观察HG526以及U盘的指示灯,等HG526上所有的灯稳定了,配置也就刷进去了。

如何删除TR069协议,修改远程管理网址和域名

正常情况下“TR069_R_8_46”连接项在WEB配置页面中无法删除,但我们可以使用Web Developer插件来做到。在安装了Web Developer插件的Firefox中打开http://192.168.1.1,用telecomadmin登录,进入“网络”—>“宽带设置”,在页面中点击右键,在Web Developer工具栏中选择“表单”->“开启 被禁用的表单”。然后你就可以删除TR069协议了。远程管理的修改与之类似。

关于TR069的解释:TR-069是由DSL论坛所开发的技术规范之一,其全称为“CPE广域网管理协议”。它提供了对下一代网络中家庭网络设备进行管理配置的通用框架和协议,用于从网络侧对家庭网络中的网关、路由器、机顶盒等设备进行远程集中管理。电信通过TR069协议修改我们的无线猫的配置,比如超级用户名密码,用户权限等,所以我们的配置文件里要关闭TR069,大家注意不要自己打开,否则一旦被改了超级用户名密码就惨了!

HG526配置文件加/解密软件:hg526-tool

7个方法养成立即行动的习惯

原文:7 Ways to Grow the Action Habit
译文1:7步养成立即行动的习惯
译文2:立即行动的七个方法
译文3:七种建立你的行动习惯的方法

所有顶级成功人士都有一个共同的品质——那就是立即行动。这种品质可以取代智商,天赋和人际关系,来决定你的薪资范围和晋升速度。

尽管这个观念很简单,但是真正掌握它的人却是极少数。行动的习惯——把想法转换成立即行动的习惯——是完成任务必不可少的习惯。这里有7个方法让你养成立即行动的习惯:

1、不要等到条件都完美了才行动

如果你想等条件都完美了才开始行动,你可能永远都不会开始。因为总是会有些事情不是那么好。或是错过时机,行情不好,或是竞争太激烈。现实世界中没有完美的开始时间。你必须立即行动,等问题出现时再去解决它。开始行动的最佳时间是去年,其实便是现在。

2、做个实干家

行动胜于空想。你想开始实践吗?你有没有好的创意要告诉老板?今天就行动起来吧。想法停留在你脑子里时间越长,它就会变得越弱。几天之后,细节就会变得模糊。一周过去,你已经完全忘记它了。要想成为一个实干家,你需要做得更多,并在做的过程中产生更多新的想法。

3、记住,想法本身并不能带来成功

想法是很重要,但是它只有在被执行后才有价值。一个被付诸行动的平凡想法,比许多“某一天”或者“正确时机”再去实施的卓越创意更有价值。如果你有一个觉得真的很不错的想法,那么就去实现它。如果你不行动起来,那么这个想法永远不会被实现。

4、用行动去克服恐惧

你有没有注意到,在公众演讲中最恐惧的时刻是轮到你上场前那段等待的时刻?即使是专业的演讲者和演员也经历过表演前的焦虑。他们一旦开始,恐惧就会消失。行动是治疗恐惧的最佳方法。万事开头难。一旦行动起来,你将建立起自信,事情也会变得简单。通过行动来克服恐惧,通过行动来建立自信。

5、机械地开启你的创造力

人们对创造性工作的一大误解就是认为它只会在有灵感的时候产生。如果你一直等待灵感来敲门,那么你的作品就会很少。与其等待,不如机械地开启你的创造马达。如果你需要写些东西,那么强制自己坐下来后再写。笔尖在纸上滑动,思绪展开,开始写作,用你的笔激起你的伟大想法吧。

6、先顾眼前

把注意力集中在你当下可以做的事情上。不要去烦恼上周本该完成的事,也不要担心明天要完成的任务。你唯一能左右的时间就是现在。如果你过于考虑从前或者将来,你不会完成任何事情。明天或下周的事经常永远都不会发生。

7、立即切入正题

人们在开会前一般都会做些社交活动或着聊聊天。对于个体工作者也是如此。你在开始真正工作前多久会查看一次Email或RSS feeds呢?如果你不去绕过它们直接开始工作的话,这些分心的事情将会耗费你大量的时间。如果你做到了这点,你就会成为一个高效的人,别人也会把你当作领导者来看待。

没有上级的指示就采取行动,这需要勇气。或许这就是为何主动性是如此稀缺的才能,以至于大多经理都在寻找这种人才。争取主动,当你有一个好的想法的时候,立即去实现它,不要等别人来告诉你。一旦人们看到你在很认真地完成事情,他们就会想要加入你。成功人士不需要别人告诉他们该做什么。如果你想成为这样的人,那就应该习惯独立行动。

Another transaction by user xxx is already in progress for app yyy

参考文章:http://blog.csdn.net/kunshan_shenbin/article/details/6658463

有时候在部署代码到Google App Engine时,会出现以下的警告信息:

Another transaction by user xxx is already in progress for app yyy ...

遇到这个错误只需要执行如下的命令即可:

appcfg --passin rollback $PROJECT$\war
appcfg --passin backends rollback $PROJECT$\war

简单实现Android底部工具栏

在大部分教程中,底部工具栏通常由TabHost和RadioGroup结合完成,每个工具栏项对应一个Activity。不过,我们要实现的是多个工具栏项在单独的一个Activity上起作用。

http://www.cnblogs.com/figoyu/archive/2010/11/20/1882691.html就是这个。不过该教程对每个工具栏项设置了固定宽度80dip,导致工具栏项或屏幕大小不定时代码布局会有问题。

这里对该教程做些改进,使之能做到适应不定的工具栏项或屏幕大小。改进后的布局代码如下:
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1.0">
        <ScrollView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:scrollbars="vertical"
            android:fadingEdge="vertical">
            <TextView
                android:id="@+id/content"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:textSize="17dip" />
        </ScrollView>
    </LinearLayout>

    <include layout="@layout/toolbar" />
</LinearLayout>

toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/toolbar_bg">
    <ImageButton
        android:id="@+id/btn_index"
        android:src="@drawable/index"
        android:text="@string/index"
        style="@style/toolbar" />
    <ImageButton
        android:id="@+id/btn_prev"
        android:src="@drawable/btn_prev_bg"
        android:text="@string/prev"
        style="@style/toolbar" />
    <ImageButton
        android:id="@+id/btn_next"
        android:src="@drawable/btn_next_bg"
        android:text="@string/next"
        style="@style/toolbar" />
    <ImageButton
        android:id="@+id/btn_zoomin"
        android:src="@drawable/zoomin"
        android:text="@string/zoomin"
        style="@style/toolbar" />
    <ImageButton
        android:id="@+id/btn_zoomout"
        android:src="@drawable/zoomout"
        android:text="@string/zoomout"
        style="@style/toolbar" />
</LinearLayout>

创建Maven archetype避免重复项目初始配置

有这么句话:遇到重复的项目初始配置,就要创建自己的Maven archetype。

手里就有这样的项目,其它项目都基于这个项目创建,只是对package名字做了修改,还有就是替换部分的图片,如何从现有项目中创建archetype呢?

首先,在已有项目的根目录下创建pom.xml,内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>your.company</groupId>
    <artifactId>artifact-name</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
</project>

然后在根目录下执行下列命令(当然,在这之前你必须先配置好Maven环境):

mvn archetype:create-from-project

进入target\generated-sources\archetype目录,手工调整相关archetype源码。需进行变量替换的文件,要在archetype-metadata.xml中开启filtered=”true”。

下面就是使用archetype了。

mvn clean install
mvn archetype:generate -DarchetypeArtifactId=artifact-name-archetype -DarchetypeGroupId=your.company -DgroupId=name.dohkoos -DartifactId=example -Dpackage=name.dohkoos.example

在Rails中使用Open Flash Chart II简单教程

今天我们讲如何用Rails结合Open Flash Chart II实现如下的图表:

在项目中安装OFC2插件,生成controller:

script/plugin install git://github.com/pullmonkey/open_flash_chart.git
script/generate controller stats index

在生成的controller中添加以下代码:

class StatsController < ApplicationController
  def index
    @graph = open_flash_chart_object(500, 300, "/stats/graph_code")
  end

  def graph_code
    title = Title.new("Glass Bar")

    bar = BarGlass.new
    bar.set_values([-1,2,3,7,8,-7.3])

    y = YAxis.new
    y.set_range(-10, 10, 5)

    chart = OpenFlashChart.new
    chart.y_axis = y
    chart.set_title(title)
    chart.add_element(bar)
    puts chart.to_s
    render :text => chart.to_
  end
end

将插件中的assets/javascripts/swfobject.js拷贝到public/javascripts目录下,在相应的view中添加代码:
// index.html.erb

<script src="/javascripts/swfobject.js" type="text/javascript"></script>
<%= @graph %>

解决VirtualBox占用CPU达100%的问题

问题:在Windows Server 2008里用VirtualBox安装XP,在使用过程中VirtualBox的CPU占用率总是在100%附近。在网上搜了几个答案,终于把问题解决。

1. 新建一个dummy虚拟机,将RAM设为4M,不用分配硬盘,与其他虚拟机一起运行。不过我试后没什么效果。
2. 在虚拟机设置中将处理器的数量改成1。到设置中看到处理器的数量已经是1了。
3. 关闭虚拟机中的防火墙。我照着做后CPU的使用率立刻降到5%以 下。

用Spinner + SQLite实现省市县三级联动

1. 建立省市县行政区划代码(截止2010年12月31日)数据表

CREATE TABLE xzqhdm (
  _id INTEGER PRIMARY KEY,
  code NUMERIC,
  region TEXT,
  parent_code NUMERIC
);

parent_code指上一级的行政区划代码,省属于最上级的行政单位,设置它的区划代码为999999。

insert into xzqhdm values(NULL, 110000, "北京市", 999999);
insert into xzqhdm values(NULL, 110100, "市辖区", 110000);
insert into xzqhdm values(NULL, 110101, "东城区", 110100);
insert into xzqhdm values(NULL, 110102, "西城区", 110100);
insert into xzqhdm values(NULL, 110103, "崇文区", 110100);
insert into xzqhdm values(NULL, 110104, "宣武区", 110100);
insert into xzqhdm values(NULL, 110105, "朝阳区", 110100);
insert into xzqhdm values(NULL, 110106, "丰台区", 110100);
...
insert into xzqhdm values(NULL, 659001, "石河子市", 659000);
insert into xzqhdm values(NULL, 659002, "阿拉尔市", 659000);
insert into xzqhdm values(NULL, 659003, "图木舒克市", 659000);
insert into xzqhdm values(NULL, 659004, "五家渠市", 659000);

2. SQLite数据库的操作
如果应用使用到了SQLite数据库,在用户初次使用应用时,需要创建应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。Android系统为我们提供了一个名为SQLiteOpenHelper的类,这是一个抽象类,该类用于对数据库版本进行管理,有两个重要的方法,分别是onCreate()和onUpgrade()。

当调用SQLiteOpenHelper的getWritableDatabase()或getReadableDatabase()方法获取数据库实例时,如果数据库不存在,Android系统会自动生成一个数据库文件,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,在onCreate()方法里可以生成数据库表结构及添加一些应用使用到的初始化数据。onUpgrade()方法在数据库的版本发生变化时会被调用,数据库的版本是由程序员控制的,假设数据库现在的版本是1,由于业务的需要,修改了数据库表的结构,这时候就需要升级软件,升级软件时希望更新用户手机里的数据库表结构,为了实现这一目的,可以把原来的数据库版本设置为2(或其他数值),并且在onUpgrade()方法里面实现表结构的更新。当软件的版本升级次数比较多,这时在onUpgrade()方法里面可以根据原版号和目标版本号进行判断,然后作出相应的表结构及数据更新。

SQLiteDatabase类则封装了一些操作数据库的常用API,使用该类可以完成对数据进行CRUD操作。主要是execSQL()和rawQuery()方法。execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法可以执行select语句。SQLiteDatabase还专门提供了对应于CRUD的操作方法: insert()、delete()、update()和query()。

问题:
如何将SQLite数据库与apk文件一起发布?
可以将数据库文件复制到res\raw目录中,所有在res\raw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。

如何打开res\raw目录中的数据库文件?
不能直接打开res\raw目录中的数据库文件,需要在程序第一次启动时将该文件复制到手机内存或SD卡中,然后再打开。

public class DBHelper extends SQLiteOpenHelper {
    private static String DB_PATH = "/data/data/name.dohkoos.linkage/databases/";
    private static String DB_NAME = "xzqh.db";
    private static DBHelper databaseHelper;
    private static SQLiteDatabase db;

    private Context context;

    private DBHelper(Context context) {
        super(context, DB_NAME, null, 1);
        this.context = context;
    }

    public static DBHelper getInstance(Context context) {
        if (databaseHelper == null) {
            databaseHelper = new DBHelper(context);
            databaseHelper.openDataBase();

            if (db == null) {
                try {
                    db = databaseHelper.getWritableDatabase();
                    databaseHelper.copyDatabase();
                }
                catch (Exception e) {
                    Log.d("DBHelper", "Error in database creation");
                }

                databaseHelper.openDataBase();
            }
        }
        return databaseHelper;
    }

    private void copyDatabase() throws IOException {
        InputStream is = context.getResources().openRawResource(R.raw.xzqh);
        OutputStream os = new FileOutputStream(DB_PATH + DB_NAME);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }

        os.flush();
        os.close();
        is.close();
    }

    private void openDataBase() {
        try {
            db = SQLiteDatabase.openDatabase(
                    DB_PATH + DB_NAME,
                    null,
                    SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
        } catch (SQLiteException e) {
            // database does't exist yet
        }
    }

    public SimpleCursorAdapter getListByParentCode(Context context, String parentCode) {
        SimpleCursorAdapter list = null;
        DBHelper dHelper = new DBHelper(context);
        SQLiteDatabase db = dHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery(
                "select code as _id, region from xzqhdm where parent_code = ?",
                new String[] {parentCode});
        if (cursor.getCount() != 0) {
            list = new SimpleCursorAdapter(context,
                    android.R.layout.simple_spinner_item,
                    cursor,
                    new String[] {"region"},
                    new int[] {android.R.id.text1});
            list.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        }
        return list;
    }

    @Override
    public synchronized void close() {
        if (db != null) {
            db.close();
        }
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

在代码实现时遇到的难题是如何在选中region的同时得到对应的code。网上有教程说定制自己的adapter,重写bingView,不过有多个spinner就需要声明多个全局变量;还有教程指出可以直接往adapter中传递对象(实现一个类,将code和region作为字段),然后重写对象的toString()方法。后来受到这个帖子的启发,修改了rawQuery中的select语句得以实现Spinner控件中的键值绑定。
原来的select语句是:

select _id, code, region from xzqhdm where parent_code = ?

因为传入到CursorAdapter中的Cursor结果集必须包含有列名为_id的列,否则CursorAdapter将不会起作用。而code可以被看作是整数,那么只需要将选出的code当作_id就行了,根据这个想法写出的select语句如下:

select code as _id, region from xzqhdm where parent_code = ?

这样,但触发Spinner上的ItemSelected事件时就可以通过最后一个参数id得到当前的code了。

写这篇文章的时候同时也在调试着代码,突然发现其实不需要改写select语句也是可以实现键值绑定的。只要在onItemSelected()方法中使用如下代码就可以取得相应的值了:

Cursor cursor = (Cursor)parent.getSelectedItem();
if (cursor != null) {
    int code = cursor.getString(cursor.getColumnIndex("code"));
    String country = cursor.getString(cursor.getColumnIndex("region"));
}

源代码下载

参考文章:
http://lxmgfd.iteye.com/blog/970637
http://www.eoeandroid.com/thread-98254-1-1.html
http://www.cmd100.com/bbs/thread-5454-1-1.html

Android Tab使用总结

TabHost是整个Tab的容器,包括TabWidget和FrameLayout两部分。TabWidget就是每个Tab的标签,FrameLayout则是Tab内容。如果我们的Activity继承自TabAcitivty,那么TabHost的id必须设置为@android:id/tabhost,TabWidget必须设置为@android:id/tabs,FrameLayout需要设置为@android:id/tabcontent。

1. 创建一个简单的Tab应用
废话不多说,直接上代码。layout文件如下:
// main.xml

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
            <include android:id="@+id/left" layout="@layout/tab_left" />
            <include android:id="@+id/right" layout="@layout/tab_right" />
        </FrameLayout>
    </LinearLayout>
</TabHost>

// tab_left.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:text="left"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

// tab_right.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:text="right"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

修改MainActivity,改成从TabActivity继承:

public class MainActivity extends TabActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TabHost tabHost = getTabHost();
        tabHost.addTab(tabHost.newTabSpec("left")
                .setIndicator("Left")
                .setContent(R.id.left));
        tabHost.addTab(tabHost.newTabSpec("right")
                .setIndicator("Right")
                .setContent(R.id.right));
        tabHost.setCurrentTab(0);
    }
}

到这里,一个简单至极的Tab应用已经完成了。不过在实际情况中,每个Tab页会有多个控件,并且有很多业务逻辑在其中。如果使用上述代码,将所有控件和逻辑放在一个类中,
那么这个类将变的极度臃肿,以后维护也会变的非常困难。需要找个简单且优雅的方案解决这个问题。

2. 简单优雅且容易维护的Tab应用
删除main.xml中的include语句:

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    </LinearLayout>
</TabHost>

新建TabLeftActivity和TabRightActivity类:

public class TabLeftActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tab_left);

        // do something
    }
}
public class TabRightActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tab_right);

        // do something
    }
}

修改MainAcvitity为如下代码:

public class MainActivity extends TabActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TabHost tabHost = getTabHost();
        tabHost.addTab(tabHost.newTabSpec("left")
                .setIndicator("Left")
                .setContent(new Intent(this, TabLeftActivity.class)));
        tabHost.addTab(tabHost.newTabSpec("right")
                .setIndicator("Right")
                .setContent(new Intent(this, TabRightActivity.class)));
        tabHost.setCurrentTab(0);
    }
}

最后,不要忘记在AndroidManifest.xml中注册TabLeftActivity和TabRightAcvitity这两个Activity。

3. 如何将Tab显示在下方
这个非常简单,只要将main.xml中的LinearLayout改成RelativeLayout,然后给TagWidget添加android:layout_alignParentBottom=”true”属性。

创建Android自定义键盘

在使用股票应用时,我们会发现这些应用使用的是特殊的软键盘,这是如何实现的呢?下面就来做个实例详解。

注意,这篇文章不是教你如何创建输入法,如果你想创建自己的输入法,可以研究文章Creating an Input Method和Android Sample中的SoftKeyboard项目。

软键盘实现:在Android中软键盘是很容易实现的,通过android.inputmethodservice.Keyboard类来创建软键盘,该类从XML文件中读取软键盘信息。有多少行,每行有多少按键,每个按键代表什么内容等。下面是软键盘的XML代码:

<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="25%p"
    android:horizontalGap="0px"
    android:verticalGap="0px"
    android:keyHeight="@dimen/key_height">
    <Row>
        <Key android:codes="49" android:keyLabel="1" />
        <Key android:codes="50" android:keyLabel="2" />
        <Key android:codes="51" android:keyLabel="3" />
        <Key android:codes="57419"
            android:keyEdgeFlags="right"
            android:keyIcon="@drawable/sym_keyboard_left" />
    </Row>
    <Row>
        <Key android:codes="52" android:keyLabel="4" />
        <Key android:codes="53" android:keyLabel="5" />
        <Key android:codes="54" android:keyLabel="6" />
        <Key android:codes="57421"
            android:keyEdgeFlags="right"
            android:keyIcon="@drawable/sym_keyboard_right" />
    </Row>
    <Row>
        <Key android:codes="55" android:keyLabel="7" />
        <Key android:codes="56" android:keyLabel="8" />
        <Key android:codes="57" android:keyLabel="9" />
        <Key android:codes="-5"
            android:keyHeight="@dimen/key_height_large"
            android:keyEdgeFlags="right"
            android:isRepeatable="true"
            android:keyIcon="@drawable/sym_keyboard_delete" />
    </Row>
    <Row>
        <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" />
        <Key android:codes="48" android:keyLabel="0" />
        <Key android:codes="88" android:keyLabel="X" />
    </Row>
</Keyboard>

在上面的键盘定义中,Row元素说明这是一行按键的定义,Key元素说明这是一个按键的定义。Key元素通过一些属性来定义每个按键,下面是一些常用的属性介绍:

codes:代表按键对应的输出值,可以为unicode值或则逗号(,)分割的多个值,也可以为一个字符串。在字符串中通过“\\”来转义特殊字符,例如’\\n’或则’\\uxxxx’。Codes通常用来定义该键的键码,例如上图中的数字按键1对应的为49;如果提供的是逗号分割的多个值则和普通手机输入键盘一样在多个值之间切换。
keyLabel:代表按键显示的文本内容。
keyIcon:代表按键显示的图标内容,如果指定了该值则在显示的时候显示为图片不显示文本。
keyWidth:代表按键的宽度,可以为精确值或则相对值,对于精确值支持多种单位,例如:像素,英寸等;相对值为相对于基础取值的百分比,为以%或则%p结尾,其中%p表示相对于父容器。
keyHeight:代表按键的高度,取值同上。
horizontalGap:代表按键前的间隙(水平方向),取值同上。
isSticky:指定按键是否为sticky的。例如Shift大小写切换按键,具有两种状态,按下状态和正常状态,取值为true或则false。
isModifier:指定按键是否为功能键(modifier key),例如Alt或则Shift,取值为true或则false。
keyOutputText:指定按键输出的文本内容,取值为字符串。
isRepeatable:指定按键是否是可重复的,如果长按该键可以触发重复按键事件则为true,否则为false。
keyEdgeFlags:指定按键的对齐指令,取值为left或则right。

然后在main.xml文件末尾加入以下代码:

<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
    <android.inputmethodservice.KeyboardView
        android:id="@+id/keyboard_view"
        android:visibility="gone"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true" />
</RelativeLayout>

下面是主要的处理代码:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        EditText edit = (EditText)findViewById(R.id.edit);
        edit.setInputType(InputType.TYPE_NULL);
        edit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showKeyboard();
            }
        });

        KeyboardView keyboardView = (KeyboardView)findViewById(R.id.keyboard_view);
        keyboardView.setKeyboard(new Keyboard(this, R.xml.qwerty));
        keyboardView.setEnabled(true);
        keyboardView.setPreviewEnabled(true);
        keyboardView.setOnKeyboardActionListener(new OnKeyboardActionListener() {
            @Override
            public void onKey(int primaryCode, int[] keyCodes) {
                Editable editable = edit.getText();
                int start = edit.getSelectionStart();
                if (primaryCode == Keyboard.KEYCODE_CANCEL) {
                    hideKeyboard();
                } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
                    if (editable != null && editable.length() > 0) {
                        editable.delete(start - 1, start);
                    }
                } else if (primaryCode == 57419) { // go left
                    if (start > 0) {
                        edit.setSelection(start - 1);
                    }
                } else if (primaryCode == 57421) { // go right
                    if (start < edit.length()) {
                        edit.setSelection(start + 1);
                    }
                } else {
                    editable.insert(start, Character.toString((char)primaryCode));
                }
            }
            ...
        });
    }

    private void showKeyboard() {
        int visibility = keyboardView.getVisibility();
        if (visibility == View.GONE || visibility == View.INVISIBLE) {
            keyboardView.setVisibility(View.VISIBLE);
        }
    }

    private void hideKeyboard() {
        int visibility = keyboardView.getVisibility();
        if (visibility == View.VISIBLE) {
            keyboardView.setVisibility(View.INVISIBLE);
        }
    }
}