网站首页 > 技术文章 正文
来电短信黑名单拦截
- 演示手机卫士相关功能
- 创建BlackNumberActivity
- 布局文件
<RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#8866ff00" > <TextView 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" /> <Button 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
<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.itheima.mobilesafeteach" /> <application> <uses-library android:name="android.test.runner" /> </application>
增删改查(crud)逻辑实现
public class BlackNumberDao {
private static BlackNumberDao sInstance;
private BlackNumberOpenHelper mHelper;
private BlackNumberDao(Context ctx) {
mHelper = new BlackNumberOpenHelper(ctx);
};
/**
* 获取单例对象
* @param ctx
* @return
*/
public static BlackNumberDao getInstance(Context ctx) {
if (sInstance == null) {
synchronized (BlackNumberDao.class) {
if (sInstance == null) {
sInstance = new BlackNumberDao(ctx);
}
}
}
return sInstance;
}
/**
* 增加黑名单
* @param number
* @param mode
*/
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();
}
/**
* 删除黑名单
* @param number
*/
public void delete(String number) {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.delete("blacknumber", "number=?", new String[] { number });
db.close();
}
/**
* 更新黑名单
* @param number
* @param mode
*/
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();
}
/**
* 查找黑名单
* @param number
* @return
*/
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;
}
/**
* 查找号码拦截模式
* @param number
* @return
*/
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;
}
/**
* 查找黑名单列表
* @return
*/
public ArrayList<BlackNumberInfo> findAll() {
SQLiteDatabase db = mHelper.getWritableDatabase();
Cursor cursor = db
.query("blacknumber", new String[] { "number", "mode" }, null,
null, null, null, null);
ArrayList<BlackNumberInfo> list = new ArrayList<BlackNumberDao.BlackNumberInfo>();
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;
}
/**
* 黑名单对象
* @author Kevin
*
*/
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
/**
* 分页查找黑名单列表
*
* @return
*/
public ArrayList<BlackNumberInfo> 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<BlackNumberInfo> list = new ArrayList<BlackNumberDao.BlackNumberInfo>();
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;
}
/**
* 获取黑名单数量
*
* @return
*/
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;
};
};
添加黑名单
/**
* 添加黑名单
*
* @param view
*/
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();
}
});
创建黑名单拦截服务
- 拦截短信逻辑实现
逻辑类似手机防盗页面拦截特殊短信指令的代码, 只不过该广播是动态注册,不是静态注册. 动态注册的好处是可以随服务的开启或关闭来决定是否监听广播,而且在同等优先级的前提下,动态注册的广播比静态注册的更先接收到广播(可以通过打印日志进行验证)
设置页面增加黑名单拦截开关
通过此开关来开启和关闭服务, 逻辑类似来电归属地显示的开关
来电拦截
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方法
/**
* 挂断电话
* 注意加权限: <uses-permission
* android:name="android.permission.CALL_PHONE"/>
*/
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();
}
}
注意加权限: <uses-permission
android:name="android.permission.CALL_PHONE"/>
猜你喜欢
- 2024-09-29 实战AC无线控制器+AP心得与总结(ac200无线控制器设置)
- 2024-09-29 Foundation 开关(dec inc开关)
- 2024-09-29 超全面!开关、复选框和单选组件在web表单应用分析
- 2024-09-29 ionic 单选框(单选框选中后触发的方法)
- 2024-09-29 微信小程序RadioGroup代替方案-分段组件
- 2024-09-29 实战PyQt5: 110-QSS样式表之基本语法
- 2024-09-29 vue2组件系列第十九节:Radio 单选框(基础应用)
- 2024-09-29 3分钟入门微信小程序开发 组件分类 常用基本组件
- 2024-09-29 微信小程序标签总结(微信小程序标签栏图标尺寸)
- 2024-09-29 设计呀,你是真会给前端找事呀!!!
- 最近发表
-
- 聊一下 gRPC 的 C++ 异步编程_grpc 异步流模式
- [原创首发]安全日志管理中心实战(3)——开源NIDS之suricata部署
- 超详细手把手搭建在ubuntu系统的FFmpeg环境
- Nginx运维之路(Docker多段构建新版本并增加第三方模
- 92.1K小星星,一款开源免费的远程桌面,让你告别付费远程控制!
- Go 人脸识别教程_piwigo人脸识别
- 安卓手机安装Termux——搭建移动服务器
- ubuntu 安装开发环境(c/c++ 15)_ubuntu安装c++编译器
- Rust开发环境搭建指南:从安装到镜像配置的零坑实践
- Windows系统安装VirtualBox构造本地Linux开发环境
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (77)
- vector线程安全吗 (73)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)
