##day08## - 来电短信黑名单拦截
- 演示金山卫士相关功能 - 创建BlackNumberActivity - 布局文件
       android:layout_width="match_parent"        android:layout_height="50dp"        android:background="#8866ff00" >                    android:id="@+id/textView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="5dp"            android:text="黑名单管理"            android:textColor="#000"            android:textSize="22sp" />                    android:id="@+id/button1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="5dp"            android:text="添加" />     - 数据库创建
public class BlackNumberOpenHelper extends SQLiteOpenHelper {
public BlackNumberOpenHelper(Context ctx) { super(ctx, "blacknumber.db", null, 1);//必须实现该构造方法 } @Override public void onCreate(SQLiteDatabase db) { // 创建表, 三个字段,_id, number(电话号码),mode(拦截模式:电话,短信,电话+短信) db.execSQL("create table blacknumber (_id integer primary key autoincrement, number varchar(20), mode integer)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } - 单元测试
- 创建具备单元测试的Android项目, 拷贝清单文件的相关代码
 File->New->Project->Android Test Project        android:name="android.test.InstrumentationTestRunner"        android:targetPackage="com.itheima.mobilesafeteach" />

- 增删改查(crud)逻辑实现
public class BlackNumberDao { private static BlackNumberDao sInstance; private BlackNumberOpenHelper mHelper; private BlackNumberDao(Context ctx) { mHelper = new BlackNumberOpenHelper(ctx); }; public static BlackNumberDao getInstance(Context ctx) { if (sInstance == null) { synchronized (BlackNumberDao.class) { if (sInstance == null) { sInstance = new BlackNumberDao(ctx); } } } return sInstance; } public void add(String number, int mode) { SQLiteDatabase db = mHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("number", number); values.put("mode", mode); db.insert("blacknumber", null, values); db.close(); } public void delete(String number) { SQLiteDatabase db = mHelper.getWritableDatabase(); db.delete("blacknumber", "number=?", new String[] { number }); db.close(); } public void update(String number, int mode) { SQLiteDatabase db = mHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("mode", mode); db.update("blacknumber", values, "number=?", new String[] { number }); db.close(); } public boolean find(String number) { SQLiteDatabase db = mHelper.getWritableDatabase(); Cursor cursor = db.query("blacknumber", new String[] { "number", "mode" }, "number=?", new String[] { number }, null, null, null); boolean result = false; if (cursor.moveToFirst()) { result = true; }    cursor.close(); db.close(); return result; } public int findMode(String number) { SQLiteDatabase db = mHelper.getWritableDatabase(); Cursor cursor = db.query("blacknumber", new String[] { "mode" }, "number=?", new String[] { number }, null, null, null); int mode = -1; if (cursor.moveToFirst()) { mode = cursor.getInt(0); } cursor.close(); db.close(); return mode; } public ArrayList findAll() { SQLiteDatabase db = mHelper.getWritableDatabase(); Cursor cursor = db .query("blacknumber", new String[] { "number", "mode" }, null, null, null, null, null); ArrayList list = new ArrayList(); while (cursor.moveToNext()) { String number = cursor.getString(0); int mode = cursor.getInt(1); BlackNumberInfo info = new BlackNumberInfo(); info.number = number; info.mode = mode; list.add(info); } cursor.close(); db.close(); return list; } public class BlackNumberInfo { public String number; public int mode; @Override public String toString() { return "BlackNumberInfo [number=" + number + ", mode=" + mode + "]"; } } } - 增删改查单元测试
public class TestBlackNumberDao extends AndroidTestCase { public void testCreateDb() { BlackNumberOpenHelper helper = new BlackNumberOpenHelper(getContext()); helper.getWritableDatabase(); } public void testAdd() { //添加100个号码,拦截模式随机 Random random = new Random(); for (int i = 0; i < 100; i++) { int mode = random.nextInt(3) + 1; if (i < 10) { BlackNumberDao.getInstance(getContext()).add("1381234560" + i, mode); } else { BlackNumberDao.getInstance(getContext()).add("138123456" + i, mode); } } } public void testDelete() { BlackNumberDao.getInstance(getContext()).delete("13812345601"); } public void testUpdate() { BlackNumberDao.getInstance(getContext()).update("13812345600", 2); } public void testFind() { boolean find = BlackNumberDao.getInstance(getContext()).find( "13812345600"); assertEquals(true, find); } public void testFindMode() { int mode = BlackNumberDao.getInstance(getContext()).findMode( "13812345600"); System.out.println("拦截模式:" + mode); } }
- 使用命令行查看数据库文件
1. 运行adb shell进入linux环境 2. 切换至data/data/包名/databases 3. 运行sqlite3 *.db,进入数据库 4. 编写sql语句,进行相关操作.记得加分号(;)结束 5. .quit退出sqlite,切换到adb shell - 介绍convertView的重用机制 - 介绍ViewHolder的使用方法 //使用static修饰内部类,系统只加载一份字节码文件,节省内存 static class ViewHolder { public TextView tvNumber; public TextView tvMode; } } - 使用convertView和ViewHolder进行优化之后,重新使用traceview计算getView的执行时间,进行对比
- 最终优化结果 @Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; ViewHolder holder = null; if (convertView == null) { view = View.inflate(BlackNumberActivity.this, R.layout.list_black_number_item, null); System.out.println("listview创建"); // viewHolder类似一个容器,可以保存findViewById获得的view对象 holder = new ViewHolder(); holder.tvNumber = (TextView) view.findViewById(R.id.tv_number); holder.tvMode = (TextView) view.findViewById(R.id.tv_mode); // 将viewHolder设置给view对象,保存起来 view.setTag(holder); } else { view = convertView; holder = (ViewHolder) view.getTag();// 从view对象中得到之前设置好的viewHolder System.out.println("listview重用了"); } BlackNumberInfo info = mBlackNumberList.get(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; } return view; }
static class ViewHolder { public TextView tvNumber; public TextView tvMode; }
- 启动子线程在数据库读取数据
当数据量比较大时,读取数据比较耗时,为了避免ANR,最好将该逻辑放在子线程中进行, 为了模拟数据量大时访问比较慢的情况,可以让线程休眠1-2秒后再加载数据 - 加载中的进度条展示 - 数据分批加载
分批加载优势:避免一次性加载过多内容, 节省时间和流量 sql语句: select * from blacknumber limit 20 offset 0, 表示起始位置是0,加载条数为20, 等同于limit 0,20
public ArrayList findPart(int startIndex) { SQLiteDatabase db = mHelper.getWritableDatabase(); Cursor cursor = db.rawQuery( "select number,mode from blacknumber order by _id desc limit 20 offset ?", new String[] { String.valueOf(startIndex) }); ArrayList list = new ArrayList(); while (cursor.moveToNext()) { String number = cursor.getString(0); int mode = cursor.getInt(1); BlackNumberInfo info = new BlackNumberInfo(); info.number = number; info.mode = mode; list.add(info); } cursor.close(); db.close(); return list; }
public int getTotalCount() { SQLiteDatabase db = mHelper.getWritableDatabase(); Cursor cursor = db.rawQuery("select count(*) from blacknumber", null); int count = 0; if (cursor.moveToNext()) { count = cursor.getInt(0); } cursor.close(); db.close(); return count; }
-------------------------------------- //监听listview的滑动事件 lvList.setOnScrollListener(new OnScrollListener() {
// 滑动状态发生变化 // 1.静止->滚动 2.滚动->静止 3.惯性滑动 @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { //获取当前listview显示的最后一个item的位置 int lastVisiblePosition = lvList.getLastVisiblePosition();
//判断是否应该加载下一页 if (lastVisiblePosition >= mBlackNumberList.size() - 1 && !isLoading) { int totalCount = BlackNumberDao.getInstance( BlackNumberActivity.this).getTotalCount();
//判断是否已经到达最后一页 if (mStartIndex >= totalCount) { Toast.makeText(BlackNumberActivity.this, "没有更多数据了", Toast.LENGTH_SHORT).show(); return; }
Toast.makeText(BlackNumberActivity.this, "加载更多数据...", Toast.LENGTH_SHORT).show(); System.out.println("加载更多数据..."); initData(); } } }
-----------------------------------
//加载数据 private void initData() { pbLoading.setVisibility(View.VISIBLE);//显示进度条 isLoading = true; new Thread() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 第一页数据 if (mBlackNumberList == null) { mBlackNumberList = BlackNumberDao.getInstance( BlackNumberActivity.this).findPart(mStartIndex); } else { mBlackNumberList.addAll(BlackNumberDao.getInstance( BlackNumberActivity.this).findPart(mStartIndex)); } mHandler.sendEmptyMessage(0); } }.start(); }
-----------------------------------
private int mStartIndex;//下一页的起始位置 private boolean isLoading;// 表示是否正在加载
private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { pbLoading.setVisibility(View.GONE);//隐藏进度条 // 第一页数据 if (mAdapter == null) { mAdapter = new BlackNumberAdapter(); lvList.setAdapter(mAdapter); } else { mAdapter.notifyDataSetChanged();//刷新adapter } mStartIndex = mBlackNumberList.size(); isLoading = false; }; }; - 添加黑名单
public void addBlackNumber(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = View.inflate(this, R.layout.dialog_add_black_number, null); final AlertDialog dialog = builder.create(); dialog.setView(view, 0, 0, 0, 0); final EditText etBlackNumber = (EditText) view .findViewById(R.id.et_black_number); final RadioGroup rgMode = (RadioGroup) view.findViewById(R.id.rg_mode); Button btnOK = (Button) view.findViewById(R.id.btn_ok); Button btnCancel = (Button) view.findViewById(R.id.btn_cancel); btnOK.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String number = etBlackNumber.getText().toString().trim(); if (!TextUtils.isEmpty(number)) { int checkedRadioButtonId = rgMode.getCheckedRadioButtonId(); int mode = 1; // 根据当前选中的RadioButtonId来判断是哪种拦截模式 switch (checkedRadioButtonId) { case R.id.rb_call: mode = 1; break; case R.id.rb_sms: mode = 2; break; case R.id.rb_all: mode = 3; break; default: break; } // 保存数据库 BlackNumberDao.getInstance(getApplicationContext()).add( number, mode); // 向列表第一个位置增加黑名单对象,并刷新listview //注意: 分页查询时需要逆序排列,保证后添加的最新数据展示在最前面 BlackNumberInfo info = new BlackNumberInfo(); info.number = number; info.mode = mode; mBlackNumberList.add(0, info); mAdapter.notifyDataSetChanged(); dialog.dismiss(); } else { Toast.makeText(getApplicationContext(), "输入内容不能为空!", Toast.LENGTH_SHORT).show(); } } }); btnCancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); } }); dialog.show(); }
- 删除黑名单    holder.ivDelete.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) { //从数据库中删除 BlackNumberDao.getInstance(getApplicationContext()).delete( info.number); //从内存列表中删除并刷新listview mBlackNumberList.remove(info); mAdapter.notifyDataSetChanged(); } });
- 创建黑名单拦截服务 - 拦截短信逻辑实现
逻辑类似手机防盗页面拦截特殊短信指令的代码, 只不过该广播是动态注册,不是静态注册. 动态注册的好处是可以随服务的开启或关闭来决定是否监听广播,而且在同等优先级的前提下,动态注册的广播比静态注册的更先接收到广播(可以通过打印日志进行验证)
- 设置页面增加黑名单拦截开关
通过此开关来开启和关闭服务, 逻辑类似来电归属地显示的开关
- 短信拦截优化 - 通过关键词智能拦截(介绍) - 金山卫士智能拦截简介 - 金山卫士关键词数据库 查看第四天资料,金山卫士apk解压文件,assets目录下找firewall_sys_rules.db, 该数据制定了短信和来电的拦截规则
短信拦截规则: 根据关键词对短信内容进行过滤. 比如fapiao
//对短信内容进行关键词过滤 String messageBody = msg.getMessageBody(); if (messageBody != null && messageBody.contains("fapiao")) { abortBroadcast(); } - 分词(介绍)
单纯依靠关键词进行过滤有时会出现一些问题, 比如: laogong, nikan,wode toufapiaobupiaoliang.... 所以有时候会对每一句话进行分词处理,比如可以将上述语句先拆分成不同的词语:laogong,nikan,wode,toufa,piaobupiaoliang, 然后在这些词汇中对关键词再进行过滤
lucene分词检索框架 - 短信拦截的兼容性处理 4.4以上系统手机对短信权限进一步限制,导致无法拦截短信,可以通过监听短信数据库的变化,及时删除最新入库的垃圾短信来实现短信拦截的目的. 为了避免误删旧的短信,需要和短信广播结合起来使用 - 来电拦截 1. 挂断电话的API早期版本endCall()是可以使用的,现在不可以用了;但本身挂断电话这个功能是存在的 2. 很多服务都是获取远程服务的代理对象IBinder,再调用里面的方法的.例如:    IBinder b = ServiceManager.getService(ALARM_SERVICE); IAlarmManager service = IAlarmManager.Stub.asInterface(b); return new AlarmManager(service); 3. 于是我们跟踪TelephoneyManager,查看它的对象到底是如何创建的.我们跟踪到了这样一个方法: private ITelephony getITelephony() { return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); } 该方法返回一个ITelephony对象, 查看ITelephony对象的方法,发现有endCall方法 4. 于是我们将获取ITelephony的代码拷贝到自己的项目中,发现无法导包,因为根本有没有ServiceManager这个类,但我们知道它肯定存在,因为TelephonyManager就引用了该类,只不过android系统隐藏了这个类, 5. 为了调用隐藏类的方法,我们想到了反射
- 通过反射获取endCall方法
public void endCall() { try { // 获取ServiceManager Class clazz = BlackNumberService.class.getClassLoader().loadClass( "android.os.ServiceManager"); Method method = clazz.getDeclaredMethod("getService", String.class);// 获取方法getService IBinder binder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);// 方法时静态的,不需要传递对象进去 ITelephony telephony = ITelephony.Stub.asInterface(binder);// 获取ITelephony对象,前提是要先配置好aidl文件 telephony.endCall();//挂断电话 } catch (Exception e) { e.printStackTrace(); } }
注意加权限:  android:name="android.permission.CALL_PHONE"/>

更多相关文章

  1. android rom短信模块最后获取的Cursor字段内容
  2. 老罗Android开发视频教程( android解析json数据 )4集集合
  3. android 中发送短信
  4. 带你了解CLR是如何创建运行时对象?
  5. lambda表达式进行对象结合操作的实例详解
  6. WPF核心的技术--数据绑定
  7. 值类型对象的两种表示形式
  8. 让WebAPI 返回JSON格式的数据实例教程
  9. 怎么在EXECL文件中同时导入数据和插入图片?

随机推荐

  1. 利用Prometheus与Grafana对Mysql服务器的
  2. MySql安装及登录详解
  3. SQL中distinct 和 row_number() over()
  4. mysql为字段添加和删除唯一性索引(unique
  5. mysql报错1033 Incorrect information in
  6. mysql增加和删除索引的相关操作
  7. 用命令创建MySQL数据库(de1)的方法
  8. mysql5.7.17安装使用图文教程
  9. MySQL创建带特殊字符的数据库名称方法示
  10. mysql更新一个表里的字段等于另一个表某