今天进入新的功能开发了:通讯卫士,主要用于手机电话、短信的黑名单设置与拦截,如果顺利的话,大概分两期就能够写完,今天的任务主要是黑名单数据库的创建与存储,涉及到 SQLite 数据库操作,ListView 操作,以及 Adapter 的优化。先给大家看看效果图:
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe
创建类 BlackNumberOpenHelper,继承自 SQLiteOpenHelper,该类用于创建数据库,代码很简单,如下:
/** * 黑名单数据库创建 */
public class BlackNumberOpenHelper extends SQLiteOpenHelper {
public BlackNumberOpenHelper(Context context) {
super(context, "blacknumber.db", null, 1);
}
// 数据库第一次创建时调用
@Override
public void onCreate(SQLiteDatabase db) {
// 两个字段number:电话号码, mode: 拦截模式 , 1 拦截电话, 2 拦截短信, 3 拦截全部
String sql = "create table blacknumber (_id integer primary key autoincrement, number varchar(20), mode integer)";
db.execSQL(sql);
}
// 数据库更新时调用
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
注释写的很清楚不需要我多说什么了。我们在构造方法中写死了数据库文件的名称以及数据库版本号,在 onCreate() 中创建数据表 blacknumber,表名固定。接下来,就是对该表进行 crud 操作了,我也将它封装成简单的类,并提供我们需要的接口,代码如下:
/** * 黑名单数据库封装 crud create remove update delete * * Created by XWdoor on 2016/3/16 016 14:25. * 博客:http://blog.csdn.net/xwdoor */
public class BlackNumberDao {
private static BlackNumberDao sInstance = null;// 懒汉模式
private BlackNumberOpenHelper mHelper;
private BlackNumberDao(Context ctx) {
mHelper = new BlackNumberOpenHelper(ctx);
}
public static BlackNumberDao getInstance(Context ctx) {
if (sInstance == null) {
// A, B
synchronized (BlackNumberDao.class) {
if (sInstance == null) {
sInstance = new BlackNumberDao(ctx);
}
}
}
return sInstance;
}
/** * 增加黑名单 * * @param number * @param mode */
public void add(String number, int mode) {
SQLiteDatabase database = mHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("number", number);
values.put("mode", mode);
database.insert("blacknumber", null, values);
database.close();
}
/** * 删除黑名单 * * @param number */
public void delete(String number) {
SQLiteDatabase database = mHelper.getWritableDatabase();
database.delete("blacknumber", "number=?", new String[] { number });
database.close();
}
/** * 修改黑名单拦截模式 * * @param number * @param mode */
public void update(String number, int mode) {
SQLiteDatabase database = mHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("mode", mode);
database.update("blacknumber", values, "number=?",
new String[] { number });
database.close();
}
/** * 查询黑名单 * * @param number */
public boolean find(String number) {
SQLiteDatabase database = mHelper.getWritableDatabase();
Cursor cursor = database
.query("blacknumber", new String[] { "number", "mode" },
"number=?", new String[] { number }, null, null, null);
boolean exist = false;
if (cursor.moveToFirst()) {
exist = true;
}
cursor.close();
database.close();
return exist;
}
/** * 查询黑名单拦截模式 * * @param number */
public int findMode(String number) {
SQLiteDatabase database = mHelper.getWritableDatabase();
Cursor cursor = database.query("blacknumber", new String[] { "mode" },
"number=?", new String[] { number }, null, null, null);
int mode = -1;
if (cursor.moveToFirst()) {
mode = cursor.getInt(0);
}
cursor.close();
database.close();
return mode;
}
/** * 查询全部黑名单 */
public ArrayList<BlackNumberInfo> findAll() {
SQLiteDatabase database = mHelper.getWritableDatabase();
Cursor cursor = database.query("blacknumber", new String[] { "number",
"mode" }, null, null, null, null, null);
ArrayList<BlackNumberInfo> list = new ArrayList<BlackNumberInfo>();
while (cursor.moveToNext()) {
BlackNumberInfo info = new BlackNumberInfo();
String number = cursor.getString(0);
int mode = cursor.getInt(1);
info.number = number;
info.mode = mode;
list.add(info);
}
cursor.close();
database.close();
return list;
}
/** * 分页查找数据 * * @param index 查询起始位置 * @return */
public ArrayList<BlackNumberInfo> findPart(int index) {
SQLiteDatabase database = mHelper.getWritableDatabase();
// select number, mode from blacknumber limit 0,20;
Cursor cursor = database
.rawQuery(
"select number, mode from blacknumber order by _id desc limit ?,20",
new String[] { index + "" });// 逆序排列
ArrayList<BlackNumberInfo> list = new ArrayList<BlackNumberInfo>();
while (cursor.moveToNext()) {
BlackNumberInfo info = new BlackNumberInfo();
String number = cursor.getString(0);
int mode = cursor.getInt(1);
info.number = number;
info.mode = mode;
list.add(info);
}
cursor.close();
database.close();
return list;
}
/** * 获取数据总数 */
public int getTotalCount() {
SQLiteDatabase database = mHelper.getWritableDatabase();
Cursor cursor = database.rawQuery("select count(*) from blacknumber",
null);
int totalCount = 0;
if (cursor.moveToFirst()) {
totalCount = cursor.getInt(0);
}
cursor.close();
database.close();
return totalCount;
}
}
BlackNumberDao 类采用单例模式对外提供服务,主要接口有:增加黑名单、删除黑名单、查询黑名单拦截模式、查询全部黑名单、分页查找数据、获取数据总数。每个接口的实现步骤都差不多,显示执行 sql 语句得到查询/执行结果,然后遍历结果,返回列表。
抱歉,请原谅我拙劣的英语水平,采用了英语汉拼的方式命名,你说什么,不是我的独创,欧~ No!由于篇幅有限,就不贴出布局文件代码了,可以去 github:上查看源代码。接下来我们说说加载数据的事,进入 Activity 后就需要加载黑名单数据,代码如下:
/** * 加载黑名单 */
private void loadBlackNumber() {
pbLoading.setVisibility(View.VISIBLE);
isLoading = true;
new Thread() {
@Override
public void run() {
if (mList == null) {
//添加模拟数据
if (mNumberDao.getTotalCount() == 0)
addMockData();
// 加载第一页数据
mList = mNumberDao.findPart(0);// 20条数据
} else {
// 给集合添加一个集合, 集合相加
mList.addAll(mNumberDao.findPart(mList.size()));
}
//更新列表数据
mHandler.sendEmptyMessage(0);
}
}.start();
}
读取数据是一个耗时操作,所以放到了线程中运行,但是加载数据的时候会更新我们的 ListView,我们需要操作 UI,所以要 Handler 来帮忙,在 initVariables() 方法中初始化 Handler,代码如下:
@Override
public void initVariables() {
mNumberDao = BlackNumberDao.getInstance(this);
//初始化 Handler
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (mAdapter == null) {// 第一页
mAdapter = new BlackNumberAdapter(BlackNumberActivity.this, mList, mNumberDao);
// 给listview设置数据
lvList.setAdapter(mAdapter);// 这个方法导致数据默认跑到第0个位置
} else {
//更新列表数据
mAdapter.notifyDataSetChanged();
}
pbLoading.setVisibility(View.GONE);
isLoading = false;
}
};
}
在 Handler 的消息处理中,因为有分页加载的需求,所以需要判断:若是第一次加载,就给 ListView 设置 Adapter,否则,就只是更新列表,发出通知即可。然后就是给 ListView 添加滚动事件的监听,以便判断何时加载下一页数据,代码如下:
lvList.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
// 获取最后一个可见item的位置
int lastPosition = lvList.getLastVisiblePosition();
// 判断是否滑动到最后一个
if (lastPosition >= mList.size() - 1 && !isLoading) {
//读取数据库中数据的总数
int totalCount = mNumberDao.getTotalCount();
if (mList.size() >= totalCount) {
//说明没有更多的数据了
showToast("没有更多的数据了");
return;
}
// 加载下一页
loadBlackNumber();
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
我感觉这是有史以来最详细的注释了,主要思路是:首先判断当前最后一个可见 Item 是否是 ListView 的最后的一个 Item,若是,则加载下一页数据,否则,就不做处理。
Android 中最厉害的控件当属 ListView,它的厉害在于,它是最多用、最复杂、最多变、最容易内存泄漏、最难伺候的主了,由于这里没有加载图片,所以就不说图片的事了,今天就讲讲 Adapter 的优化工作,当然,是比较常用的、简单的优化,代码如下:
/** * Created by XWdoor on 2016/3/16 016 14:30. * 博客:http://blog.csdn.net/xwdoor */
public class BlackNumberAdapter extends BaseAdapter {
ArrayList<BlackNumberInfo> mList;
Context mContext;
BlackNumberDao mNumberDao;
public BlackNumberAdapter(Context ctx, ArrayList<BlackNumberInfo> list, BlackNumberDao numberDao) {
this.mContext = ctx;
this.mList = list;
this.mNumberDao = numberDao;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public BlackNumberInfo getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//listview优化
// 1. 使用convertView,重用对象,保证对象不被创建多次
// 2. 使用ViewHolder,减少findViewById的次数
// 3. 将ViewHolder改为static, 内存只加载一次
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
if(convertView == null) {
view = View.inflate(mContext, R.layout.item_black_number_adapter, null);
holder = new ViewHolder();
holder.tvNumber = (TextView) view.findViewById(R.id.tv_number);
holder.tvMode = (TextView) view.findViewById(R.id.tv_mode);
holder.ivDelete = (ImageView) view.findViewById(R.id.iv_delete);
// 将holder对象和view绑定在一起
view.setTag(holder);
}else {
view = convertView;
holder = (ViewHolder) view.getTag();
}
final BlackNumberInfo info = getItem(position);
holder.tvNumber.setText(info.number);
switch (info.mode) {
case 1:
holder.tvMode.setText("拦截电话");
break;
case 2:
holder.tvMode.setText("拦截短信");
break;
case 3:
holder.tvMode.setText("拦截电话+短信");
break;
default:
break;
}
holder.ivDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 从集合中移除
mList.remove(info);
// 从数据库移除
mNumberDao.delete(info.number);
//更新数据
notifyDataSetChanged();
}
});
return view;
}
static class ViewHolder {
public TextView tvNumber;
public TextView tvMode;
public ImageView ivDelete;
}
}
ListView 简单优化有三步,首先是 View 的复用,每次判断 convertView 是否为空,若不为空则复用之。其次,创建 ViewHolder,用于存储每个 Item 布局文件中的控件,减少每次都要使用 findViewById() 方法来获取控件,减少性能的消耗。最后,将 ViewHolder 改为static, 这样内存只加载一次。当然,关于 View 的复用,里面还有很深的技术,需要逐步学习。
今天算是再一次熟悉了 ListView 的使用,随着使用次数的增加,写代码的效率也越来越高,这让人很激动啊~~ 今天的代码量其实很大,我只是贴出了主要的代码,像黑名单实体类、布局文件、选择器就都没有贴出来,大家可以去看看源码。
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe