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卡中,然后再打开。
import android.app.Activity; import android.database.Cursor; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.Spinner; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.SpinnerAdapter; import android.widget.Toast; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); loadSpinner(); } private void loadSpinner() { Spinner provinceSpinner = (Spinner)findViewById(R.id.province_spinner); provinceSpinner.setPrompt("请选择省份"); provinceSpinner.setAdapter(getSpinnerAdapter(999999)); provinceSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Spinner citySpinner = (Spinner)findViewById(R.id.city_spinner); citySpinner.setPrompt("请选择城市"); citySpinner.setAdapter(getSpinnerAdapter(id)); citySpinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Spinner countySpinner = (Spinner)findViewById(R.id.county_spinner); countySpinner.setPrompt("请选择县区"); countySpinner.setAdapter(getSpinnerAdapter(id)); countySpinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Cursor cursor = (Cursor)parent.getSelectedItem(); if (cursor != null) { String country = cursor.getString(cursor.getColumnIndex("region")); Toast.makeText(MainActivity.this, country + " " + id, Toast.LENGTH_LONG).show(); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } private SpinnerAdapter getSpinnerAdapter(long code) { DBHelper helper = DBHelper.getInstance(this); SpinnerAdapter adapter = helper.getListByParentCode(this, String.valueOf(code)); helper.close(); return adapter; } }
import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import android.widget.SimpleCursorAdapter; 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")); }