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

1.2 欢迎界面

在每个应用程序中欢迎界面都是必不可少的,它的主要作用是展示产品Logo、检查程序完整性、检查程序的版本更新、加载广告页、做一些初始化操作等。本节将针对欢迎界面开发进行详细讲解。

1.2.1 开发流程图

在程序开发中,使用流程图可以更好地分析程序开发流程。接下来展示一下欢迎界面的开发流程图,具体如图1-13所示。

从图1-13可以看出,欢迎页面需要获取应用程序的本地版本号,并与服务器中应用程序版本比对,若版本号相同则进入主界面,若不相同则弹出版本升级对话框,让用户选择是否更新应用程序版本,如果选择更新则下载安装新版本APK文件,否则立即进入程序主界面。

图1-13 欢迎界面开发流程图

1.2.2 欢迎界面UI

在开发手机安全卫士项目时,首先需要创建一个工程,将其命名为“手机安全卫士”,将包名指定为“cn.itcast.mobliesafe”,然后创建第一个包cn.itcast.mobliesafe.chatper01。由于欢迎界面就是Splash界面,因此将要创建的Activity命名为“SplashActivity”,布局文件指定为“activity_splash.xml”,然后将欢迎界面的背景图(launch_bg.jpg)导入到drawable-hdpi目录中。欢迎界面的图形化界面如图1-14所示。

图1-14 欢迎界面

图1-14所示欢迎界面对应的布局文件如【文件1-1】所示。

【文件1-1】activity_splash.xml

上述布局文件中,使用的是RelativeLayout布局,该布局中放置了一个ProgressBar控件和一个TextView控件,ProgressBar控件用于显示程序加载的进度条,TextView控件用于显示程序的版本号。

需要注意的是, TextView中有几个特殊属性用于指定文字阴影效果,其中android:shadowColor属性用于指定阴影颜色, android:shadowDx 、 android:shadowDy 、android:shadowRadius属性分别用于指定阴影在X轴和Y轴上的偏移量以及阴影的半径。阴影的半径必须设置,当数值为0时无阴影,数值越大时阴影会越透明,扩散效果越明显。

1.2.3 服务器的搭建

由于本程序需要获取服务端应用的版本号以及下载服务器最新的APK,因此需要搭建一个服务器。搭建步骤如下:

(1)本程序使用Tomcat作为服务器,点击Tomcat目录下的bin/startup.bat开启服务器。

(2)创建一个HTML页面(updateinfo.html),该页面返回的信息需要包括服务器中APK的版本号、版本说明以及新版本的下载地址。updateinfo.html页面的JSON信息如下所示:

(3)将updateinfo.html页面以及手机安全卫士2.0的APK(经过签名打包用于发布的APK,而不是调试的APK)复制到Tomcat的webapps/ROOT文件夹下。

多学一招:JSON数据

