乐者为王

Do one thing, and do it well.

使用showDialog()创建的ProgressDialog再次打开时进度条不变化

先来看段代码:

1
2
3
4
5
6
7
8
9
10
11
<?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"
    android:orientation="vertical">
    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Copy Contacts"
        android:onClick="copyContacts" />
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;

public class DialogActivity extends Activity {
    private static final int COPY_CONTACTS = 0;

    private ProgressDialog mProgressDialog;

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

    public void copyContacts(View target) {
        showDialog(COPY_CONTACTS);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
        case COPY_CONTACTS:
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setTitle("Copy contacts");
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressDialog.setMax(100);
            new AsyncCopyContactsTask().execute();
            break;
        }
        return mProgressDialog;
    }

    private class AsyncCopyContactsTask extends AsyncTask<Void, Void, Integer> {
        @Override
        protected Integer doInBackground(Void... params) {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                publishProgress();
            }
            return 0;
        }

        protected void onProgressUpdate(Void... progress) {
            mProgressDialog.incrementProgressBy(1);
        }

        protected void onPostExecute(Integer result) {
            mProgressDialog.dismiss();
        }
    }
}

第一次点击按钮会显示一个进度条,上面的数字处于变动当中,到达最大值后进度条会消失不见;再次点击按钮后,就会发现进度条上的数值和消失前相同,并且不再变化,而且进度条也不会消失。这是为什么呢?

Activity中有三个和对话框显示有关的方法:showDialog(),dismissDialog()和removeDialog()。其中showDialog()用于显示一个对话框;dismissDialog()使对话框消失,但仍然处于内存中,只是不显示而已,如果再次调用showDialog()方法,则缓存在内存中的对话框会重新显示,而不需要重新创建。removeDialog()使对话框消失,并从内存中将对话框清除,如果再次调用showDialog()来显示它,则在显示之前需要重新创建对话框。

在一个对话框的声明周期内,onCreateDialog()仅被调用一次,但onPrepareDialog()方法会每次被调用。比如,第一次调用 showDialog()方法显示某个对话框时,会先触发onCreateDialog()的执行,然后再触发onPrepareDialog()方法;如果将某个对话框 removeDialog()后再showDialog(),也会先触发onCreateDialog(),接着触发onPrepareDialog()方法;如果某个对话框是被dismiss()掉的,再调用 showDialog()方法来显示它时就不会触发onCreateDialog()方法的执行,只会触发onPrepareDialog()方法。

这是因为第一次调用showDialog()时,Android从你的Activity中调用onCreateDialog()方法,得到返回的Dialog对象。把当前Activity设置为该对话框的所有者,从而把对话框挂靠到Activity上,让Activity自动管理该对话框的状态。这样,下次调用showDialog()时就不用重新创建Dialog对象,而是复用旧的。

所以,解决再次打开ProgressDialog时进度条不变化的最好方法就是将mProgressDialog.dismiss()改成removeDialog(COPY_CONTACTS)。

注意:如果你决定在onCreateDialog()方法之外创建一个对话框,它将不会被附着到活动上。不过,你可以通过setOwnerActivity()把它附着到一个Activity上。

史蒂夫·乔布斯的辞职信

英文原文:http://www.apple.com/pr/library/2011/08/24Letter-from-Steve-Jobs.html

致苹果董事会和苹果社区:

我一直说,如果有一天我不能履行作为苹果CEO的职责和期望,我会第一个让你们知道。遗憾的是,这一天已经来临。

我在此辞去苹果CEO的职务。如果董事会同意,我将愿意担任董事长、董事或者苹果员工。

就我的继任者而言,我强烈建议执行我们的接班计划,任命Tim Cook为苹果的CEO。

我相信,苹果最辉煌和最具创新的日子就在前方。我期望在新的岗位看到它的成功,并为此做出自己的贡献。

在苹果,我结交了一些我一生中最好的朋友。谢谢你们大家,多年来能和你们一起工作。

史蒂夫

《货币战争》是如何混淆读者视听的

这是早就写好了的。《货币战争》刚发行就买过来读,结果看到三分之一的时候就看不下去,准备写篇文章驳斥书中的观点。下面是列出的提纲,不过因为文笔太差的缘故,一直没能把内容扩充起来,到现在也没了这个心思。这里把已经写的贴出来给大家看看吧。

  1. 读者们喜欢阴谋论;
  2. 美联储是私有的从来就不是什么秘密。因为金融机构和公有还是私有无关,而是和财力及信用有关;
  3. 偷换概念,将金融家们的被动行为说成是主动行为,造成一种金融家们无所不能的环境;
  4. 放大作者想写的,缩小作者不想说的。只要是美国总统遇刺身亡就说是不符合金融家们的利益导致,那为什么不说别的国家呢?不要忘记军队掌握在谁的手中,给你个叛国罪啊什么的就能叫金融家们玩完,况且国家还有那么多秘密机构呢!想想希特勒时期的罗斯柴尔德家族吧。

货币战争真实存在,亚洲金融风暴、索罗斯狙击英镑这些都是货币战争。金融家们能调动数亿万计的财富,确实也很强大,但是不应该把他们的能力无限放大。

对美联储性质感兴趣的可以到它的官方网站看看。这里还有篇评论宋鸿兵和他书的文章:http://wenyidaobk.blog.163.com/blog/static/1243789052009712928966/

工作日志:从拒绝到接受再到喜欢

刚毕业,在苏州一家公司工作,需要写工作日志,心里不喜欢,但不得不写,每天应付了事。现在想来大概是因为不知道写什么,怎么写的缘故(自觉语言贫乏,感觉好多话要说,但写出来却是干巴巴的几句,所以对一切需要动笔的如思想汇报、工作小结、年终总结等都特别不乐意,更别提上学时候的写作文了)。

到第二家公司,刚开始不要写,后来公司作了调整就要写了,这时是不再拒绝,以一种无所谓的态度接受了。

在第三家后,也是开始不要写,后来调整要写,不过由于有具体的日志格式,所以对这个还是非常接受的。

今天凌晨醒来后睡不着,在床上胡思乱想,突然想到工作日志的事。从管理人员的角度思考了工作日志的问题,有了一些新的想法,发现工作日志其实是管理人员了解工作进度的这么一个东西。在一个项目组里,项目经理可以通过你每天的工作日志对你今天的工作有一个大致的了解,综合所有小组成员的工作日志就可以对项目所处的状态有个大约的概念。否则,每天只见你上班下班,坐在那里做事,但不知道你做了什么,做的怎么样,对项目状态不了解,项目就非常容易失控。

工作日志格式:

1
2
3
4
5
6
7
[main content]
今天做了什么,完成度怎么样?有哪些问题?
明天的计划是什么?

[questions]

[suggestions]

我觉得明天的计划这一项非常重要,通过明天的计划以及当天工作完成的情况我们就可以了解我们制定的计划是否合理有效。如果每天制定的计划都不能按时完成,那么说明自己对自己的能力估计过高,需要降低期望值。而项目经理可以通过某个组员一段时间内工作计划的完成度来了解组员的工作状态,如果组员一直处于计划不能按时完成状态,那么就要考虑是否要与组员沟通这个情况,帮助组员解决情况。

为什么要有个工作日志系统,而不是采用邮件的方式?

  1. 有文字记录(这个邮件也可以做到);
  2. 日志集中放置(这个邮件无法做到);
  3. 可以通过分析日志得出一些有用的统计(这个邮件无法做到)。

2014/12/23更新

其实把日志格式中的“今天”改成“昨天”,“明天”改成“今天”就是每日例会的模板了。

如何破解华为HG526电信猫

把家里的电信从旧的4M套餐改为新的4M套餐,每月费用省了40多块,还送了个华为的HG526电信猫和iTV。拿到HG526后发现它带有无线路由功能,想着是不是可以让它自动拨号,以替换原来的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 Developer插件来做到。在安装了Web Developer插件的Firefox中访问路由器,用telecomadmin登录,进入“网络” -> “宽带设置”,在页面中点击右键,在Web Developer工具栏中选择“表单” -> “开启 被禁用的表单”。然后你就可以删除TR069协议了。远程管理的修改与之类似。

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

