前言

最近老是被什么基金原油的电话骚扰,但是手机没显示归属地,搞得我有时以为是快递电话之类的就接了..然后各种烦..所以打算做一个来去电显示归属地的小软件,碰到某些城市的陌生号码就直接挂掉,既然要做就顺便写篇博客把,显示来去电归属地这个功能商业app用得不多,但是权当学习了。

实现原理

网上找一个有号码段的归属地数据库,然后开启个服务监听系统去电广播和来电,然后获取来去电号码,跟数据库的号码字段进行比对,然后显示在手机界面上。

实现步骤

下面一步步来完成这个功能:
主界面布局:

<?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"><Buttonandroid:id="@+id/btn_show_address"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="显示来去电归属地"android:textSize="19sp" /><Buttonandroid:id="@+id/btn_noshow_address"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="不显示来去电归属地"android:textSize="19sp" />
</LinearLayout>

这个布局是自己为了自己设置是否显示归属地的。

在main下面新建一个assets文件夹,并把网上找到的数据库拷贝到该文件夹下面
接下来分析数据库表:
我下的数据库里面有两个表,表结构如下:
data1:
data2:
其中data1的id就是电话号码,前七位就可以确定归属地了,然后根据id对应的outkey去匹配data2的id就可以得到归属地信息了。知道了两个表之间的联系之后就可以开始我们的查询工作了。
新建一个CallAddressDao类,这个类是用来访问数据库的
public class CallAddressDao {//数据库路径private static final String PATH = "data/data/com.sjr.calladdress/files/address.db";/*** 从数据库中获取手机归属地* @param number* @return 手机归属地*/public static String getCallAddress(String number){String callAddress = "未知号码";//获取数据库对象SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH, null,SQLiteDatabase.OPEN_READONLY);//正则表达式匹配if (number.matches("^1[3-8]\\d{9}$")) {//匹配器11位手机号Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)",new String[]{number.substring(0, 7)});//截取前七个if (cursor.moveToNext())callAddress = cursor.getString(0);cursor.close();}else if (number.matches("^\\d+$")){//匹配数字switch (number.length()){case 3:callAddress = "报警电话";//三位数就是报警电话break;case 4:callAddress = "模拟器";break;case 5:callAddress = "客服电话";break;case 7:case 8:callAddress = "本地电话";break;default:if (number.startsWith("0")&&number.length()>10){//有可能是长途电话//有些区号是4位,有些区号是3位(包括0)
//                        先查询4位区号Cursor cursor = database.rawQuery("select location from data2 where area =?",new String[]{number.substring(1,4)});if (cursor.moveToNext())callAddress = cursor.getString(0);else {cursor.close();//查询3位区号cursor = database.rawQuery("select location from data2 where area =?",new String[]{number.substring(1,3)});if (cursor.moveToNext())callAddress = cursor.getString(0);cursor.close();}}break;}}database.close();//关闭数据库return callAddress;}
}
下面是打开一个数据库:
 //获取数据库对象SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH, null,SQLiteDatabase.OPEN_READONLY);

PATH是数据库路径,第二个是一个可选的选项,直接传null,第三个然后我们只是要读,并不修改数据库,所以设置为只读

这里我设定的数据库路径是:

data/data/com.sjr.calladdress/files/address.db