JSON即JavaScript Object Notation(对象表示法),是一种轻量级的数据交换格式,它基于JavaScript的一个子集,使用了类似于C语言家族的习惯(包括C、C++、C#、Java、JavaScript、Perl、Python等)。这些特性使JSON成为理想的数据交互语言,易于阅读和编写,同时也易于机器解析和生成。

与XML一样,JSON也是基于纯文本的数据格式,并且JSON的数据格式非常简单,既可以用JSON传输一个简单的String、Number、Boolean类型数据,也可以传输一个数组,或者一个复杂的Object对象。

JSON有两种结构,具体如下:

●值的有序列表:在大部分语言中,它被理解为数组(array)。

●“名称/值”对的集合:在不同的语言中,它被理解为对象、记录、结构、字典、哈希表等。

这些都是常见的数据结构,事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在不同的编程语言之间交互成为可能。

使用JSON表示数组时,数组以“[”开始,以“]”结束,每个元素之间使用“,”(逗号)分隔(元素可以是任意的Value)。其存储形式如图1-15所示。

图1-15 存储数组

例如,一个数组包含了String、Number、Boolean、null类型数据,JSON的表示形式如下:

["abc",12345,false,null]

使用JSON表示Object类型数据时,Object对象以“{”开始,以“}”结束,每个“名称”后跟一个“:”(冒号);“名称/值”对之间使用“,”(逗号)分隔。其存储形式如图1-16所示。

图1-16 存储对象

例如,一个address对象包含城市、街道、邮编等信息,JSON的表示形式如下:

{"city":"Beijing","street":"Chaoyang Road","postcode":100025 }

当使用JSON存储Object时,其中的Value既可以是一个Object,也可以是数组,因此,复杂的Object可以嵌套表示。例如,一个Person对象包含name和address对象,其表示格式如下:

假设Value值是一个数组,例如,一个Person对象包含name和hobby信息,其表示格式如下:

需要注意的是,如果使用JSON存储单个数据(如“abc”),一定要使用数组的形式,不要使用Object形式,因为Object形式必须是“名称/值”的形式。

1.2.4 下载和安装APK

1.下载APK

本项目采用第三方开源框架xUtils下载APK,因此需要将xUtils的jar包复制到工程libs目录下,选中xUtils工具包并右击,选择Build Path→Add to Build Path命令将jar包导入工程。

xUtils是Android的第三方开源框架,它起源于Afinal框架(用于发送HTTP请求、显示Bitmap图片等),同时xUtils包含了很多实用的Android工具类。接下来介绍一下本项目用到xUtils的几个类和方法,具体如下所示:

(1)HttpUtils类用于发送HTTP请求、上传文件、下载文件等,其中HttpUtils的download(String url,String target,RequestCallBack<File> requestCallBack)方法是用来下载文件的。参数url代表要下载文件的路径,target代表下载文件的本地路径,requestCallBack是一个接口对象用于监听文件下载状态的。

(2)RequestCallBack<File>接口有三个抽象方法,分别在下载成功、下载失败、下载中调用,通过这三个抽象方法可以获取到文件的下载状态。

在cn.itcast.mobliesafe.chapter01中创建utils包,用于存放下载文件的工具类DownLoadUtils.java,具体代码如【文件1-2】所示。

【文件1-2】DownLoadUtils.java

代码说明:

●第8~27行的downapk()方法用于下载APK,在该方法中创建HttpUtils对象,然后调用它指定的下载方法download(),在调用该方法时,需要实现RequestCallBack<File>接口中的三个抽象方法。

●第31~38行的MyCallBack回调接口用于监听文件的下载状态,它的作用与Resquest CallBack的作用一致。

多学一招:回调函数

在学习Android过程中,经常会遇到“回调函数”这个词,那么什么是回调函数呢?简单地说,回调函数就是通过其指针来调用的函数,它不会被自己所在的对象调用,只会在调用别人的方法的时候反过来被调用。大家都知道,Android程序是通过Java语言来实现的,Java中是没有指针的,因此在实现回调时都是通过接口或抽象类。

回调的过程简单理解为,在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现。当B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用。这种机制就称为回调。

回调机制是将实现功能和定义分离的一种手段,是一种松耦合的设计思想。接下来通过一段代码进行分析,具体如下:

(1)定义回调接口ICallBack

(2)定义一个实现类FooBar

在上述代码中,第一段代码定义了一个回调接口ICallBack,该接口中有一个postExec()方法,但是并没有实现。第二段代码定义了一个FooBar类,该类中有一个setCallBack()方法,接收一个ICallBack参数,然后在doSth()方法中通过callBack.postExec()实现postExec()方法,最后在main()方法中进行调用。这就是一个回调函数的基本用法。

在Android开发中,回调函数使用非常广泛,下面列举两个回调函数的使用场景,让大家更直观地看到回调函数是如何应用的。

应用场景一:事件监听器的回调

上面的代码给按钮加了一个事件监听器,自己不会显式地去调用onClick()方法。用户触发了该按钮的点击事件后,它会由Android系统来自动调用。

应用场景二:Activity生命周期中的回调

上面的代码是创建Activity时,系统自带的onCreate()方法,该方法不会被人为调用,但是它会在Android系统进行自动调用。

2.获取版本号和安装APK

在下载APK之前首先要获取到程序的本地版本号,当本地版本号与服务器版本号不一致时,弹出更新提醒对话框,进行下载安装。由于这部分代码功能比较独立,且在其他程序中也可以使用,因此将其抽取出来作为工具类MyUtils,具体代码如【文件1-3】所示。

【文件1-3】MyUtils.java

代码说明:

●第7~19行的getVersion()方法用于获取本地版本号,首先要获取到PackageManger对象,然后调用getPackageInfo()方法获取到PackageInfo对象,通过PackageInfo对象即可获取到本地版本号。

●第24~33行的installApk()方法利用了隐式意图开启了系统中用于安装APK的Activity。

1.2.5 版本更新工具类

通过前面的讲解可知,欢迎界面需要获取服务器中程序的版本号,【实现版本号比对】→【弹出更新提醒对话框】→【弹出下载APK进度条】→【替换安装程序】等。由于这个逻辑是一个整体,因此将这块代码抽取出来放在工具类VersionUpdateUtils中,具体代码如【文件1-4】所示。

【文件1-4】VersionUpdateUtils.java

代码说明:

●第9~34行的Handler代码用于线程间通信,及时通知主线程更新UI,并进入主界面。

●第47~85行的getCloudVersion()方法用于访问网络获取服务器版本号,首先创建一个HttpClient对象,然后通过HttpConnectionParams设置链接超时时间和请求超时时间,并通过HttpGet请求updateinfo.html页面,解析该页面的JSON数据,与本地版本号进行比对,如果不一致则使用Handler发送消息。

●第90~117行的showUpdateDialog方法用于弹出升级对话框,当点击“暂不升级”按钮时,会进入主界面,当点击“立即升级”时,会初始化下载对话框调用下载APK的方法。

●第130~155行的downloadNewApk()方法用于下载APK,在该方法中调用了DownLoadUtils.downapk()方法。当APK下载完成后,调用了MyUtils.installApk()方法进行安装。

上述代码是一个完整的下载更新流程,因此只需要在Activity中创建VersionUpdateUtils实例,并在子线程中调用getCloudVersion()方法即可。

1.2.6 版本信息的实体类

由于从服务器中获取的程序版本号、版本描述、下载地址需要存储到实体类中,因此需要定义一个实体类VersionEntity,具体代码如【文件1-5】所示。

【文件1-5】VersionEntity.java

1.2.7 欢迎界面逻辑

创建好一系列的工具类和实体类之后,接下来需要在SplashActivity中调用相应的代码,在欢迎页面中实现版本更新操作,具体代码如【文件1-6】所示。

【文件1-6】SplashActivity.java

代码说明:

●第12行代码通过MyUtils类中的getVersion()方法获取到应用的本地版本号。

●第14~21行代码创建VersionUpdateUtils对象并开启子线程用于获取服务器端程序版本号。

●第24~27行initView()方法用于将获取到的本地版本号显示在界面上。

由于本程序需要请求网络下载APK到手机内存卡,因此需要在AndroidManifest.xml文件中添加访问网络和写SD卡的权限,具体代码如下所示: