![Android项目实战:手机安全卫士](https://wfqqreader-1252317822.image.myqcloud.com/cover/676/31728676/b_31728676.jpg)
1.2 欢迎界面
在每个应用程序中欢迎界面都是必不可少的,它的主要作用是展示产品Logo、检查程序完整性、检查程序的版本更新、加载广告页、做一些初始化操作等。本节将针对欢迎界面开发进行详细讲解。
1.2.1 开发流程图
在程序开发中,使用流程图可以更好地分析程序开发流程。接下来展示一下欢迎界面的开发流程图,具体如图1-13所示。
从图1-13可以看出,欢迎页面需要获取应用程序的本地版本号,并与服务器中应用程序版本比对,若版本号相同则进入主界面,若不相同则弹出版本升级对话框,让用户选择是否更新应用程序版本,如果选择更新则下载安装新版本APK文件,否则立即进入程序主界面。
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00019001.jpg?sign=1739528617-cCAUouoHQ9PmYA9ap34Gz8QFv6xZKF1x-0-0fd2bc01d6c21c677c3f0008952c785f)
图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所示。
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00019002.jpg?sign=1739528617-mx6M8H7QCl4aBMdgk9jVWzzuMjXiPvNv-0-42948e99459af802395693f454386686)
图1-14 欢迎界面
图1-14所示欢迎界面对应的布局文件如【文件1-1】所示。
【文件1-1】activity_splash.xml
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00019003.jpg?sign=1739528617-YsvHAdlLR9SUvwybmEznzz9prJzvITFi-0-158d13055af9f9928d30de6eea78c4d2)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00020001.jpg?sign=1739528617-gdg6kRp12hI1cJxrxmcMHv22bIrXzI2H-0-7df530e849f34c29896ba00957d16adc)
上述布局文件中,使用的是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信息如下所示:
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00020002.jpg?sign=1739528617-IyHzCFhmB1zDBevF3C09cJGH8RiPmssS-0-69baa7762c2740e699950fdc1601d062)
(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所示。
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00021001.jpg?sign=1739528617-uc2xxD01BfZk88E43UytK3uyfloBvGIE-0-135f1e4f212ee292a7e67a224fc31dad)
图1-15 存储数组
例如,一个数组包含了String、Number、Boolean、null类型数据,JSON的表示形式如下:
["abc",12345,false,null]
使用JSON表示Object类型数据时,Object对象以“{”开始,以“}”结束,每个“名称”后跟一个“:”(冒号);“名称/值”对之间使用“,”(逗号)分隔。其存储形式如图1-16所示。
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00021002.jpg?sign=1739528617-5h494fyeun89AZFA6DtRwDAHzt6q9Ra7-0-f5dc93c8ed90efe20accc2b03f601147)
图1-16 存储对象
例如,一个address对象包含城市、街道、邮编等信息,JSON的表示形式如下:
{"city":"Beijing","street":"Chaoyang Road","postcode":100025 }
当使用JSON存储Object时,其中的Value既可以是一个Object,也可以是数组,因此,复杂的Object可以嵌套表示。例如,一个Person对象包含name和address对象,其表示格式如下:
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00021003.jpg?sign=1739528617-SXYEwbR7WdHRdHw2i83o6QIq9CrwNwVf-0-d198a8215bf1e83d6b43531bbc858c99)
假设Value值是一个数组,例如,一个Person对象包含name和hobby信息,其表示格式如下:
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00022001.jpg?sign=1739528617-IT3tdA8OCQnlABXvphCQAUai8us0m9YU-0-9c2e86bf797fd26a61cbb6dab69761d2)
需要注意的是,如果使用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
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00022002.jpg?sign=1739528617-kLgPj2vsEUqkvvEua8yypI682VYAQ1jp-0-ac6dc6a87f30f2127a9f4c898716d614)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00023001.jpg?sign=1739528617-M789HPgPZDR88m65m2VEuDWH3oHkzZyb-0-9e7b95d2ccebd39a88bc321dc6a56371)
代码说明:
●第8~27行的downapk()方法用于下载APK,在该方法中创建HttpUtils对象,然后调用它指定的下载方法download(),在调用该方法时,需要实现RequestCallBack<File>接口中的三个抽象方法。
●第31~38行的MyCallBack回调接口用于监听文件的下载状态,它的作用与Resquest CallBack的作用一致。
多学一招:回调函数
在学习Android过程中,经常会遇到“回调函数”这个词,那么什么是回调函数呢?简单地说,回调函数就是通过其指针来调用的函数,它不会被自己所在的对象调用,只会在调用别人的方法的时候反过来被调用。大家都知道,Android程序是通过Java语言来实现的,Java中是没有指针的,因此在实现回调时都是通过接口或抽象类。
回调的过程简单理解为,在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现。当B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用。这种机制就称为回调。
回调机制是将实现功能和定义分离的一种手段,是一种松耦合的设计思想。接下来通过一段代码进行分析,具体如下:
(1)定义回调接口ICallBack
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00024001.jpg?sign=1739528617-v0Lu4RahQ7cujFxcfot2RpWAJlKmHa66-0-755741d0d1afe0567658f6fbc867ab04)
(2)定义一个实现类FooBar
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00024002.jpg?sign=1739528617-0q1savO3NBmYg37EtLVUF5CSGcgG5raW-0-62835351459c313e166888aa67d144de)
在上述代码中,第一段代码定义了一个回调接口ICallBack,该接口中有一个postExec()方法,但是并没有实现。第二段代码定义了一个FooBar类,该类中有一个setCallBack()方法,接收一个ICallBack参数,然后在doSth()方法中通过callBack.postExec()实现postExec()方法,最后在main()方法中进行调用。这就是一个回调函数的基本用法。
在Android开发中,回调函数使用非常广泛,下面列举两个回调函数的使用场景,让大家更直观地看到回调函数是如何应用的。
应用场景一:事件监听器的回调
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00024003.jpg?sign=1739528617-RFV8R0nUvCaxDm2HDR6X8Wl7ehpKEBmz-0-fdbcb1675962b71077a6176ccac47bd0)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00025001.jpg?sign=1739528617-83wZCswvBVNTlxKBw7QL6i2AX1tEwzwH-0-14f43bf97528f02aafbb69b328fe369b)
上面的代码给按钮加了一个事件监听器,自己不会显式地去调用onClick()方法。用户触发了该按钮的点击事件后,它会由Android系统来自动调用。
应用场景二:Activity生命周期中的回调
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00025002.jpg?sign=1739528617-4tKDBvBmX7j9voqw3ZnZbyPz2cCSuvMo-0-14441e35725ec7c0eae5990c5a5bcae8)
上面的代码是创建Activity时,系统自带的onCreate()方法,该方法不会被人为调用,但是它会在Android系统进行自动调用。
2.获取版本号和安装APK
在下载APK之前首先要获取到程序的本地版本号,当本地版本号与服务器版本号不一致时,弹出更新提醒对话框,进行下载安装。由于这部分代码功能比较独立,且在其他程序中也可以使用,因此将其抽取出来作为工具类MyUtils,具体代码如【文件1-3】所示。
【文件1-3】MyUtils.java
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00025003.jpg?sign=1739528617-zMWCMx5GvLObelVzmYTSPQjVyYwa4mgU-0-2ff91caeec46307ec122a52c7a69ca4c)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00026001.jpg?sign=1739528617-OpEt9kYPHjdWYL53M4FLfqujap7yyYfY-0-bb7b1f4b688c5467ffe67a1824d3a8da)
代码说明:
●第7~19行的getVersion()方法用于获取本地版本号,首先要获取到PackageManger对象,然后调用getPackageInfo()方法获取到PackageInfo对象,通过PackageInfo对象即可获取到本地版本号。
●第24~33行的installApk()方法利用了隐式意图开启了系统中用于安装APK的Activity。
1.2.5 版本更新工具类
通过前面的讲解可知,欢迎界面需要获取服务器中程序的版本号,【实现版本号比对】→【弹出更新提醒对话框】→【弹出下载APK进度条】→【替换安装程序】等。由于这个逻辑是一个整体,因此将这块代码抽取出来放在工具类VersionUpdateUtils中,具体代码如【文件1-4】所示。
【文件1-4】VersionUpdateUtils.java
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00026002.jpg?sign=1739528617-CIl1nPkh3fXjrYe5wjrhbCKTPL35H7r3-0-e035ff6345df52ccf153b67e60835b63)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00027001.jpg?sign=1739528617-DMLNt70YYanxfZMFHrsTcdf0gee7laT6-0-155e537cdaeab5fb948741ad5acbfc7c)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00028001.jpg?sign=1739528617-LXIQwULEy0umX7rMLT3xXXdQqELuDKEz-0-d8353a21de83e4f255176f7f350fd6f8)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00029001.jpg?sign=1739528617-GSCusdXhngTkMRAlW7iAwnOcFPVfbQbj-0-db79e4859b024bf8c562bb54185b9b5f)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00030001.jpg?sign=1739528617-tBGKbI2cF5uPWG2H6mut0nQoFn4DM3qm-0-7ef2845e8a7618063cf9426a516557a1)
代码说明:
●第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
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00031001.jpg?sign=1739528617-GpXfpdMrNgx5C7j2DAup0PlCDtfsKi78-0-1656af9feaa30a513591658859b24432)
1.2.7 欢迎界面逻辑
创建好一系列的工具类和实体类之后,接下来需要在SplashActivity中调用相应的代码,在欢迎页面中实现版本更新操作,具体代码如【文件1-6】所示。
【文件1-6】SplashActivity.java
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00031002.jpg?sign=1739528617-7rk257vevSRzDTCY3ZV7YOj8JzstGziJ-0-a675699b85880612d2c850377799c9cd)
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00032001.jpg?sign=1739528617-tGMdQaKKRCugDQRQVqltS4U8vIS87xhn-0-6c43b30044f748d96963bc4245474df5)
代码说明:
●第12行代码通过MyUtils类中的getVersion()方法获取到应用的本地版本号。
●第14~21行代码创建VersionUpdateUtils对象并开启子线程用于获取服务器端程序版本号。
●第24~27行initView()方法用于将获取到的本地版本号显示在界面上。
由于本程序需要请求网络下载APK到手机内存卡,因此需要在AndroidManifest.xml文件中添加访问网络和写SD卡的权限,具体代码如下所示:
![](https://epubservercos.yuewen.com/42DD75/17180239704450606/epubprivate/OEBPS/Images/img00032002.jpg?sign=1739528617-KPX02FKh6Poc5OF1TMoWc3Zf7PUaCx84-0-a0f3b59042679e896d1e3558ec7f2e2c)