Android软件开发中为什么要制作第三方数据库?在程序中获取数据的方式无非就是两种:本地获取,服务器获取。如果项目中的数据非常庞大,并且又不能使用联网获取数据的方式,那么就需要采取制作第三方数据库的方式。我用一个比较实际的例子向大家说明这一点,最近我制作了一个来电归属地查询的小DEMO,产品的需求是对方打过来电话后能再屏幕中现实对方手机号码的归属地。Android提供了联网获取归属地的方式,但是我没有采取这样的方式获取号码归属地。因为联网获取号码归属地时间实在无法控制,有可能用户已经接通电话数据才获取到,那么来电归属地的意义就不存在了。并且联网获取数据还必需是在有网络的情况下,所以限制太多了,所以我放弃的联网获取手机号码归属地的方法。
接着我在网络中找了一份07年手机号码归属地的数据库原始资源。它是以TXT形式包含中国所有省份与城市的号码归属地规则,如下图所示,打开上海所在的归属地文件后。发现归属的规则是截取手机号码前6位判断手机号码所在的归属地。归属地数据库的原始资源包括移动号码与联通号码,加起来一共有61个文本文件。我们需要编写程序这些数据需要全部写入数据库中,然后将生成的数据库文件放入需要查询的新工程中。这么做的原因是归属地数据库的数据量比较大,如果在查询的工程中写入数据库的话至少也要20分钟。这样会造成用户安装完软件后,第一个电话打进来时有可能你的数据库还没写完,用户体验大打折扣。所以我们需要在第三方程序中将这个庞大的数据库先制作出来,然后在放入软件中,最后直接操作这个数据库即可。
07年的数据确实有点老,但是本篇博文的重点不是探讨手机号码归属地。我最终的涉及思路是将.db文件放置在服务器中,服务器端来维护这个db文件,用户首次安装时从服务器下载这个数据库文件,说的有点远了 呵呵。下面学习如何将归属地数据库中庞大的原始数据写入数据库当中,首先需要制作生成数据库的程序。如下图所示,本程序的是方法为点击“开始读取资源写入数据库”按钮后,程序将循环开始在本地读取所有原始资源,截取出需要的数值后在写入归属地数据库当中。
上图对应的Activity是GenerateDBActivity,代码如下所示:
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.UnsupportedEncodingException;
- import android.app.Activity;
- import android.content.res.Resources;
- import android.content.res.Resources.NotFoundException;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- import android.widget.Toast;
- public class GenerateDBActivity extends Activity
- {
- //DB对象
- public DBHelper m_db = null;
- //按钮对象
- public Button button = null;
- //文本对象
- public TextView textNumber = null;
- public TextView textCity = null;
- public TextView textLocation = null;
- //接收线程中传递的数值,用于刷新UI
- Handler handler = new Handler() {
- //注解3
- @Override
- public void handleMessage(Message msg) {
- Bundle bundle = msg.getData();
- //获取号码、城市、省级
- String number = bundle.getString("number");
- String city = bundle.getString("city");
- String location = bundle.getString("location");
- //更新UI
- textNumber.setText(number);
- textCity.setText(city);
- textLocation.setText(location);
- super.handleMessage(msg);
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // 获取DBHelper对象
- m_db = DBHelper.getInstance(this);
- //获取按钮对象
- button = (Button)this.findViewById(R.id.button);
- button.setText("开始读取资源写入数据库");
- //获取文本对象
- textNumber = (TextView)this.findViewById(R.id.number);
- textCity = (TextView)this.findViewById(R.id.city);
- textLocation = (TextView)this.findViewById(R.id.location);
- //开始读取资源
- button.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
- {
- //注解1
- //启动线程开始读取资源并写入数据库当中
- new Thread()
- {
- @Override
- public void run()
- {
- int count = DBHelper.LOCATIONS.length;
- for(int i =0; i < count; i++)
- {
- loadLocationFile(i);
- }
- }
- }.start();
- }
- });
- }
- private void loadLocationFile(int resourceID)
- {
- //注解2
- Resources res = this.getResources();
- InputStream in = null;
- BufferedReader br = null;
- try
- {
- in = res.openRawResource(R.raw.location_a0 + resourceID);
- String str;
- br = new BufferedReader(new InputStreamReader(in, "GBK"));
- while ((str = br.readLine()) != null)
- {
- //手机号码特征值为前7位
- String number = str.substring(0, 6);
- //手机号码归属地从第24位开始到最后
- String city = str.substring(24, str.length());
- //将数据写入数据库当中
- m_db.insert(DBHelper.TABLE_NAME,
- new String[] { DBHelper.NUMBER, DBHelper.CITY, DBHelper.LOCATION },
- new String[]{ number, city, DBHelper.LOCATIONS[resourceID] });
- Message msg = new Message();
- Bundle bundle = new Bundle();
- bundle.putString("number", "手机号码特征数据:" + number);
- bundle.putString("city", "手机号归属地城市:" + city);
- bundle.putString("location", "手机号码省级运营商:" + DBHelper.LOCATIONS[resourceID]);
- msg.setData(bundle);
- handler.sendMessage(msg);
- Log.v("xuanyusong", "手机号码特征数据:" + number + " 手机号归属地城市:" + city + " 手机号码省级运营商:" + DBHelper.LOCATIONS[resourceID]);
- }
- } catch (NotFoundException e)
- {
- Toast.makeText(this, "文本文件不存在", 100).show();
- e.printStackTrace();
- } catch (UnsupportedEncodingException e)
- {
- Toast.makeText(this, "文本编码出现异常", 100).show();
- e.printStackTrace();
- } catch (IOException e)
- {
- Toast.makeText(this, "文件读取错误", 100).show();
- e.printStackTrace();
- } finally
- {
- try
- {
- if (in != null)
- {
- in.close();
- }
- if (br != null)
- {
- br.close();
- }
- } catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- }
- }
注解1:在窗口中用户点击“读取写入数据库”按钮后,程序将开启一个线程来读取资源,而且代码必需写在线程中读取,因为数据量比较大,主线程中读取会出现ANR的情况。DBHelper.LOCATIONS.Length表示原始数据资源文件的数量,这里使用循环将读取所有原始资源文件。
注解2:loadLocationFile()方法开始读取本地原始资源,原始资源全部放置在res/raw下,使用openRawResource()方法取得每一个原始文件的流对象。然后在while循环中调用br.readLine()方法逐行读取文本对象中的数据,数据读取完毕后将它们写入数据库当中。因为这里是线程,所以需要刷新UI时就得使用Handler了。
注解3:数据库中每插入一条数据库使用handler发来一条消息,在这里获取该消息附带的参数,然后刷新UI将数据现实在屏幕中。
数据库的创建与插入数据的方法写在 DBHelper类当中,代码如下所示:
- package com.m15.cn;
- import android.content.ContentValues;
- import android.content.Context;
- import android.database.sqlite.SQLiteDatabase;
- import android.database.sqlite.SQLiteOpenHelper;
- public class DBHelper extends SQLiteOpenHelper
- {
- public static DBHelper mInstance = null;
- /** 数据库名称 **/
- public static final String DATABASE_NAME = "location.db";
- /** 数据库版本号 **/
- private static final int DATABASE_VERSION = 1;
- /** DB对象 **/
- SQLiteDatabase mDb = null;
- Context mContext = null;
- // 归属地
- public final static String TABLE_NAME = "location_date";
- public final static String ID = "_id";
- public final static String NUMBER = "number";
- public final static String LOCATION = "location";
- public final static String CITY = "city";
- // 索引ID
- public final static int ID_INDEX = 0;
- public final static int NUMBER_INDEX = 1;
- public final static int LOCATION_INDEX = 2;
- public final static int CITY_INDEX = 3;
- /** 数据库SQL语句 创建归属地表 **/
- public static final String NAME_TABLE_CREATE = "create table location_date(" + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + "number TEXT NOT NULL," + "city TEXT NOT NULL,"
- + "location TEXT NOT NULL);";
- public final static String[] LOCATIONS =
- { "上海移动", "上海联通", "云南移动", "云南联通", "内蒙古移动", "内蒙古联通", "北京移动", "北京联通", "吉林移动", "吉林联通", "四川移动", "四川联通", "天津移动", "天津联通", "宁夏移动", "宁夏联通", "安徽移动", "安徽联通", "山东移动", "山东联通", "山西移动", "山西联通", "广东移动", "广东联通",
- "广西移动", "广西联通", "新疆移动", "新疆联通", "江苏移动", "江苏联通", "江西移动", "江西联通", "河北移动", "河北联通", "河南移动", "河南联通", "浙江移动", "浙江联通", "海南移动", "海南联通", "湖北移动", "湖北联通", "湖南移动", "湖南联通", "甘肃移动", "甘肃联通", "福建移动",
- "福建联通", "西藏移动", "西藏联通", "贵州移动", "贵州联通", "辽宁移动", "辽宁联通", "重庆移动", "重庆联通", "陕西移动", "陕西联通", "青海移动", "青海联通", "黑龙江移动", "黑龙江联通" };
- /** 单例模式 **/
- public static synchronized DBHelper getInstance(Context context)
- {
- if (mInstance == null)
- {
- mInstance = new DBHelper(context);
- }
- return mInstance;
- }
- public DBHelper(Context context)
- {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- // 得到数据库对象
- mDb = getReadableDatabase();
- mContext = context;
- }
- @Override
- public void onCreate(SQLiteDatabase db)
- {
- //创建数据库
- db.execSQL(NAME_TABLE_CREATE);
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
- {
- }
- /**
- * 插入一条数据
- *
- * @param key
- * @param date
- */
- public void insert(String tablename, String key[], String date[])
- {
- ContentValues values = new ContentValues();
- for (int i = 0; i < key.length; i++)
- {
- values.put(key[i], date[i]);
- }
- mDb.insert(tablename, null, values);
- }
- }
数据库写入完毕后,然后将生成的数据库从程序中拷贝至本地。如下图所示,将本例程序的location.db文件拷贝至电脑中。
然后使用数据库查看软件,来看一看我们生成的数据库。因为我这里使用的是mac在做开发,所以和以前写用的数据库查看软件有点不一样。如下图所示,本地数据已经写入到数据库当中 , 下面说一下字段的含义:_id为升序具有唯一性 , number 表示号码的前6位字段,city表示号码归属地的城市,location:表示省级的运营商归属地。那么到这一步我们的第三方数据库文件就已经制作完毕。如果有朋友问能不能使用非Android程序生成的数据库,那么我建议最好不要用。或者你将系统生成的表android_metadata与sqlite_sequence表添加进你的数据库试一试,因为这个数据库一定要与Android生成出来的数据库结构一样。否则在部分Android手机上无法打开它db文件的对象。
接着创建一个新android工程,用于我们查询数据库。首先将上面工程中生成的归属地数据库文件location.db拷贝至新工程的raw文件夹中。在如下代码中开始载入数据库中的内容。
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.InputStream;
- import android.app.Activity;
- import android.content.Context;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.TextView;
- import android.widget.Toast;
- public class GetInfoActivity extends Activity
- {
- //文件的路径
- public final static String URL = "/data/data/cn.location.xys/files";
- //数据库文件
- public final static String DB_FILE_NAME = "location.db";
- // 归属地
- public final static String TABLE_NAME = "location_date";
- // 索引ID
- public final static int ID_INDEX = 0;
- public final static int NUMBER_INDEX = 1;
- public final static int LOCATION_INDEX = 2;
- public final static int CITY_INDEX = 3;
- EditText editText = null;
- Button button = null;
- TextView textView = null;
- SQLiteDatabase db = null;
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.getinfo);
- //首先将DB文件拷贝至程序内存当中
- //注解1
- if (copyDB())
- {
- //得到数据库文件
- File file = new File(URL, DB_FILE_NAME);
- db = SQLiteDatabase.openOrCreateDatabase(file, null);
- editText = (EditText) this.findViewById(R.id.edit);
- textView = (TextView) this.findViewById(R.id.location);
- button = (Button) this.findViewById(R.id.button);
- button.setOnClickListener(new OnClickListener()
- {
- public void onClick(View v)
- {
- String editStr = editText.getText().toString();
- if (editStr.length() == 11)
- {
- //输入手机号码
- Cursor cursor = db.query("location_date", null, "number=?", new String[]{ editStr.substring(0, 6) }, null, null, null);
- if (cursor != null && cursor.moveToFirst())
- {
- //获取号码省级与市级归属地
- String text = cursor.getString(LOCATION_INDEX) + cursor.getString(CITY_INDEX);
- textView.setText(text);
- }
- else
- {
- Toast.makeText(getApplicationContext(), "未能在数据库中查询到您的手机号码", Toast.LENGTH_SHORT).show();
- }
- } else
- {
- Toast.makeText(getApplicationContext(), "手机号码为11位,重新输入", Toast.LENGTH_SHORT).show();
- }
- }
- });
- }
- }
- // 将raw文件中的数据库文件拷贝至手机中的程序内存当中
- public boolean copyDB()
- {
- try
- {
- // 判断程序内存中是否有拷贝后的文件
- if (!(new File(URL)).exists())
- {
- InputStream is = getResources().openRawResource(R.raw.location);
- FileOutputStream fos = this.openFileOutput(DB_FILE_NAME, Context.MODE_WORLD_READABLE);
- // 一次拷贝的缓冲大小1M
- byte[] buffer = new byte[1024 * 1024];
- int count = 0;
- // 循环拷贝数据库文件
- while ((count = is.read(buffer)) > 0)
- {
- fos.write(buffer, 0, count);
- }
- fos.close();
- is.close();
- }
- return true;
- } catch (Exception e)
- {
- e.printStackTrace();
- return false;
- }
- }
- }
注解1:首先将raw文件夹中的数据库文件拷贝至程序内存当中,接着通过拷贝后的路径获取数据库文件的对象。有了数据库对象那么增、删、改、查的操作都可以执行啦。最后的效果图如下所示,输入手机号码后点击查询,下方将现实手机号码的归属地信息。
源码下载地址:http://vdisk.weibo.com/s/aa7gG
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。