Android项目:手机安全卫士(12)—— 通讯卫士之电话短信黑名单设置与拦截

Android项目:手机安全卫士(12)—— 通讯卫士之电话、短信黑名单设置与拦截

1 介绍

今天进入新的功能开发了:通讯卫士,主要用于手机电话、短信的黑名单设置与拦截,如果顺利的话,大概分两期就能够写完,今天的任务主要是黑名单数据库的创建与存储,涉及到 SQLite 数据库操作,ListView 操作,以及 Adapter 的优化。先给大家看看效果图:

通讯卫士:黑名单管理

关于项目相关文章,请访问:

项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe

2 创建数据库,封装 CRUD 操作

创建类 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 语句得到查询/执行结果,然后遍历结果,返回列表。

3 创建活动:BlackNumberActivity

抱歉,请原谅我拙劣的英语水平,采用了英语汉拼的方式命名,你说什么,不是我的独创,欧~ 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,若是,则加载下一页数据,否则,就不做处理。

4 适配器 Adapter 的优化

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 的复用,里面还有很深的技术,需要逐步学习。

5 总结

今天算是再一次熟悉了 ListView 的使用,随着使用次数的增加,写代码的效率也越来越高,这让人很激动啊~~ 今天的代码量其实很大,我只是贴出了主要的代码,像黑名单实体类、布局文件、选择器就都没有贴出来,大家可以去看看源码。

关于项目相关文章,请访问:

项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值