Android项目实战:手机安全卫士
上QQ阅读APP看书,第一时间看更新

2.5 设置向导功能

设置向导主要用于展示手机防盗模块,以及绑定SIM卡、设置安全号码、开启防盗保护功能等,本节将针对设置向导功能进行详细讲解。

2.5.1 滑屏动画

在Android系统中,通过手势识别切换界面时,通常会在界面切换时加入动画,以提高用户的体验效果,这种动画一般都采用平移动画,当下一个界面进入时,上一个界面移出屏幕,如图2-14所示。

图2-14 动画效果图

从图2-14可以看出,屏幕左上角的坐标为(0,0),进入屏幕的界面坐标为(100%p,0),从屏幕切出界面的坐标为(-100%p,0)。需要注意的是,p是指屏幕,100%p表示整个屏幕,切入界面和切出界面都是以整个屏幕为单位计算的。

界面切换的平移动画有四个,分别是下一个界面进入与切出效果,以及上一个界面进入与切出效果,接下来分别定义这四个动画文件。

1.下一个界面进入、切出动画

在res目录中添加一个anim文件夹,在该文件夹中定义下一个界面进入的动画效果(next_in.xml),具体代码如【文件2-23】所示。

【文件2-23】res/anim/next_in.xml

上述代码中,android:fromXDelta="100%p",android:toXDelta="0"属性表示当前界面从X轴坐标100%p移动到0,android:duration="500"表示动画执行时长为500毫秒,repeatCount="0"表示动画不重复执行。

在anim文件夹下创建下一个界面切出的动画效果(next_out.xml),具体代码如【文件2-24】所示。

【文件2-24】res/anim/next_out.xml

下一个界面进入效果与切出效果代码类似,只不过当前界面是从X轴坐标0位置移动到-100%p,时长为0.5秒,动画不重复执行。

2.上一个界面进入、切出动画

上一个界面的进入、切出动画与上面的代码非常类似,只不过是X轴坐标的起始位置与结束位置的变化而已。下面在anim文件夹下创建上一个界面进入的动画效果(pre_in.xml),具体代码如【文件2-25】所示。

【文件2-25】res/anim/pre_in.xml

在anim文件夹下创建上一个界面切出的动画效果(pre_in.xml),具体代码如【文件2-26】所示。

【文件2-26】res/anim/pre_out.xml

至此,所有的动画效果已经创建完成,只需在切换屏幕时引入即可。

2.5.2 手势滑动

设置向导中最核心的内容就是通过手势识别切换界面,当手指落在手机屏幕上按住屏幕快速滑动屏幕即可切换。为了更生动形象地展示切换效果,通过一个图例进行展示,具体如图2-15所示。

图2-15 屏幕切换效果图

从图2-15可以看出,当手指在左侧按下向右快速滑动时,上一屏进入当前界面,当手指在右侧按下快速向左侧滑动时,下一屏进入当前界面。需要注意的是,在滑动过程中需要过滤一些无效的动作(如滑动太慢)。

从功能介绍中可知,设置向导的每一个界面都需要进行滑动,也就是说每个Activity都要有手势识别器。为了避免代码的重复编写,可以定义一个父类实现手势识别功能,其他类继承该类即可。设置向导界面的父类如【文件2-27】所示。

【文件2-27】BaseSetUpActivity.java

代码说明:

●第10~37行代码用于初始化手势识别器,并实现onFling()方法,在该方法中通过if语句对手势的滑动效果进行判断,如果if(Math.abs(velocityX)<200)时表示1秒移动小于200像素,移动速度太慢界面不切换;如果if((e2.getRawX()-e1.getRawX())>200)时,则从左向右滑动屏幕显示上一个界面并显示动画效果;如果if((e1.getRawX()-e2.getRawX())> 200)时,则从右向左滑动屏幕,显示下一个界面并显示动画效果。

●第38~39行代码定义了一个showNext()方法和一个showPre()方法,分别用于展示下一个页面和上一个页面。这两个方法都是抽象的,需要子类重写。

●第42~46行重写了onTouchEvent()方法,并通过手势识别器分析屏幕上的手势事件。

●第51~55行的startActivityAndFinishSelf(Class<?> cls)方法用于在当前界面开启一个新的Activity时,把当前的Activity关闭,其中传入的参数Class<?> cls可以是一个Activity。

2.5.3 向导功能(一)

当父类创建好之后接下来实现设置向导第一个界面的逻辑,由于第一个界面只是单纯的用于展示,因此逻辑较为简单,具体代码如【文件2-28】所示。

【文件2-28】SetUp1Activity.java

代码说明:

●第8~11行的initView()方法用于初始化控件,将界面小圆点的状态置为选中状态,此时小圆点的颜色为紫色。

●第13~15行的showNext()方法重写了BaseSetUpActivity中的showNext()方法,用于开启SetUp2Activity并关闭当前Activity。

●第17~19行showPre()方法重写了BaseSetUpActivity中的showPre()方法,当手指在屏幕上从左向右滑动时用于弹出提示信息,说明当前页面是第一页不能显示前一页。

2.5.4 向导功能(二)

设置向导第二个界面主要用于绑定SIM卡,相比第一个界面来说逻辑较为复杂,具体代码如【文件2-29】所示。

【文件2-29】SetUp2Activity.java

代码说明:

●第12~22行的initView()方法用于初始化控件,指定第2个小圆点被选中,并判断SIM卡是否绑定,如果已绑定则将该Button按钮设置为不可用。

●第54~67行的bindSIM()方法用于绑定SIM卡,通过if语句判断如果当前SIM卡没有绑定,则获取手机的SIM卡串号,并存入SharedPreferences对象中,如果绑定了则弹出Toast进行,并将按钮设置为不可用。

在检测SIM卡是否发生变化时,可以使用Application类。该类是Android框架的一个系统组件,当Android程序启动时系统会创建Application对象(单例模式的一个类,需要在application标签增加name属性,并添加Application名字即可)。正是由于它的这种特性,可以将检测SIM卡是否变更的方法放在Application的onCreate()方法中,当程序启动时就会检测SIM卡是否变更。接下来创建一个Application子类App,具体代码如【文件2-30】所示。

【文件2-30】App.java

代码说明:

●第9~12行代码用于获取当前手机防盗保护的状态。

●第13~33行代码用于判断手机SIM卡是否更换,当防盗保护开启时,获取绑定的SIM卡串号,然后获取当前手机SIM卡串号进行比对,如果一致则代表SIM卡未发生变化,如果不一致则代表SIM卡发生变化,此时需要向安全号码发送短信,提示手机SIM卡已更换。

多学一招:Application类

Application和Activity、Service一样是Android框架的一个系统组件,当Android程序启动时系统会创建一个Application对象(只创建一个,所以Application可以说是单例模式的一个类),用来存储系统的一些信息,如全局变量,全局变量相对静态类更有保障,直到应用的所有Activity全部被销毁之后才会被释放。

通常情况下Application是不需要手动指定的,系统会自动创建。如果需要自己创建Application,也很简单,只需创建一个类继承Application,并在AndroidManifest.xml文件中的application标签进行注册即可(只需要给application标签增加name属性,并添加自己的Application名字)。

当Application启动时,系统会创建一个PID,即进程ID,所有的Activity都会在此进程上运行。也就是说,当Application创建时会初始化全局变量,同一个应用的所有Activity都可以取得这些全局变量的值,换句话说,在某一个Activity中改变了这些全局变量的值,那么在同一个应用的其他Activity中值也会改变。

Application对象的生命周期是整个程序中最长的,它的生命周期就等于整个程序的生命周期。由于它是全局的单例的,在不同的Activity、Service中获得的对象都是同一个对象,因此可以通过Application来进行一些数据传递、数据共享和数据缓存等操作。

在Android系统中,有些手机SIM卡更换后,需要重新启动手机识别新的SIM卡,因此,为了最大限度地知道SIM卡变化,还需要创建一个开机启动的广播接收者,监听手机开机事件,并调用App中的correctSIM()方法判断SIM卡是否变更,开机启动的广播接收者如【文件2-31】所示。

【文件2-31】BootCompleteReciever.java

在AndroidManifest.xml文件中,注册开机启动的广播接收者以及配置权限信息,具体代码如下所示:

2.5.5 向导功能(三)

设置向导第三个界面用于选择或输入安全联系人,当手机SIM卡变更后会向安全号码发送短信通知,具体如【文件2-32】所示。

【文件2-32】SetUp3Activity.java

代码说明:

●第13~21行的initView()方法用于初始化控件,指定第三个小圆点被选中,并判断是否指定过安全号码,如果指定过则会显示在当前文本编辑框中。

●第23~34行的showNext()方法用于判断文件编辑框中是否有输入了安全号码,如果没有则弹出提示让用户输入号码,否则将号码保存在SharedPreferences对象中,然后开启SetUp4Activity进入下一个界面,并关闭当前Activity。

●第40~46行的onClick()方法用于响应按钮的点击事件,当点击添加联系人按钮时,会跳转到ContactSelectActivity中选择要添加的安全号码(查询联系人功能会在下一小节实现)。

●第48~55行的onActivityResult()方法用于接收联系人界面中选中的联系人信息,并将联系人号码展示到安全号码中。

2.5.6 获取联系人

在设置向导第三个界面中,当点击“添加联系人”按钮时,需要跳转到联系人列表界面。获取联系人功能相对来说比较独立,因此将其作为一个单独小节进行讲解。

1.联系人列表UI

由于联系人信息是以条目依次展示的,因此,在界面中可以使用ListView控件,联系人列表的图形化界面如图2-16所示。

图2-16所示联系人列表界面对应的布局文件如【文件2-33】所示。

【文件2-33】activity_contact_select.xml

上述布局文件中,引入了一个titlebar.xml布局,该布局是一个标题栏,用于展示“选择联系人”的标题,以及在左侧放置一个返回按钮,当点击返回按钮时返回到设置向导第三个界面。

下面开发联系人列表的Item布局,用于填充activity_contact_select.xml布局,Item布局的图形化界面如图2-17所示。

图2-17中Item对应的布局文件如【文件2-34】所示。

图2-16 联系人

图2-17 Item布局

【文件2-34】item_list_contact_select.xml

上述布局文件中,定义了一个View控件以及两个TextView控件,其中View控件用于显示联系人图标,TextView控件分别用于显示联系人姓名、电话号码。

2.联系人的实体类

接下来开发一个ContactInfo类,该类用于封装联系人信息,如姓名、手机号码等,具体代码如【文件2-35】所示。

【文件2-35】ContactInfo.java

3.解析联系人

联系人信息都存储在SQLite数据库中,因此需要先获取到联系人的id,根据id在data表中查询联系人名字以及电话号码,并封装到ContactInfo中,然后存入List集合,具体代码如【文件2-36】所示。

【文件2-36】ContactInfoParser.java

4.数据适配器

从数据库查询出的联系人信息,需要通过数据适配器填充到ListView中,接下来定义联系人列表的数据适配器ContactAdapter,具体代码如【文件2-37】所示。

【文件2-37】ContactAdapter.java

多学一招:ListView优化

在使用ListView控件的过程中,由于加载条目过多在滑动时可能造成卡顿。这是因为ListView在当前屏幕显示多少个条目,就会创建多少个对象,每一个条目都是一个对象。在滑动时,滑出屏幕的条目对象会被销毁,新加载到屏幕上的条目会创建新的对象,这样在ListView快速滑动时就会不断地创建对象→销毁对象→创建对象,并且每一个条目都需要加载一次布局,加载布局时会不断进行findViewById()操作初始化控件,而布局XML文件是以树形结构进行加载,每次加载一个条目都需要从根节点进行初始化,这样对内存消耗也比较大,并且浪费时间。如果每个条目都有图片,图片加载的时间比较长,就会造成内存溢出异常。为此就需要对ListView进行优化,优化的目的是在滑动时不会重复创建对象,减少内存消耗和屏幕渲染处理。具体步骤如下:

(1)创建静态类

创建一个静态类,将需要加载的控件变量放在该静态类中,保证所有控件只创建一次对象,不会重复创建对象,具体代码如下:

(2)复用缓存View对象

在Adapter的getView(int position,View convertView,ViewGroup parent)方法中,第二个参数convertView代表的就是之前滑出屏幕的条目对象。如果是第一次加载该方法,会创建新的View对象,如果滑动ListView,滑动出屏幕的View对象会以缓存的形式存在,而convertView就是缓存的View对象,可以复用缓存该对象减少新对象的创建。在加载布局时先判断convertView是否存在,如果convertView==null说明没有缓存的View对象,则使用View.inflate()方法加载布局,进行布局的初始化,否则复用缓存的View对象,具体代码如下:

多学一招:ListView优化