这不能是assets文件夹下面的,只能是data/data/包名/ 目录下,不然是访问不到的,但是这时这个路径下是没有数据库的,所以我就在第一次进入主界面的时候先把数据库拷到这个目录下

    /*** 把assets目录下的数据库拷贝到data/data目录下** @param dbName*/private void copyDB(String dbName) {
//                File filesDir = getFilesDir();///data/data/com.sjr.calladdress/files
//        Log.d("print","路径:"+filesDir.getAbsolutePath());File desFile = new File(getFilesDir(), dbName);//要拷贝的目标地址if (desFile.exists()) {//如果已经存在,就不进行拷贝工作直接返回Log.d("print", "数据库" + dbName + "已经存在");return;}//否则就进行数据库拷贝FileOutputStream out = null;InputStream in = null;try {in = getAssets().open(dbName);//读写assets文件夹下的数据库out = new FileOutputStream(desFile);//写入data目录int len = 0;byte[] buffer = new byte[1024];while ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {try {if (out != null)out.close();if (in != null)in.close();} catch (IOException e) {e.printStackTrace();}}}

上面的代码就把数据库从assets文件夹下面拷贝到data目录下了

然后通过数据库查询语句跟正则表达式对手机号进行匹配。
这时我们开启一个服务去监听来电广播和去电状态,我先把整个服务类拷下来然后详细分析:
   <pre name="code" class="java">/*** Created by 宋家任 on 2016/3/1 15:22.* 监听来去电服务*/
public class CallAddressService extends Service {private TelephonyManager mTM;//电话管理者private WindowManager mWM;//桌面窗口管理者private MPhoneStateListener mListener;private View mView;//桌面展示归属地的viewprivate OutCallReceiver mReceiver;//监听去电广播private SharedPreferences mPref;//记录归属地的位置private int startX;private int startY;private WindowManager.LayoutParams params;private int windowWidth;private int windowHeight;@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {mPref = getSharedPreferences("config", MODE_PRIVATE);mTM = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);mListener = new MPhoneStateListener();mTM.listen(mListener, PhoneStateListener.LISTEN_CALL_STATE);mReceiver = new OutCallReceiver();//动态注册去电广播IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);registerReceiver(mReceiver, filter);}/*** 监听来电通话状态* 需要权限*  <uses-permission android:name="android.permission.READ_PHONE_STATE" />*/class MPhoneStateListener extends PhoneStateListener {@Overridepublic void onCallStateChanged(int state, String incomingNumber) {switch (state) {case TelephonyManager.CALL_STATE_RINGING:// 电话铃响String address = CallAddressDao.getCallAddress(incomingNumber);//把来电号码跟数据库号码进行比对showView(address);break;case TelephonyManager.CALL_STATE_IDLE:// 电话闲置状态if (mWM != null && mView != null) {mWM.removeView(mView);// 从window中移除viewmView = null;}break;}}}/*** 去电广播* 监听去电广播需要下面的权限* <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />*/class OutCallReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String number = getResultData();//获取去电号码String address = CallAddressDao.getCallAddress(number);//将去电号码跟数据库的进行比对showView(address);}}/**** 弹自定义的界面* 使用电话窗口需要下面的权限*<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />* @param address*/private void showView(String address) {//获得一个窗口管理者实例mWM = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);windowWidth = mWM.getDefaultDisplay().getWidth();//获取屏幕宽度windowHeight = mWM.getDefaultDisplay().getHeight();//获取屏幕高度//布局参数params = new WindowManager.LayoutParams();params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;params.format = PixelFormat.TRANSLUCENT;params.type = WindowManager.LayoutParams.TYPE_PHONE;//电话窗口。用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。params.gravity = Gravity.LEFT + Gravity.TOP;// 将重心位置设置为左上方,// 也就是(0,0)从左上方开始,而不是默认的重心位置params.setTitle("Toast");//先获取上次显示的位置int lastX = mPref.getInt("lastX", 0);int lastY = mPref.getInt("lastY", 0);//设置悬浮窗的位置,基于左上方的偏移量params.x = lastX;params.y = lastY;//要展示的viewmView = View.inflate(this, R.layout.view_address, null);//给view设置背景mView.setBackgroundResource(R.drawable.call_locate_blue);// 根据存储的样式更新背景//设置文字TextView tvText = (TextView) mView.findViewById(R.id.tv_number);tvText.setText(address);mWM.addView(mView, params);// 将view添加在屏幕上(Window)//给这个归属地view设置监听,移动的时候记录坐标mView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic 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 endX = (int) event.getRawX();int endY = (int) event.getRawY();// 计算移动偏移量int dx = endX - startX;int dy = endY - startY;// 更新浮窗位置params.x += dx;params.y += dy;// 防止坐标偏离屏幕if (params.x < 0) {params.x = 0;}if (params.y < 0) {params.y = 0;}// 防止坐标偏离屏幕if (params.x > windowWidth - mView.getWidth()) {params.x = windowWidth - mView.getWidth();}if (params.y > windowHeight - mView.getHeight()) {params.y = windowHeight - mView.getHeight();}mWM.updateViewLayout(mView, params);//更新布局// 重新初始化起点坐标startX = (int) event.getRawX();startY = (int) event.getRawY();break;case MotionEvent.ACTION_UP://抬起// 记录坐标点SharedPreferences.Editor edit = mPref.edit();edit.putInt("lastX", params.x);edit.putInt("lastY", params.y);edit.commit();break;default:break;}return true;//返回true消费这个手势事件}});}/*** 服务销毁的时候会回调这个方法*/@Overridepublic void onDestroy() {mTM.listen(mListener, PhoneStateListener.LISTEN_NONE);//停止来电监听unregisterReceiver(mReceiver);//注销监听去电广播}
}

先解决监听去电广播的问题,写一个广播接收者,去接收去电广播,注意监听去电广播是要权限的,权限如下

  /*** 去电广播* 监听去电广播需要下面的权限* <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />*/class OutCallReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String number = getResultData();//获取去电号码String address = CallAddressDao.getCallAddress(number);//将去电号码跟数据库的进行比对showView(address);}}

让后因为是要动态监听去电广播,所以就只能动态注册,所以在服务开启的时候就注册这个广播:
   //动态注册去电广播IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);registerReceiver(mReceiver, filter);

在服务关闭的时候就取消注册:

 /*** 服务销毁的时候会回调这个方法*/@Overridepublic void onDestroy() {mTM.listen(mListener, PhoneStateListener.LISTEN_NONE);//停止来电监听unregisterReceiver(mReceiver);//注销监听去电广播}

上面就完成了一个动态监听去电广播了,然后就是监听电话状态了
 /*** 监听来电通话状态* 需要权限*  <uses-permission android:name="android.permission.READ_PHONE_STATE" />*/class MPhoneStateListener extends PhoneStateListener {@Overridepublic void onCallStateChanged(int state, String incomingNumber) {switch (state) {case TelephonyManager.CALL_STATE_RINGING:// 电话铃响String address = CallAddressDao.getCallAddress(incomingNumber);//把来电号码跟数据库号码进行比对showView(address);break;case TelephonyManager.CALL_STATE_IDLE:// 电话闲置状态if (mWM != null && mView != null) {mWM.removeView(mView);// 从window中移除viewmView = null;}break;}}}

写一个类去继承PhoneStateListener,然后监听通话状态,注意也是需要权限的,然后同监听去电广播类似,在服务初始化的时候监听,在服务销毁的时候注销监听。

然后写一个方法在通话窗口展示归属地当然使用电话窗口也是需要权限的,权限如下

  /**** 弹自定义的界面* 使用电话窗口需要下面的权限*<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />* @param address*/private void showView(String address) {//获得一个窗口管理者实例mWM = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);windowWidth = mWM.getDefaultDisplay().getWidth();//获取屏幕宽度windowHeight = mWM.getDefaultDisplay().getHeight();//获取屏幕高度//布局参数params = new WindowManager.LayoutParams();params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;params.format = PixelFormat.TRANSLUCENT;params.type = WindowManager.LayoutParams.TYPE_PHONE;//电话窗口。用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。params.gravity = Gravity.LEFT + Gravity.TOP;// 将重心位置设置为左上方,// 也就是(0,0)从左上方开始,而不是默认的重心位置params.setTitle("Toast");//先获取上次显示的位置int lastX = mPref.getInt("lastX", 0);int lastY = mPref.getInt("lastY", 0);//设置悬浮窗的位置,基于左上方的偏移量params.x = lastX;params.y = lastY;//要展示的viewmView = View.inflate(this, R.layout.view_address, null);//给view设置背景mView.setBackgroundResource(R.drawable.call_locate_blue);// 根据存储的样式更新背景//设置文字TextView tvText = (TextView) mView.findViewById(R.id.tv_number);tvText.setText(address);mWM.addView(mView, params);// 将view添加在屏幕上(Window)//给这个归属地view设置监听,移动的时候记录坐标mView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic 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 endX = (int) event.getRawX();int endY = (int) event.getRawY();// 计算移动偏移量int dx = endX - startX;int dy = endY - startY;// 更新浮窗位置params.x += dx;params.y += dy;// 防止坐标偏离屏幕if (params.x < 0) {params.x = 0;}if (params.y < 0) {params.y = 0;}// 防止坐标偏离屏幕if (params.x > windowWidth - mView.getWidth()) {params.x = windowWidth - mView.getWidth();}if (params.y > windowHeight - mView.getHeight()) {params.y = windowHeight - mView.getHeight();}mWM.updateViewLayout(mView, params);//更新布局// 重新初始化起点坐标startX = (int) event.getRawX();startY = (int) event.getRawY();break;case MotionEvent.ACTION_UP://抬起// 记录坐标点SharedPreferences.Editor edit = mPref.edit();edit.putInt("lastX", params.x);edit.putInt("lastY", params.y);edit.commit();break;default:break;}return true;//返回true消费这个手势事件}});}

上面的方法就是在通话界面显示自定义的view,为了便于理解我加了很多注释,然后在这里还是解释下好了,获得一个桌面管理者,然后设置归属地view的相关参数,然后把归属地view添加到桌面,然后给view设置一个监听,让view可以移动,上面用到了SharedPreferences,这是为了移动完归属地view后记录下位置然后下次初始就让这个view显示在那个位置,view布局如下:

<span style="font-size:14px;"><?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:background="@drawable/call_locate_blue"android:gravity="center_vertical"android:orientation="horizontal" ><ImageViewandroid:id="@+id/iv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@android:drawable/ic_menu_call" /><TextViewandroid:id="@+id/tv_number"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="未知号码"android:textSize="18sp"android:textColor="#fff" /></LinearLayout></span>

背景是从网上找的一张.9图然后图标是Android系统的一个电话图标,效果如下

然后完成这个服务类以后只要在MainActivity里开启服务就好了,MainActivity代码如下:

<span style="font-size:14px;">/*** Created by 宋家任 on 2016/5/6 13:55.* 主界面*/
public class MainActivity extends Activity implements View.OnClickListener {private Button btnShow;//显示归属地private Button btnNoShow;//不显示归属地@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);copyDB("address.db");// 拷贝归属地查询数据库initDatas();}/*** 把assets目录下的数据库拷贝到data/data目录下** @param dbName*/private void copyDB(String dbName) {
//                File filesDir = getFilesDir();///data/data/com.sjr.calladdress/files
//        Log.d("print","路径:"+filesDir.getAbsolutePath());File desFile = new File(getFilesDir(), dbName);//要拷贝的目标地址if (desFile.exists()) {//如果已经存在,就不进行拷贝工作直接返回Log.d("print", "数据库" + dbName + "已经存在");return;}//否则就进行数据库拷贝FileOutputStream out = null;InputStream in = null;try {in = getAssets().open(dbName);//打开assets下的数据库out = new FileOutputStream(desFile);int len = 0;byte[] buffer = new byte[1024];while ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {try {if (out != null)out.close();if (in != null)in.close();} catch (IOException e) {e.printStackTrace();}}}private void initDatas() {btnNoShow = (Button) findViewById(R.id.btn_show_address);btnShow = (Button) findViewById(R.id.btn_noshow_address);btnShow.setOnClickListener(this);btnNoShow.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_show_address:startService(new Intent(this, CallAddressService.class));//开启显示归属地服务break;case R.id.btn_noshow_address:stopService(new Intent(this, CallAddressService.class));//关闭显示归属地服务break;}}
}</span>

效果

到这里整个归属地的设置就已经完成了,下面是实际运行结果

源码地址为http://download.csdn.net/detail/lxzmmd/9512104

Android显示来去电归属地相关推荐

  1. Android 显示、隐藏状态栏和导航栏

    Android 显示.隐藏状态栏和导航栏 控制状态栏显示,Activity的主题中配置全屏属性 <item name="android:windowFullscreen"&g ...

  2. android显示多个网络图片不显示,Android显示网络图片实例

    本文实例讲述了Android显示网络图片的方法,分享给大家供大家参考.具体方法如下: 一般来说,在Android中显示一张网络图片其实是非常简单的,下面就是一个非常简单的例子: 步骤1: ① 创建你的 ...

  3. Android 系统(254)---Android libphonenumber Demo 手机号码归属地

    Android libphonenumber Demo 手机号码归属地 libphonenumber 是google 开源的库,提供手机号码格式化,来电归属地,运营商等多种功能十分强大,现在做个简单的 ...

  4. Android显示系统详解

    一.显示系统的分类: 我们来思考一个问题:从普通用户角度来说,某个APP页面(例如购物APP首页)是怎么被显示到屏幕的? 首先看到的是物理屏幕,然后是屏幕中软件工程师编写的APP页面,也就是手机屏幕驱 ...

  5. Android显示九宫图(自定义圆角,仿微信九宫格图)

    详细解析Android显示九宫图(自定义圆角,仿微信九宫格图) 这是一个自定义九宫格图片框架,里面有设置圆角大小,还有当图片一张的时候控件自定义的大小,图片的间隔,四张图片的时候图片自定义为两行两列等 ...

  6. Android显示Gif图片

    关于android显示gif图片的方法有许多种.我试了许多方法之后,觉得都不是很理想.庆幸的是,查看了这篇博客加载网络gif图片之后,我总算找到了理想的方法,在此博客的基础上,本博客将讲解得更详细,更 ...

  7. Android显示MP3专辑封面

    Android显示MP3专辑封面 一.目标 二.实现方案 三.获取内嵌图片 四.遇到的一些问题 1. 读取文件影响加载速度 2. Glide刷新时出现闪烁 五.最终实现 六.开发过程回顾 七.接下来 ...

  8. 【Android显示系统初探】surface初相识

    Android显示系统非常复杂,从早期版本演化至今有很大的变化和改进,所以从当前的版本直接去查看会很困难. 初学者看到繁多的概念和类会感到无从下手. 这里我们将从实践应用的方式来展开这一系列,试图对A ...

  9. 底层之旅——Android显示驱动(framebuffer)的分析

    Android有其完整的显示系统,上层显示系统提供系统图形的输出设备,java层的和和控件的外观和直接的图形接口的绘制都是通过显示系统呈现出来的.Android的底层显示系统与Android的Surf ...

最新文章

  1. python爬虫实例-Python爬虫原理与python爬虫实例大全
  2. 【TensorFlow2.0】以后我们再也离不开Keras了?
  3. 汉字转拼音php代码函数,php中将汉字转换成拼音的函数代码
  4. 重学java基础第十一课:基本的dos命令
  5. 位运算中的左移和右移的计算详解
  6. 利用火狐浏览器Firebug查看网页相关属性
  7. long在C语言中是非法字符吗,C程序设计实践——实验指导
  8. apache开源项目_众筹开源笔记本电脑,新的Apache项目等
  9. Kotlin学习笔记 第一章开始 第二章 基础
  10. 在Orderby子句中使用CASE 语句
  11. java服务器项目,java项目服务器部署
  12. Oracle 相关知识点结构图
  13. 极客大学架构师训练营 微服务架构 Service Mesh 服务网格 RPC 协议实现原理 Dubbo 通讯协议 第19课 听课总结
  14. java编译过程_Java编译运行过程
  15. 用excel做线性规划
  16. cisco模拟器无线路由器
  17. matlab直方图均衡化代码
  18. ZCMU-1345: 国际象棋
  19. 写在前面 - 跟小智一起学网络(1)
  20. html和css中盒子大小,CSS大小设置实例——盒子模型

热门文章

  1. Oracle入门笔记(七)——分组查询
  2. 搜索引擎登录工具_用开源搜索引擎定制你的互联网
  3. FALSE/TRUE与false/true的区别
  4. 没有一点虚荣心——王利芬为马云传记写的序
  5. 绝版珍藏---.net2.0框架老爷机专用
  6. jade基础文档总结
  7. word文档批量替换字体样式
  8. xampp集成环境里查看php版本
  9. Error: Can‘t find Python executable “python“, you can set the PYTHON env variable
  10. FFMPEG NVIDIA硬件加速总结