HG526配置文件加/解密工具下载:https://github.com/dohkoos/hw-hg526

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

英文原文:http://www.pickthebrain.com/blog/grow-the-action-habit/

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

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

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

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

2. 做个实干家

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

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

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

4. 用行动去克服恐惧

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

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

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

6. 先顾眼前

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

7. 立即切入正题

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

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

实现Android底部工具栏

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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的布局代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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界有这么一句话:遇到重复的Maven项目初始配置,就创建自己的archetype。其实不光是Maven项目,其它也是如此。《测试驱动开发》中有个Triangulation法则,《重构》中也有Rule of three,都是用来指导如何解决类似重复问题的。第一次是特殊解决,第二次还是特殊解决,第三次就要抽象解决了。

Maven Archetype Plugin允许从当前存在的项目创建archetype,这样以后用户就可以基于该archetype创建项目了。

那么如何通过现有的项目创建archetype呢?首先清理项目中那些不必要的文件和目录,然后在根目录下执行:

1
2
3
mvn archetype:create-from-project
cd target/generated-sources/archetype
mvn clean install  # 本地安装

现在就可以使用上面创建的archetype来建立新项目了。在新的目录中执行以下命令即可:

1
2
3
4
5
6
7
8
mvn archetype:generate                          \
  -DarchetypeCatalog=local                      \
  -DarchetypeGroupId=<archetype-groupId>        \
  -DarchetypeArtifactId=<archetype-artifactId>  \
  -DgroupId=<your-groupId>                      \
  -DartifactId=<your-artifactId>                \
  -Dpackage=<your-package>                      \
  -Dversion=1.0

这里要注意的是,不要在target/generated-sources/archetype目录下运行上述命令,否则会生成失败,报如下错误:

1
2
3
[ERROR] Failed to execute goal archetype:generate:
  org.apache.maven.archetype.exception.InvalidPackaging:
  Unable to add module to the current project as it is not of packaging type 'pom'

在Rails中使用Open Flash Chart II

今天我们讲如何用Rails结合Open Flash Chart II(以下简称OFC2)实现如下的图表:

在项目中安装OFC2插件,生成相关控制器:

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

在生成的app/controllers/stats_controller.rb中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
    render :text => chart.to_s
  end
end

在config/routes.rb中添加路由:

1
match 'stats/graph_code' => 'stats#graph_code'

将插件中的assets/javascripts/swfobject.js拷贝到public/javascripts目录下(可以看到该目录下已经有两个open-flash-chart开头的swf文件,如果没有的话,需要从插件中拷过来)。在相应的app/views/stats/index.html中添加如下代码:

1
2
3
<script src="/javascripts/swfobject.js" type="text/javascript"></script>

<%= @graph %>

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

建立省市县行政区划代码数据表。

1
2
3
4
5
6
CREATE TABLE xzqhdm (
    _id integer PRIMARY KEY,
    code numeric,
    region text,
    parent_code numeric
);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

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目录中的数据库文件?不能直接打开,需要在程序第一次启动时将该文件复制到手机内存或SD卡中后再打开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class DBHelper extends SQLiteOpenHelper {
    private static String DB_PATH = "/data/data/com.codemany.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,覆写bindView()方法,不过有多个Spinner就需要声明多个全局变量;还有教程指出可以直接往Adapter中传递对象(实现一个类,将code和region作为字段),然后覆写对象的toString()方法。后来受到这个帖子的启发,修改了rawQuery中的select语句得以实现Spinner控件中的键值绑定。

原来的查询语句是:

1
SELECT _id, code, region FROM xzqhdm WHERE parent_code = ?

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

1
SELECT code as _id, region FROM xzqhdm WHERE parent_code = ?

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

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

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

代码下载