需要注意的是,通常情况下getView()方法中最后的返回值都是View,但如果复用了convertView,最后的返回值一定要改为convertView,这样才会将布局显示到页面中。

至此ListView的优化已经完成,通过以上几个步骤便可保证ListView滑动时,无论有多少个条目或者滑动速度多快,ListView只会创建一屏的条目对象,不会创建多余对象,对滑动的流畅度和内存占用都进行了优化。在进行项目开发中,都会采用该方法对ListView进行优化。

5.联系人Activity

获取联系人所需的文件都已开发完成,接下来在ContactSelectActivity类中将数据填充到界面中即可,具体代码如【文件2-38】所示。

【文件2-38】ContactSelectActivity.java

代码说明:

●第25~54行的initView()方法用于初始化控件,并注册ListView的条目点击事件,然后通过Adapter将联系人信息添加到界面中。

●第56~62行的onClick()方法用于响应返回按钮的点击事件,当点击该按钮时关闭当前窗口。

2.5.7 向导功能(四)

设置向导第四个界面用于展示设置完成界面,并默认开启防盗保护功能(可以手动关闭防盗保护),具体代码如【文件2-39】所示。

【文件2-39】SetUp4Activity.java

代码说明:

●第10~38行的initView()方法用于初始化控件,设置第四个小圆点被选中,并为ToggleButton按钮注册onCheckedChanged事件,当按钮为被选中状态时,显示“防盗保护已经开启”,反之则显示“防盗保护没有开启”,然后将按钮的状态存储到SharedPreferences对象中。其中第27~34行代码用于判断防盗保护是否开启,如果已经开启则将按钮设置为被选中状态,否则将按钮设置为未被选中状态。

●第40~46行的showNext()方法用于将设置过向导的状态存储到SharedPreferences中,然后关闭当前界面并跳转到LostFindActivity类中。

●第48~50行的showPre()方法用于将显示上一个界面,并关闭当前界面。

2.5.8 防盗指令

防盗指令界面可以控制防盗保护功能的开启和关闭、重新进入设置向导界面,以及展示当前可通过哪些指令来远程操控手机,具体代码如【文件2-40】所示。

【文件2-40】LostFindActivity.java

代码说明:

●第18~20行的isSetUp()方法用于获取SharedPreferences存入的isSetUp是否为true,用来判断是否设置过向导。

●第37~44行代码用于查询手机防盗是否开启(默认状态为开启),如果已经开启,则将文字展示为“防盗保护已经开启”,并将ToggleButton按钮设置为true;如果没有开启,则将文字展示为“防盗保护没有开启”,并将ToggleButton按钮设置为false。

●第45~60行代码是为ToggleButton按钮注册状态改变的监听事件,当按钮被选中时,则当前文字显示“防盗保护已经开启”,否则显示“防盗保护没有开启”,最后将防盗保护状态是否开启存入SharedPreferences对象中。

●第62~73行的onClick()方法用于响应“重新进入设置向导”按钮以及标题栏返回按钮的点击事件,当点击重新进入设置向导按钮时,进入设置向导第一个界面,当点击返回按钮时,则关闭当前Activity,返回主界面。

●第74~78行的startSetUp1Activity()方法用于跳转到SetUp1Activity界面重新进入设置向导,并关闭当前界面。

为了监听安全号码发送的防盗指令,需要创建一个广播接收者,根据收到的防盗指令来执行不同的操作,具体代码如【文件2-41】所示。

【文件2-41】SmsLostFindReciver.java

代码说明:

●第6~8行代码用于获取SharedPreferences对象中存入的key为protecting(表示防盗保护是否开启)的值,如果开启则执行if语句中的操作。

●第11~12行代码用于获取超级管理员权限,只有超级管理员才能完成远程清除数据和远程锁屏功能,超级管理员权限需要在清单文件中配置。

●第13~17行代码用于遍历数据库中的短信,获取发件人以及发送的短信内容。

●第18~44行代码用于获取安全联系人的号码,并判断发送短信的号码中是否有安全联系人发送的,如果有则执行下面的if语句,分别根据发送的防盗指令返回位置信息、播放报警音乐、远程清除数据、远程锁屏功能。其中第35行清除数据代码和第40行锁屏代码是必须要有超级管理员权限的,并且当调用这段代码时,必须开启超级管理员权限,否则系统会崩溃。

