Android开发实战《手机安全卫士》——5.“高级工具”模块实现 获取经纬度 锁屏 卸载
文章目录
- 1.手机防盗——接收短信播放音乐
- 2.手机防盗——定位方式
- 3.手机防盗——获取经纬度坐标
- 4.手机防盗——设备管理器使用
- 5.手机防盗——锁屏 & 数据清除 & 卸载
- 6.高级工具——归属地查询
- 7.高级工具——导入数据库
- 8.高级工具——编写查询过程
- 9.高级工具——查询号码归属地
- 10.高级工具——抖动效果
1.手机防盗——接收短信播放音乐
之前我们已经“手机防盗”模块中的界面和跳转逻辑编写完毕,现在需要着手实现该模块的4种防盗功能,我们先实现较为简单的功能——即播放报警音乐的功能,如图中的红框所示:
由于涉及到短信接收,可以联想到以下几个步骤:
- 接收短信的时候,会发送广播,对系统的广播进行监听;
- 监听短信内容,如果内容中包含关键字
#*alarm*#
,播放报警音乐
在receiver包下新建SmsReceiver,继承BroadcastReceiver,作为接收到短信发送时的广播接收器,代码如下:
package com.example.mobilesafe.receiver;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.telephony.SmsMessage;import com.example.mobilesafe.R;
import com.example.mobilesafe.constant.ConstantValue;
import com.example.mobilesafe.utils.SharedPreferencesUtil;public class SmsReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 1.判断是否开启了防盗保护boolean open_security = SharedPreferencesUtil.getBoolean(context, ConstantValue.OPEN_SECURITY, false);// 2.获取短信内容if (open_security){Object[] pdus = (Object[])intent.getExtras().get("pdus");// 3.循环遍历短信的过程for (Object object : pdus) {// 4.获取短信对象SmsMessage sms = SmsMessage.createFromPdu((byte[]) object);// 5.获取短信对象的基本信息String originatingAddress = sms.getOriginatingAddress(); // 短信地址String messageBody = sms.getMessageBody(); // 短信内容// 6.判断是否包含播放音乐的关键字if (messageBody.contains("#*alarm*#")){// 7.播放音乐(准备音乐,MediaPlay,音乐提前准备好放在Raw文件夹中)MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.ylzs);mediaPlayer.setLooping(true); // 一直循环音乐mediaPlayer.start();}}}}
}
逻辑代码写完后,记得在manifest.xml中添加接收短信的权限,代码如下:
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
2.手机防盗——定位方式
完成了“接收短信播放音乐的功能后”,接下来需要完成第二个功能,即“GPS追踪”,涉及到经纬度的获取,根绝精确程度,获取手机经纬度共有三种方式:
- 网络定位:IP地址对应一个真实的物理地址,但是只能定位到大体位置;
- 基站定位:通常由三个基站进行定位,确认物理地址;
- 卫星定位:顾名思义,采用卫星进行定位。
获取经纬度的相关类有LocationListener
、LocationManager
等,在获取坐标时,还需要在清单文件下声明以下权限:
<!-- 允许模拟器模拟位置坐标的权限 --><uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/><!-- 获取粗略坐标的权限(网络定位时使用) --><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/><!-- 获取精准坐标的权限 --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
默认获取的坐标,是真实正确的坐标,但是由于地图偏振,所以无法在偏振地图上定位到真实的位置上。但是已经有人破解了地图偏振算法,即将获取到的坐标转换成**火星坐标(即偏振坐标)**放到偏振地图上,就能够得到坐标对应的真实位置。
在util包下新建ModifyOffsetUtil,作为获取火星坐标的工具类,代码如下:
package com.example.mobilesafe.utils;import java.io.InputStream;
import java.io.ObjectInputStream;/*** 火星地球坐标转化.地图坐标修偏* */
public class ModifyOffsetUtil {private static ModifyOffsetUtil modifyOffset;static double[] X = new double[660 * 450];static double[] Y = new double[660 * 450];private ModifyOffsetUtil(InputStream inputStream) throws Exception {init(inputStream);}public synchronized static ModifyOffsetUtil getInstance(InputStream is) throws Exception {if (modifyOffset == null) {modifyOffset = new ModifyOffsetUtil(is);}return modifyOffset;}public void init(InputStream inputStream) throws Exception {ObjectInputStream in = new ObjectInputStream(inputStream);try {int i = 0;while (in.available() > 0) {if ((i & 1) == 1) {Y[(i - 1) >> 1] = in.readInt() / 100000.0d;;} else {X[i >> 1] = in.readInt() / 100000.0d;;}i++;}} finally {if (in != null)in.close();}}// standard -> chinapublic PointDouble s2c(PointDouble pt) {int cnt = 10;double x = pt.x, y = pt.y;while (cnt-- > 0) {if (x < 71.9989d || x > 137.8998d || y < 9.9997d || y > 54.8996d)return pt;int ix = (int) (10.0d * (x - 72.0d));int iy = (int) (10.0d * (y - 10.0d));double dx = (x - 72.0d - 0.1d * ix) * 10.0d;double dy = (y - 10.0d - 0.1d * iy) * 10.0d;x = (x + pt.x + (1.0d - dx) * (1.0d - dy) * X[ix + 660 * iy] + dx* (1.0d - dy) * X[ix + 660 * iy + 1] + dx * dy* X[ix + 660 * iy + 661] + (1.0d - dx) * dy* X[ix + 660 * iy + 660] - x) / 2.0d;y = (y + pt.y + (1.0d - dx) * (1.0d - dy) * Y[ix + 660 * iy] + dx* (1.0d - dy) * Y[ix + 660 * iy + 1] + dx * dy* Y[ix + 660 * iy + 661] + (1.0d - dx) * dy* Y[ix + 660 * iy + 660] - y) / 2.0d;}return new PointDouble(x, y);}// china -> standardpublic PointDouble c2s(PointDouble pt) {int cnt = 10;double x = pt.x, y = pt.y;while (cnt-- > 0) {if (x < 71.9989d || x > 137.8998d || y < 9.9997d || y > 54.8996d)return pt;int ix = (int) (10.0d * (x - 72.0d));int iy = (int) (10.0d * (y - 10.0d));double dx = (x - 72.0d - 0.1d * ix) * 10.0d;double dy = (y - 10.0d - 0.1d * iy) * 10.0d;x = (x + pt.x - (1.0d - dx) * (1.0d - dy) * X[ix + 660 * iy] - dx* (1.0d - dy) * X[ix + 660 * iy + 1] - dx * dy* X[ix + 660 * iy + 661] - (1.0d - dx) * dy* X[ix + 660 * iy + 660] + x) / 2.0d;y = (y + pt.y - (1.0d - dx) * (1.0d - dy) * Y[ix + 660 * iy] - dx* (1.0d - dy) * Y[ix + 660 * iy + 1] - dx * dy* Y[ix + 660 * iy + 661] - (1.0d - dx) * dy* Y[ix + 660 * iy + 660] + y) / 2.0d;}return new PointDouble(x, y);}}class PointDouble {double x, y;PointDouble(double x, double y) {this.x = x;this.y = y;}public String toString() {return "x=" + x + ", y=" + y;}
}
3.手机防盗——获取经纬度坐标
经过上面小节的分析,我们来正式完成获取经纬度的业务实现。为了方便起见,这里我们使用Service
来绑定获取经纬度坐标的业务,达到实时获取坐标的功能。
修改SmsReceiver,加入判断开启位置服务的代码,代码如下:
package com.example.mobilesafe.receiver;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.telephony.SmsMessage;import com.example.mobilesafe.service.LocationService;
import com.example.mobilesafe.R;
import com.example.mobilesafe.constant.ConstantValue;
import com.example.mobilesafe.utils.SharedPreferencesUtil;public class SmsReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 1.判断是否开启了防盗保护boolean open_security = SharedPreferencesUtil.getBoolean(context, ConstantValue.OPEN_SECURITY, false);// 2.获取短信内容if (open_security){Object[] pdus = (Object[])intent.getExtras().get("pdus");// 3.循环遍历短信的过程for (Object object : pdus) {// 4.获取短信对象SmsMessage sms = SmsMessage.createFromPdu((byte[]) object);// 5.获取短信对象的基本信息String originatingAddress = sms.getOriginatingAddress(); // 短信地址String messageBody = sms.getMessageBody(); // 短信内容// 6.判断是否包含播放音乐的关键字if (messageBody.contains("#*alarm*#")){// 7.播放音乐(准备音乐,MediaPlay,音乐提前准备好放在Raw文件夹中)MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.ylzs);mediaPlayer.setLooping(true); // 一直循环音乐mediaPlayer.start();}// 8.判断是否包含定位的关键字if (messageBody.contains("#*location*#")){// 9.开启获取位置的服务context.startService(new Intent(context, LocationService.class));}}}}
}
新建service包,在包下新建名为LocationService的类,作为获取位置的服务,记得在manifest下声明这个Service,代码如下:
package com.example.mobilesafe.service;import android.Manifest;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.SmsManager;import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;import com.example.mobilesafe.activity.SettingActivity;public class LocationService extends Service {@Overridepublic void onCreate() {super.onCreate();// 获取手机的经纬度坐标// 1.获取位置管理者对象LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);// 2.以最优的方式获取经纬度Criteria criteria = new Criteria();criteria.setCostAllowed(true); // 允许使用流量criteria.setAccuracy(Criteria.ACCURACY_FINE); // 指定获取经纬度的精确度String bestProvider = lm.getBestProvider(criteria, true);// 3.在一定时间间隔,移动一定距离后获取经纬度坐标,注意动态申请权限if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {return;}lm.requestLocationUpdates(bestProvider, 0, 0, new LocationListener() {@Overridepublic void onLocationChanged(Location location) {// 经度double longitude = location.getLongitude();// 纬度double latitude = location.getLatitude();// 4.发送短信给另一台手机报警SmsManager sms = SmsManager.getDefault();sms.sendTextMessage("5556",null,"longtituede change!!" + longitude,null,null);}@Overridepublic void onStatusChanged(String provider, int status, Bundle extras) {// GPS状态发生切换的事件监听}@Overridepublic void onProviderEnabled(String provider) {// GPS开启的时候的事件监听}@Overridepublic void onProviderDisabled(String provider) {// GPS关闭的时候的事件监听}});}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onDestroy() {super.onDestroy();}
}
4.手机防盗——设备管理器使用
我们在上面的小节中完成了防盗模块的两个功能,接下来完成剩下的两个功能——数据销毁和远程锁屏。由于这两个功能都会涉及到设备管理器,所以这一节中简单介绍一下。
设备管理器本质上是一个广播接收器BroadCastReceiver
,其中涉及到的api主要为:DeviceAdminReceiver
、ComponentName
,要使用设备管理器,大概分为以下步骤:
- 查看Google文档 Administration -> device polices -> 清单文件的配置
- 将清单文件中对应的广播接受者子类创建出来,需要继承
DeviceAdminReceiver
- 将清单文件中的错误进行修复(创建对应xml文件),对应代码如下:
<device-admin xmlns:android="http://schemas.android.com/apk/res/android"><uses-policies><limit-password /><watch-login /><reset-password /><force-lock /><wipe-data /><expire-password /><encrypted-storage /><disable-camera /></uses-policies></device-admin>
- 开启激活界面的Activity,对应代码如下:
ComponentName mDeviceAdminSample = new ComponentName(context, DeviceAdmin.class);
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,mActivity.getString(R.string.add_admin_extra_app_text));
startActivity(intent);
5.手机防盗——锁屏 & 数据清除 & 卸载
要想实现锁屏功能,需要在开启设备管理器下(即需要激活)的前提下调用api:DevicePolicyManager
来获取对象,锁屏的api为lockNow()
,重置密码的api为resetPassword("密码",0)
,整体流程为:
// 1.创建对象
DevicePolicyManager mDpm = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
// 2.锁屏
mDpm.lockNow();
// 3.重置密码为空
mDpm.resetPassword("密码",0);
要想实现数据清除功能,即将数据全部清除(同时取消激活),同样需要调用api:DevicePolicyManager
来获取对象,清除手机数据的api为:wipeData(0)
,而清除手机SD卡数据的api为:wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE)
:
// 1.创建对象
DevicePolicyManager mDpm = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
// 2.清除手机数据
mDpm.wipeData(0);
要想实现卸载功能,需要注意以下两点:
- 在设备管理器中没有激活的应用,可以卸载;
- 在设备管理器中有激活的应用,不可以卸载,系统会提示取消在设备管理器中激活,然后才可以卸载;
- 卸载是Android系统中自带的功能,可以调用固有api(查看PackageInstaller源码,找到
uninstallActivity
源码,调用隐式Intent匹配对应的Action、CateGory、Data(即应用的包名)就可以卸载指定应用)。
对应的调用代码如下:
Intent intent = new Intent("android.intent.action.DELETE");
intent.addCategory("android.intent.category.DEFAULT");
intent.setData("package:" + Uri.parse(getPackageName()));
startActivity(intent);
最后,整体修改过后的SmsReceiver代码如下:
package com.example.mobilesafe.receiver;import android.app.admin.DevicePolicyManager;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.media.MediaPlayer;import android.telephony.SmsMessage;import com.example.mobilesafe.service.LocationService;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;public class SmsReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 0.创建DevicePolicyManager对象DevicePolicyManager mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);// 1.判断是否开启了防盗保护boolean open_security = SharedPreferencesUtil.getBoolean(context, ConstantValue.OPEN_SECURITY, false);// 2.获取短信内容if (open_security){Object[] pdus = (Object[])intent.getExtras().get("pdus");// 3.循环遍历短信的过程for (Object object : pdus) {// 4.获取短信对象SmsMessage sms = SmsMessage.createFromPdu((byte[]) object);// 5.获取短信对象的基本信息String originatingAddress = sms.getOriginatingAddress(); // 短信地址String messageBody = sms.getMessageBody(); // 短信内容// 6.判断是否包含播放音乐的关键字if (messageBody.contains("#*alarm*#")){// 7.播放音乐(准备音乐,MediaPlay,音乐提前准备好放在Raw文件夹中)MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.ylzs);mediaPlayer.setLooping(true); // 一直循环音乐mediaPlayer.start();}// 8.判断是否包含定位的关键字if (messageBody.contains("#*location*#")){// 9.开启获取位置的服务context.startService(new Intent(context, LocationService.class));}// 10.判断是否包含锁屏的关键字if (messageBody.contains("#*lockscreen*")){// 锁屏mDpm.lockNow();// 重置密码为空mDpm.resetPassword("密码",0);}// 11.判断是否包含清除数据的关键字if (messageBody.contains("#*wipedate*")){// 清除手机数据mDpm.wipeData(0);}}}}
}
写到这里,手机防盗的模块我们已经完全实现了,接下来需要实现高级工具的模块功能。
6.高级工具——归属地查询
“高级工具”模块总共具有四个功能,如图所示:
这一节我们需要完成该模块下的第一个功能——归属地查询,即查询某个号码对应的相关信息,如图所示:
该功能需要实现:
- 实时查询 & 点击按钮查询
- 输入框的抖动效果
- 手机的震动效果
我们先修改HomeActivity中initData()方法中跳转到“高级工具”的跳转逻辑,代码如下:
private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 0:// 手机防盗showDialog();break;case 7:// 高级工具startActivity(new Intent(getApplicationContext(),AToolActivity.class));break;case 8:// 设置中心Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}
与此同时,创建名为AToolActivity的Activity,作为“高级中心”的Activity,套用Empty Activity即可。随后,我们编写一个名为selector_atool_item_bg.xml的按钮点击的选择器,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><!-- 选中状态:灰色 --><item android:state_pressed="true" android:drawable="@android:color/darker_gray"/><!-- 未选中状态:粉红色 --><item android:drawable="@android:color/holo_red_light"/>
</selector>
然后修改activity_a_tool.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.AToolActivity"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="高级工具"/><TextViewandroid:id="@+id/tv_query_phone_address"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="归属地查询"android:padding="5dp"android:background="@drawable/selector_atool_item_bg"android:gravity="center"android:drawableLeft="@android:drawable/btn_star"/></LinearLayout>
修改AToolActivity,添加initPhoneAddress()方法,该方法要跳转到查询电话归属地页面,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;import com.example.mobilesafe.R;public class AToolActivity extends AppCompatActivity {private TextView tv_query_phone_address;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_a_tool);// 1.电话归属地查询方法initPhoneAddress();}/*** 1.电话归属地查询方法*/private void initPhoneAddress() {tv_query_phone_address = findViewById(R.id.tv_query_phone_address);tv_query_phone_address.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivity(new Intent(getApplicationContext(),QueryAddressActivity.class));}});}
}
创建对应的QueryAddressActivity,同样套用Empty Activity模板,我们先修改其布局文件,即activity_query_address.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.QueryAddressActivity"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="归属地查询"/><EditTextandroid:id="@+id/et_phone"android:hint="请输入电话号码"android:inputType="phone"android:layout_width="match_parent"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/btn_query"android:text="查询"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tv_query_result"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout>
7.高级工具——导入数据库
为了实现归属地查询功能,我们需要使用到已经存储了海量手机号信息的数据库来进行关联查询,以此来获取到电话号码对应的归属地信息。
这里需要用到一个名为address.db的数据库,其中主要有两张表:data1和data2。这两张表的信息如下:
- data1:主键id代表电话号码的前7位,外键outkey做关联查询使用;
- data2:主键id和data1表中outkey关联外键,location代表电话号码的归属地信息,area代表区号(省略第一位0)。
将address.db放到main/assets目录下,然后再进行读取,将其转换在工程中的Files/Cache/SD卡,做好资源准备。
修改SplashActivity,添加initDB()方法,作为初始化数据库的方法。再创建initAddressDB()方法,当打开应用时,就将数据库载入进去,代码如下:
/*** 12.初始化数据库*/private void initDB(){// 归属地数据拷贝过程initAddressDB("address.db");}/*** 13.初始化归属地数据库* @param dbName 数据库名称*/private void initAddressDB(String dbName) {// 1.在files文件夹下创建同名数据库File filesDir = getFilesDir();File file = new File(filesDir, dbName);if (file.exists()){return;}InputStream stream = null;FileOutputStream fileOutputStream = null;// 2.读取第三方资产目录下的文件try {stream = getAssets().open(dbName);// 3.将读取的内容写入到指定文件夹的文件中fileOutputStream = new FileOutputStream(file);// 4.每次的读取内容大小byte[] bytes = new byte[1024];int temp = -1;while ( (temp = stream.read(bytes)) != -1){fileOutputStream.write(bytes,0,temp);}} catch (IOException e) {e.printStackTrace();} finally {if (stream != null && fileOutputStream != null){try {stream.close();fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}}
重新运行项目,可以发现address.db文件已经存储到相应位置了,即data/data/工程包名/files/address.db,如图所示:
8.高级工具——编写查询过程
上一节中我们完成了数据库的导入,现在需要编写SQL查询过程。为了代码的复用性,这里将查询过程整合成Dao操作类。首先在包下创建Dao层,然后创建AddressDao作为查询地址的功能封装类,使用SQLite原生的查询语句进行管理查询,代码如下:
package com.example.mobilesafe.dao;import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;public class AddressDao {// 1.指定访问数据库的路径public static String path = "data/data/com.example.mobilesafe/files/address.db";private static String mAddress;/*** 开启数据库连接,进行访问* @param phone 电话号码*/public static String getAddress(String phone){mAddress = "未知号码";// 0.定义SQLite数据库对象SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);;// 1.对手机号码进行正则表达式处理String regularExpression = "^1[3-8]\\\\d{9}";if (phone.matches(regularExpression)){phone = phone.substring(0,7); // 取电话号的前7位// 2.数据库查询Cursor cursor = db.query("data1", new String[]{"outkey"}, "id = ?", new String[]{phone}, null, null, null);// 3.查到即可if (cursor.moveToNext()){// 4.从data1表中获取外键outkeyString outkey = cursor.getString(0);// 5.从data2表中通过outkey来获取locationCursor indexCursor = db.query("data2", new String[]{"location"}, "id = ?", new String[]{outkey}, null, null, null);if (indexCursor.moveToNext()){// 6.获取查询到的电话归属地mAddress = indexCursor.getString(0);}}else {mAddress = "未知号码";}}else {int length = phone.length();switch (length){case 3: // 119,110,112,114mAddress = "报警电话";break;case 4:mAddress = "模拟器";break;case 5:// 10086,99555mAddress = "服务电话";break;case 7:mAddress = "固定电话";break;case 8:mAddress = "固定电话";break;case 11: // (3 + 8) 区号 + 座机号码String area = phone.substring(1, 3);Cursor cursor2 = db.query("data2", new String[]{"location"}, "area = ?", new String[]{area}, null, null, null);if (cursor2.moveToNext()){mAddress = cursor2.getString(0);}else {mAddress = "未知号码";}break;case 12: // (4 + 8) 区号 + 座机号码String area2 = phone.substring(1, 4);Cursor cursor3 = db.query("data2", new String[]{"location"}, "area = ?", new String[]{area2}, null, null, null);if (cursor3.moveToNext()){mAddress = cursor3.getString(0);}else {mAddress = "未知号码";}break;}}return mAddress;}
}
注意,这里要匹配电话号码,为了避免空指针异常,需要使用到正则表达式。
9.高级工具——查询号码归属地
我们将查询逻辑编写完成了,接下来需要实现输入电话号码,点击按钮后显示归属地
修改QueryAddressActivity,完善相应逻辑,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AddressDao;public class QueryAddressActivity extends AppCompatActivity {private EditText et_phone;private Button btn_query;private TextView tv_query_result;private String mAddress;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {// 4.控制使用查询结果tv_query_result.setText(mAddress);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_query_address);// 初始化UIinitUI();}private void initUI() {et_phone = findViewById(R.id.et_phone);btn_query = findViewById(R.id.btn_query);tv_query_result = findViewById(R.id.tv_query_result);// 1.点击查询功能,注册按钮的点击事件btn_query.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String phone = et_phone.getText().toString();// 2.查询是耗时操作,需要开启子线程query(phone);}});}/*** 查询操作,在子线程中* @param phone 查询电话号码*/private void query(final String phone) {new Thread(){@Overridepublic void run() {mAddress = AddressDao.getAddress(phone);// 3.消息机制,告知主线程查询结束,可以去使用查询结果mHandler.sendEmptyMessage(0);}}.start();}
}
为了实现文本变化时实时更新归属地,我们调用EditText组件中的addTextChangedListener
方法注册一个监听器,再次修改QueryAddressActivity,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AddressDao;public class QueryAddressActivity extends AppCompatActivity {private EditText et_phone;private Button btn_query;private TextView tv_query_result;private String mAddress;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {// 4.控制使用查询结果tv_query_result.setText(mAddress);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_query_address);// 初始化UIinitUI();}private void initUI() {et_phone = findViewById(R.id.et_phone);btn_query = findViewById(R.id.btn_query);tv_query_result = findViewById(R.id.tv_query_result);// 1.点击查询功能,注册按钮的点击事件btn_query.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String phone = et_phone.getText().toString();// 2.查询是耗时操作,需要开启子线程query(phone);}});// 5.实时查询et_phone.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {// 文本发生改变前}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {// 文本发生改变时}@Overridepublic void afterTextChanged(Editable s) {// 文本发生改变后String phone = et_phone.getText().toString();query(phone);}});}/*** 查询操作,在子线程中* @param phone 查询电话号码*/private void query(final String phone) {new Thread(){@Overridepublic void run() {mAddress = AddressDao.getAddress(phone);// 3.消息机制,告知主线程查询结束,可以去使用查询结果mHandler.sendEmptyMessage(0);}}.start();}
}
10.高级工具——抖动效果
我们实现了查询号码归属地的功能,现在需要实现——让EditText控件在内容为空时,点击按钮后具有抖动的效果,修改QueryAddressActivity,完善按钮的点击事件逻辑,注意这里的抖动效果由官方的api来提供,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AddressDao;public class QueryAddressActivity extends AppCompatActivity {private EditText et_phone;private Button btn_query;private TextView tv_query_result;private String mAddress;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {// 4.控制使用查询结果tv_query_result.setText(mAddress);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_query_address);// 初始化UIinitUI();}private void initUI() {et_phone = findViewById(R.id.et_phone);btn_query = findViewById(R.id.btn_query);tv_query_result = findViewById(R.id.tv_query_result);// 1.点击查询功能,注册按钮的点击事件btn_query.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String phone = et_phone.getText().toString();if (!TextUtils.isEmpty(phone)){// 2.查询是耗时操作,需要开启子线程query(phone);}else {Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.shake);et_phone.startAnimation(animation);}}});// 5.实时查询et_phone.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {// 文本发生改变前}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {// 文本发生改变时}@Overridepublic void afterTextChanged(Editable s) {// 文本发生改变后String phone = et_phone.getText().toString();query(phone);}});}/*** 查询操作,在子线程中* @param phone 查询电话号码*/private void query(final String phone) {new Thread(){@Overridepublic void run() {mAddress = AddressDao.getAddress(phone);// 3.消息机制,告知主线程查询结束,可以去使用查询结果mHandler.sendEmptyMessage(0);}}.start();}
}
Android开发实战《手机安全卫士》——5.“高级工具”模块实现 获取经纬度 锁屏 卸载相关推荐
- 学习笔记之《Android项目实战——手机安全卫士》
[Android项目实战-手机安全卫士] 目标:快速积累开发经验,具备中级Android工程师能力. 如遇到难以理解的逻辑或功能,可以先将程序打断点观察程序的执行逻辑. 第一章项目简介:欢迎界面.主界 ...
- android在使用单位方面,《Android项目实战——手机安全卫士》_面试题答案.docx
<Android项目实战--手机安全卫士>_面试题答案 <Android项目实战--手机安全卫士>面试题答案第1章项目简介请问Android程序的真正入口是什么.Android ...
- Android 程序员不得不收藏的 90+ 个人博客(持续更新,android项目实战手机安全卫士
来自滴滴出行,Android 开发助手 开发者,android-open-project 维护者 ,android-open-project-analysis 维护者. 中二病也要开发 ANDROID ...
- Android 简单的视频录制,android项目实战手机安全卫士
*/ public static Camera getDefaultCamera(int <Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义> [docs. ...
- 基于android开发的手机安全卫士
随着智能手机和网络的完美结合,使得智能机的功能越来越强大,浏览网页.网络购物.视频对话都普及到各个手机终端,然而手机平台越广泛,存在的危险就越大,越来越多的安全问题出现在手机的日常运用中.当我们以为只 ...
- Android项目实战手机安全卫士(02)
目录 项目结构图 源代码 运行结果 项目源代码 项目结构图 源代码 清单 01. SplashActivity.java package com.coderdream.mobilesafe.acti ...
- Android毕业设计——基于Android+Eclipse的手机安全卫士设计与实现(毕业论文+程序源码)——手机安全卫士
基于Android+Eclipse的手机安全卫士设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于Android+Eclipse的手机安全卫士设计与实现,文章末尾附有本毕业设计的论文和源码下 ...
- Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 窗体小部件 生成快捷方式
文章目录 1.进程管理--隐藏系统进程 2.进程管理--锁屏清理 3.拓展功能--生成快捷方式 4.高级工具--常用号码查询(布局实现) 5.高级工具--常用号码查询(逻辑实现) 6.拓展功能--窗体 ...
- Android开发实战《手机安全卫士》——12.“手机杀毒”模块实现 病毒数据库
文章目录 1.高级工具--程序锁(细节完善) 2.手机杀毒--界面分析 3.手机杀毒--病毒数据库查询过程 4.手机杀毒--初始化旋转动画 5.手机杀毒--遍历所有应用 & 判断是否为病毒 1 ...
最新文章
- java bufferedwrite_Java BufferedWriter BufferedReader 源码分析
- xshell 6和xftp 6的下载和使用
- linux每日命令(26):Linux文件属性详解
- QMouseEvent鼠标事件简介
- 快来参加学习.NET 挑战赛
- Linux 创建网页服务,Linux使用Node.js建立访问静态网页的服务实例详解
- 还没搞完的排序(后期更新)
- WCDMA是什么意思?CDMA是什么意思?GSM是什么意思
- android 音乐播放器----获取专辑封面图片
- 亚马逊表示并未放弃WP平台:正在打造新应用
- ElasticSearch - 聚合 aggs
- Enterprise Library 4.1 Configuration Sources 图文笔记
- 怎样使用SQL SERVER新建立一个数据库
- 校园二手交易平台小程序《云开发演示》
- win11输入法繁体字切换简体字——记录
- Alt+Shift+NumLock
- 咖啡馆html报告,咖啡屋调查报告.ppt
- 和平精英追猎模式怎么没了 和平精英追猎下架原因
- 华硕u4000u 重装window7 重装win7 重装window10 重装win10 重装系统 戴尔 联想 鸿基 重装系统远程协助
- 知名APP(支付宝、微信、花瓣等)首页设计技巧及原型实例讲解