火箭特效
效果展示
实现思路
1,火箭游离在activity,activity不论开启还是关闭,不影响小火箭代码逻辑,火箭写在服务中
2,火箭挂载载窗体上
3,喷射火焰,两张图片做轮训切换展示
4,火箭可以被拖拽
5,火箭拖拽到指定区域的时候放手(抬起)才可以被发射
6,尾气动画效果(开启一个透明的activity尾气图片使用渐变动画))
火箭的服务
代码
public class RocketService extends Service {
private WindowManager mWM;
private int mScreenHeight;
private int mScreenWidth;
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
private View mRocketView;
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
params.y = (Integer) msg.obj;
//告知窗体更新火箭view的所在位置
mWM.updateViewLayout(mRocketView, params);
};
};
private WindowManager.LayoutParams params; @Override
public void onCreate() {
//获取窗体对象
mWM = (WindowManager) getSystemService(WINDOW_SERVICE);
mScreenHeight = mWM.getDefaultDisplay().getHeight();
mScreenWidth = mWM.getDefaultDisplay().getWidth();
//开启火箭
showRocket();
super.onCreate();
}
private void showRocket() {
params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 默认能够被触摸
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
//在响铃的时候显示吐司,和电话类型一致
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.setTitle("Toast");
//指定吐司的所在位置(将吐司指定在左上角)
params.gravity = Gravity.LEFT+Gravity.TOP;
//定义吐司所在的布局,并且将其转换成view对象,添加至窗体(权限)
mRocketView = View.inflate(this, R.layout.rocket_view, null);
ImageView iv_rocket = (ImageView) mRocketView.findViewById(R.id.iv_rocket);
AnimationDrawable animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
animationDrawable.start();
mWM.addView(mRocketView, params);
mRocketView.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int moveY = (int) event.getRawY();
int disX = moveX-startX;
int disY = moveY-startY;
params.x = params.x+disX;
params.y = params.y+disY;
//容错处理
if(params.x<0){
params.x = 0;
}
if(params.y<0){
params.y=0;
}
if(params.x>mScreenWidth-mRocketView.getWidth()){
params.x = mScreenWidth-mRocketView.getWidth();
}
if(params.y>mScreenHeight-mRocketView.getHeight()-22){
params.y = mScreenHeight-mRocketView.getHeight()-22;
}
//告知窗体吐司需要按照手势的移动,去做位置的更新
mWM.updateViewLayout(mRocketView, params);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
if(params.x>100 && params.x<200 && params.y>350){
//发射火箭
sendRocket();
//开启产生尾气的activity
Intent intent = new Intent(getApplicationContext(), BackgroundActivity.class);
//开启火箭后,关闭了唯一的activity对应的任务栈,所以在此次需要告知新开启的activity开辟一个新的任务栈
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
break;
}
return true;
}
});
}
protected void sendRocket() {
//在向上的移动过程中,一直去减少y轴的大小,直到减少为0为止
//在主线程中不能去睡眠,可能会导致主线程阻塞
new Thread(){
public void run() {
for(int i=0;i<11;i++){
int height = 350-i*35;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
msg.obj = height;
mHandler.sendMessage(msg);
}
};
}.start();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
if(mWM!=null && mRocketView!=null){
mWM.removeView(mRocketView);
}
super.onDestroy();
}
}
火箭图标的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<ImageView
android:id="@+id/iv_rocket"
android:background="@drawable/rocket_bg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout
两张切换的背景图
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 第一张图片,火焰较短 -->
<item android:drawable="@drawable/desktop_rocket_launch_1"
android:duration="200"/>
<!-- 第二张图片,火焰较长 -->
<item android:drawable="@drawable/desktop_rocket_launch_2"
android:duration="200"/>
</animation-list>
尾气activity
注册透明activity
<activity android:name="com.example.rocketman.BackgroundActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/iv_bottom"
android:layout_alignParentBottom="true"
android:background="@drawable/desktop_smoke_m"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/iv_top"
android:layout_above="@id/iv_bottom"
android:background="@drawable/desktop_smoke_t"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
代码
public class BackgroundActivity extends Activity {
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
finish();
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_background);
ImageView iv_top = (ImageView) findViewById(R.id.iv_top);
ImageView iv_bottom = (ImageView) findViewById(R.id.iv_bottom);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(500);
iv_top.startAnimation(alphaAnimation);
iv_bottom.startAnimation(alphaAnimation);
mHandler.sendEmptyMessageDelayed(0, 1000);
}
}
去电监听显示归属地
在来电监听的服务中,注册去电的广播接收者
在接收的方法中根据电话号码,展示自定义toast(同来电显示归属地)
去电广播监听的权限
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
代码
//监听播出电话的广播过滤条件(权限)
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
//创建广播接受者
mInnerOutCallReceiver = new InnerOutCallReceiver();
registerReceiver(mInnerOutCallReceiver, intentFilter);
class InnerOutCallReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//接收到此广播后,需要显示自定义的吐司,显示播出归属地号码
//获取播出电话号码的字符串
String phone = getResultData();
showToast(phone);
}
}
服务销毁时需要反注册广播接收者
@Override
public void onDestroy() {
//取消对电话状态的监听(开启服务的时候监听电话的对象)
if(mTM!=null && mPhoneStateListener!=null){
mTM.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
}
if(mInnerOutCallReceiver!=null){
//去电广播接受者的注销过程
unregisterReceiver(mInnerOutCallReceiver);
}
super.onDestroy();
}
拦截短信电话黑名单功能
功能效果
黑名单相关数据库创建
代码
public class BlackNumberOpenHelper extends SQLiteOpenHelper {
public BlackNumberOpenHelper(Context context) {
super(context, "blacknumber.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建数据库中表的方法
db.execSQL("create table blacknumber " +
"(_id integer primary key autoincrement , phone varchar(20), mode varchar(5));");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Dao 增删改查
创建单例Dao 代码
public class BlackNumberDao {
private BlackNumberOpenHelper blackNumberOpenHelper;
//BlackNumberDao单例模式
//1,私有化构造方法
private BlackNumberDao(Context context){
//创建数据库已经其表机构
blackNumberOpenHelper = new BlackNumberOpenHelper(context);
}
//2,声明一个当前类的对象
private static BlackNumberDao blackNumberDao = null;
//3,提供一个静态方法,如果当前类的对象为空,创建一个新的
public static BlackNumberDao getInstance(Context context){
if(blackNumberDao == null){
blackNumberDao = new BlackNumberDao(context);
}
return blackNumberDao;
}
/**增加一个条目
* @param phone 拦截的电话号码
* @param mode 拦截类型(1:短信 2:电话 3:拦截所有(短信+电话))
*/
public void insert(String phone,String mode){
//1,开启数据库,准备做写入操作
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("phone", phone);
values.put("mode", mode);
db.insert("blacknumber", null, values);
db.close();
}
/**从数据库中删除一条电话号码
* @param phone 删除电话号码
*/
public void delete(String phone){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
db.delete("blacknumber", "phone = ?", new String[]{phone});
db.close();
}
/**
* 根据电话号码去,更新拦截模式
* @param phone 更新拦截模式的电话号码
* @param mode 要更新为的模式(1:短信 2:电话 3:拦截所有(短信+电话)
*/
public void update(String phone,String mode){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("mode", mode);
db.update("blacknumber", contentValues, "phone = ?", new String[]{phone});
db.close();
}
/**
* @return 查询到数据库中所有的号码以及拦截类型所在的集合
*/
public List<BlackNumberInfo> findAll(){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
Cursor cursor = db.query("blacknumber", new String[]{"phone","mode"}, null, null, null, null, "_id desc");
List<BlackNumberInfo> blackNumberList = new ArrayList<BlackNumberInfo>();
while(cursor.moveToNext()){
BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
blackNumberInfo.phone = cursor.getString(0);
blackNumberInfo.mode = cursor.getString(1);
blackNumberList.add(blackNumberInfo);
}
cursor.close();
db.close();
return blackNumberList;
}
/**
* 每次查询20条数据
* @param index 查询的索引值
*/
public List<BlackNumberInfo> find(int index){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
Cursor cursor = db.rawQuery("select phone,mode from blacknumber order by _id desc limit ?,20;", new String[]{index+""});
List<BlackNumberInfo> blackNumberList = new ArrayList<BlackNumberInfo>();
while(cursor.moveToNext()){
BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
blackNumberInfo.phone = cursor.getString(0);
blackNumberInfo.mode = cursor.getString(1);
blackNumberList.add(blackNumberInfo);
}
cursor.close();
db.close();
return blackNumberList;
}
/**
* @return 数据库中数据的总条目个数,返回0代表没有数据或异常
*/
public int getCount(){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
int count = 0;
Cursor cursor = db.rawQuery("select count(*) from blacknumber;", null);
if(cursor.moveToNext()){
count = cursor.getInt(0);
}
cursor.close();
db.close();
return count;
}
/**
* @param phone 作为查询条件的电话号码
* @return 传入电话号码的拦截模式 1:短信 2:电话 3:所有 0:没有此条数据
*/
public int getMode(String phone){
SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
int mode = 0;
Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "phone = ?", new String[]{phone}, null, null,null);
if(cursor.moveToNext()){
mode = cursor.getInt(0);
}
cursor.close();
db.close();
return mode;
}
}
Junit单元测试Dao
清单文件增加配置
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.itheima.mobilesafe74" />
applicatin下增加
<uses-library android:name="android.test.runner" />
test类代码
继承AndroidTestCase 可获得context上下文环境
public class Test extends AndroidTestCase {
public void insert(){
BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
for(int i=0;i<100;i++){
if(i<10){
dao.insert("1860000000"+i, 1+new Random().nextInt(3)+"");
}else{
dao.insert("186000000"+i, 1+new Random().nextInt(3)+"");
}
}
}
public void delete(){
BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
dao.delete("110");
}
public void update(){
BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
dao.update("110", "2");
}
public void findAll(){
BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
List<BlackNumberInfo> blackNumberInfoList = dao.findAll();
}
}
黑名单页面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/TitleStyle"
android:gravity="left"
android:text="黑名单管理"/>
<Button
android:id="@+id/bt_add"
android:text="添加"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
<ListView
android:id="@+id/lv_blacknumber"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
listview,adapter的view布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<TextView
android:id="@+id/tv_phone"
android:text="拦截号码"
android:textColor="#000"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_mode"
android:layout_below="@id/tv_phone"
android:text="拦截类型"
android:textColor="#000"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/iv_delete"
android:background="@drawable/selector_blacknumber_delete_btn_bg"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
黑名单activity
初始化数据
private void initData() {
//获取数据库中所有电话号码
new Thread(){
public void run() {
//1,获取操作黑名单数据库的对象
mDao = BlackNumberDao.getInstance(getApplicationContext());
//2,查询部分数据
mBlackNumberList = mDao.find(0);
mCount = mDao.getCount();
//3,通过消息机制告知主线程可以去使用包含数据的集合
mHandler.sendEmptyMessage(0);
}
}.start();
}
初始化UI
listview 分页
监听listview的滚动条事件
//监听其滚动状态
lv_blacknumber.setOnScrollListener(new OnScrollListener() {
//滚动过程中,状态发生改变调用方法()
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// OnScrollListener.SCROLL_STATE_FLING 飞速滚动
// OnScrollListener.SCROLL_STATE_IDLE 空闲状态
// OnScrollListener.SCROLL_STATE_TOUCH_SCROLL 拿手触摸着去滚动状态
if(mBlackNumberList!=null){
//条件一:滚动到停止状态
//条件二:最后一个条目可见(最后一个条目的索引值>=数据适配器中集合的总条目个数-1)
if(scrollState == OnScrollListener.SCROLL_STATE_IDLE
&& lv_blacknumber.getLastVisiblePosition()>=mBlackNumberList.size()-1
&& !mIsLoad){
/*mIsLoad防止重复加载的变量
如果当前正在加载mIsLoad就会为true,本次加载完毕后,再将mIsLoad改为false
如果下一次加载需要去做执行的时候,会判断上诉mIsLoad变量,是否为false,如果为true,就需要等待上一次加载完成,将其值
改为false后再去加载*/
//如果条目总数大于集合大小的时,才可以去继续加载更多
if(mCount>mBlackNumberList.size()){
//加载下一页数据
new Thread(){
public void run() {
//1,获取操作黑名单数据库的对象
mDao = BlackNumberDao.getInstance(getApplicationContext());
//2,查询部分数据
List<BlackNumberInfo> moreData = mDao.find(mBlackNumberList.size());
//3,添加下一页数据的过程
mBlackNumberList.addAll(moreData);
//4,通知数据适配器刷新
mHandler.sendEmptyMessage(0);
}
}.start();
}
}
}
}
//滚动过程中调用方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
}
使用convertView与ViewHolder优化listview
convertView复用机制
viewholder
利用converView的setTag方法,存储一个保存了item中View对象的引用
代码
private List<BlackNumberInfo> mBlackNumberList;
private MyAdapter mAdapter;
private int mode = 1;
private boolean mIsLoad = false;
private int mCount;
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
//4,告知listView可以去设置数据适配器
if(mAdapter == null){
mAdapter = new MyAdapter();
lv_blacknumber.setAdapter(mAdapter);
}else{
mAdapter.notifyDataSetChanged();
}
};
};
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return mBlackNumberList.size();
}
@Override
public Object getItem(int position) {
return mBlackNumberList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// View view = null;
/* if(convertView == null){
view = View.inflate(getApplicationContext(), R.layout.listview_blacknumber_item, null);
}else{
view = convertView;
}*/
//1,复用convertView
//复用viewHolder步骤一
ViewHolder holder = null;
if(convertView == null){
convertView = View.inflate(getApplicationContext(), R.layout.listview_blacknumber_item, null);
//2,减少findViewById()次数
//复用viewHolder步骤三
holder = new ViewHolder();
//复用viewHolder步骤四
holder.tv_phone = (TextView) convertView.findViewById(R.id.tv_phone);
holder.tv_mode = (TextView)convertView.findViewById(R.id.tv_mode);
holder.iv_delete = (ImageView)convertView.findViewById(R.id.iv_delete);
//复用viewHolder步骤五
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.iv_delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//1,数据库删除
mDao.delete(mBlackNumberList.get(position).phone);
//2,集合中的删除
mBlackNumberList.remove(position);
//3,通知数据适配器刷新
if(mAdapter!=null){
mAdapter.notifyDataSetChanged();
}
}
});
holder.tv_phone.setText(mBlackNumberList.get(position).phone);
int mode = Integer.parseInt(mBlackNumberList.get(position).mode);
switch (mode) {
case 1:
holder.tv_mode.setText("拦截短信");
break;
case 2:
holder.tv_mode.setText("拦截电话");
break;
case 3:
holder.tv_mode.setText("拦截所有");
break;
}
return convertView;
}
}
//复用viewHolder步骤二
static class ViewHolder{
TextView tv_phone;
TextView tv_mode;
ImageView iv_delete;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blacknumber);
initUI();
initData();
}
添加按钮保存弹窗dialog
弹窗布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
style="@style/TitleStyle"
android:background="#fcc"
android:text="添加黑名单号码"/>
<EditText
android:id="@+id/et_phone"
android:hint="请输入拦截号码"
android:inputType="phone"
android:textColor="#000"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<RadioGroup
android:id="@+id/rg_group"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/rb_sms"
android:text="短信"
android:textColor="#000"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/rb_phone"
android:text="电话"
android:textColor="#000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:id="@+id/rb_all"
android:text="所有"
android:textColor="#000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/bt_submit"
android:text="确认"
android:textColor="#000"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_cancel"
android:text="取消"
android:textColor="#000"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
添加按钮添加点击事件展示对应弹窗代码
bt_add.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showDialog();
}
});
protected void showDialog() {
Builder builder = new AlertDialog.Builder(this);
final AlertDialog dialog = builder.create();
View view = View.inflate(getApplicationContext(), R.layout.dialog_add_blacknumber, null);
dialog.setView(view, 0, 0, 0, 0);
final EditText et_phone = (EditText) view.findViewById(R.id.et_phone);
RadioGroup rg_group = (RadioGroup) view.findViewById(R.id.rg_group);
Button bt_submit = (Button) view.findViewById(R.id.bt_submit);
Button bt_cancel = (Button)view.findViewById(R.id.bt_cancel);
//监听其选中条目的切换过程
rg_group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.rb_sms:
//拦截短信
mode = 1;
break;
case R.id.rb_phone:
//拦截电话
mode = 2;
break;
case R.id.rb_all:
//拦截所有
mode = 3;
break;
}
}
});
bt_submit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//1,获取输入框中的电话号码
String phone = et_phone.getText().toString();
if(!TextUtils.isEmpty(phone)){
//2,数据库插入当前输入的拦截电话号码
mDao.insert(phone, mode+"");
//3,让数据库和集合保持同步(1.数据库中数据重新读一遍,2.手动向集合中添加一个对象(插入数据构建的对象))
BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
blackNumberInfo.phone = phone;
blackNumberInfo.mode = mode+"";
//4,将对象插入到集合的最顶部
mBlackNumberList.add(0, blackNumberInfo);
//5,通知数据适配器刷新(数据适配器中的数据有改变了)
if(mAdapter!=null){
mAdapter.notifyDataSetChanged();
}
//6,隐藏对话框
dialog.dismiss();
}else{
ToastUtil.show(getApplicationContext(), "请输入拦截号码");
}
}
});
bt_cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
拦截黑名单的短信与电话
拦截短信
短信在接受的时候,广播发送,监听广播接受者,拦截短信(有序),
动态注册短信广播接收者,将广播的优先级级别提高到最高 (1000)
拦截电话
有电话拨入,处于响铃状态,响铃状态通过代码去挂断电话(aidl,反射),拦截电话
挂断电话的API早期版本endCall()是可以使用的,现在不可以用了;但本身挂断电话这个功能是存在的
挂断电话号码的方法,放置在了aidl文件中名称为endCall
在此处去查看TelePhoneManager源码,去查找获取ITelephony对象的方法:
ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
ServiceManager此类android对开发者隐藏,所以不能去直接调用其方法,所以需要反射调用
需要的权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
aidl 挂断电话endCall 调用过程
将对应的
ITelephony.aidl 和其依赖的NeighboringCellInfo.aidl 文件放入对应的包(原包名)中
//1,获取ServiceManager字节码文件
Class<?> clazz = Class.forName("android.os.ServiceManager");
//2,获取方法
Method method = clazz.getMethod("getService", String.class);
//3,反射调用此方法
IBinder iBinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
//4,调用获取aidl文件对象方法
ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
//5,调用在aidl中隐藏的endCall方法
iTelephony.endCall();
使用内容观察者删除来电记录
由于代码挂断电话后,被挂断的号码仍然会进入通话记录中, 我们需要将这种记录删除
需要的权限
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
思路
1.找到通话记录所在数据库对应的表
data/data/com.android.providers.contacts/databases/contacts2.db/calls
2.访问数据库方式,系统数据库有提供内容提供者(),通过内容解析器解析,Uri地址(源码)
3.访问数据库中calls表的Uri地址content://call_log/calls
编写代码需要注意bug:
再删除通话记录的时候,删除的是以前的通话记录,本次拦截下来的电话号码,通话记录没有删除?
问题原因:数据库中本次通话记录的电话号码还没有插入,就做了删除操作
bug解决方法:
内容提供者:对外提供数据库的访问方式
内容解析器:用内容提供者提供的访问方式Uri,访问数据库(增删改查)
内容观察者:观察数据库的变化,一旦数据发生改变,调用相应方法
通过内容观察者,观察数据库的插入,一旦有插入,则做删除此条插入数据操作
黑名单拦截服务代码
public class BlackNumberService extends Service {
private InnerSmsReceiver mInnerSmsReceiver;
private BlackNumberDao mDao;
private TelephonyManager mTM;
private MyPhoneStateListener mPhoneStateListener;
private MyContentObserver mContentObserver;
@Override
public void onCreate() {
mDao = BlackNumberDao.getInstance(getApplicationContext());
//拦截短信
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
intentFilter.setPriority(1000);
mInnerSmsReceiver = new InnerSmsReceiver();
registerReceiver(mInnerSmsReceiver, intentFilter);
//监听电话的状态
//1,电话管理者对象
mTM = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//2,监听电话状态
mPhoneStateListener = new MyPhoneStateListener();
mTM.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
class MyPhoneStateListener extends PhoneStateListener{
//3,手动重写,电话状态发生改变会触发的方法
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
break;
case TelephonyManager.CALL_STATE_RINGING:
//挂断电话 aidl文件中去了
// mTM.endCall();
endCall(incomingNumber);
break;
}
super.onCallStateChanged(state, incomingNumber);
}
}
class InnerSmsReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//获取短信内容,获取发送短信电话号码,如果此电话号码在黑名单中,并且拦截模式也为1(短信)或者3(所有),拦截短信
//1,获取短信内容
Object[] objects = (Object[]) intent.getExtras().get("pdus");
//2,循环遍历短信过程
for (Object object : objects) {
//3,获取短信对象
SmsMessage sms = SmsMessage.createFromPdu((byte[])object);
//4,获取短信对象的基本信息
String originatingAddress = sms.getOriginatingAddress();
String messageBody = sms.getMessageBody();
int mode = mDao.getMode(originatingAddress);
if(mode == 1 || mode == 3){
//拦截短信(android 4.4版本失效 短信数据库,删除)
abortBroadcast();
}
}
}
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
public void endCall(String phone) {
int mode = mDao.getMode(phone);
if(mode == 2 || mode == 3){
// ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
//ServiceManager此类android对开发者隐藏,所以不能去直接调用其方法,需要反射调用
try {
//1,获取ServiceManager字节码文件
Class<?> clazz = Class.forName("android.os.ServiceManager");
//2,获取方法
Method method = clazz.getMethod("getService", String.class);
//3,反射调用此方法
IBinder iBinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
//4,调用获取aidl文件对象方法
ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
//5,调用在aidl中隐藏的endCall方法
iTelephony.endCall();
} catch (Exception e) {
e.printStackTrace();
}
//6,在内容解析器上,去注册内容观察者,通过内容观察者,观察数据库(Uri决定那张表那个库)的变化
mContentObserver = new MyContentObserver(new Handler(),phone);
getContentResolver().registerContentObserver(
Uri.parse("content://call_log/calls"), true, mContentObserver);
}
}
class MyContentObserver extends ContentObserver{
private String phone;
public MyContentObserver(Handler handler,String phone) {
super(handler);
this.phone = phone;
}
//数据库中指定calls表发生改变的时候会去调用方法
@Override
public void onChange(boolean selfChange) {
//插入一条数据后,再进行删除
getContentResolver().delete(
Uri.parse("content://call_log/calls"), "number = ?", new String[]{phone});
super.onChange(selfChange);
}
}
@Override
public void onDestroy() {
//注销广播
if(mInnerSmsReceiver!=null){
unregisterReceiver(mInnerSmsReceiver);
}
//注销内容观察者
if(mContentObserver!=null){
getContentResolver().unregisterContentObserver(mContentObserver);
}
//取消对电话状态的监听
if(mPhoneStateListener!=null){
mTM.listen(mPhoneStateListener,PhoneStateListener.LISTEN_NONE);
}
super.onDestroy();
}
}