值得一提的是,开启超级管理员权限有两种方式,一种是手动开启,另一种是通过代码开启。手动开启相对来说比较简单,通过“设置”→“安全”→“设备管理器”选中手机安全卫士项目即可。代码开启相对比较麻烦,不过在程序开发中大部分都是通过代码开启超级管理员权限。

在Android系统中,为了信息安全,普通用户是无法随意删除系统中数据的,因此在执行远程锁屏和删除数据时,需要获得超级管理员权限,接下来定义一个超级管理员的广播接收者,具体代码如【文件2-42】所示。

【文件2-42】MyDeviceAdminReciever.java

在AndroidManifest.xml文件中,注册超级管理员的广播接收者,具体代码如【文件2-43】所示。

【文件2-43】AndroidManifest.xml

上述代码中,定义了一个描述信息description以及label,这些信息是放置在strings.xml文件中的,还添加了一个权限android:permission="android.permission.BIND_DEVICE_ADMIN",用于指定绑定超级管理员的权限。

<meta-data>标签表示超级管理员的元数据,其中的resource属性表示资源,用于指定安全策略,在此将安全策略放置在device_admin_sample.xml文件中,具体代码如【文件2-44】所示。

【文件2-44】res/xml/device_admin_sample.xml

下面创建防盗指令中使用的定位服务(GPSLocationService),该服务用于获取手机的经度、纬度、移动速度、精确度等,具体代码如【文件2-45】所示。

【文件2-45】GPSLocationService.java

代码说明:

●第10~22行的onCreate()方法用于位置提供者,首先获取系统的位置管理器,然后通过Criteria对象返回可用的位置提供者,通过lm.getBestProvider(criteria,true)获取最好的位置提供者,最后通过lm.requestLocationUpdates(name,0,0,listener)使用位置提供者。

●第23~50行定义了一个位置监听器MyListener,并实现了位置变化的四个方法,其中onLocationChanged()方法是当手机位置发生变化时调用,因此可以在该方法中获取手机的精确度、移动速度、纬度、经度,然后拼接成一个字符串发送给安全号码。onStatusChanged()方法表示当前位置提供者位置发生变化时调用。onProviderEnabled()方法表示当前位置提供者可用的时候调用,onProviderDisabled()方法表示当前位置提供者不可用时调用。

●第52~56行onDestroy()方法用于注销当前的位置监听器。接下来在AndroidManifest.xml文件中注册GPSLocationService服务,具体代码如下所示:

2.5.9 修改HomeActivity文件

手机防盗模块代码已编写完成,接下来需要在HomeActivity中进行调用,在调用该模块代码时,首先需要调用设置密码对话框代码,在这段代码中判断两次输入的密码是否一致,是否设置过密码等,如果设置过密码则调用输入密码对话框代码,判断密码是否正确,具体代码如【文件2-46】所示。

【文件2-46】HomeActivity.java

代码说明:

●第22~64行代码用于响应GridView条目的点击事件,当点击第一个功能图标时,也就是switch语句中的case等于0时,会进入手机防盗功能模块。点击其他的图标会进入相应模块,在此将其跳转代码全部实现。

●第66~78行代码用于获取设备的超级管理员权限,首先得到PolicyManager对象,然后通过该对象申请超级管理员权限。在程序启动时这段代码就会执行,方便后期通过管理员权限锁屏以及清除数据。

●第83~117行的showSetUpPswdDialog()方法用于弹出设置密码对话框,首先在setUpPasswordDialog.setCallBack()回调方法中创建SetUpPasswordDialog.MyCallBack(){}对象,实现该回调接口中的ok()方法及cancle()方法,并通过if语句判断两次输入的密码是否一致。

●第121~149行的showInterPswdDialog()方法用于弹出输入密码对话框,同样在mInPswdDialog.setCallBack()方法中创建InterPasswordDialog.MyCallBack(){}对象,实现confirm()方法以及cancle()方法,当点击“确定”按钮时判断输入的密码是否正确,当点击“取消”按钮时关闭对话框。

●第154~159行的savePswd()方法用于将加密过的密码保存到SharedPreferences对象中。

●第164~171行的getPassword()方法用于从SharedPreferences对象中获取保存的密码。

●第173~179行的isSetUpPassword()方法用于判断用户是否设置过手机防盗密码。