文章目录
- 1.手机防盗需求分析
- 2.手机卫士其余模块需求分析
- 3.手机卫士的包名划分
- 4.SVN提交代码 & 代码的下载
- 5.SplashActivity布局分析
- 6.文本框阴影效果
- 7.Activity去头操作 & 保留高版本主题
- 8.获取版本名称并且展示
- 9.构建服务器端json & 无BOM编码
- 10.请求网络数据 & 测试
- 11.json解析过程
- 12.断点调试json解析错误过程
- 13.消息机制 & 发送不同类型的消息
- 14.弹出对话框
- 15.xUtils说明 & 下载方法使用
- 16.打包生成apk & 维护到服务器
- 17.签名文件说明 & 包名说明
- 18.安装过程中点击取消
1.手机防盗需求分析
首先应用要有一个闪屏页面,闪屏页面(SplashActivity)会有以下的功能:
- 版本名称的展示
- 服务端新版本的检测
- 展示logo
而手机防盗功能,需要有以下的功能:
- sim卡绑定:每一款手机都会有相应的卡的序列号(唯一性),一旦替换掉原有电话卡,序列号会发生改变
- GPS追踪
- 远程锁屏
- 数据销毁
防盗模块大体功能图如下:
2.手机卫士其余模块需求分析
其他模块包括黑名单管理,软件管理、进程管理、流量统计、手机杀毒、缓存清理、高级工具、设置中心等模块,模块图大概如下所示:
高级工具的功能包括:
- 归属地查询
- 短信备份
- 常用号码查询
- 程序锁
3.手机卫士的包名划分
每一个类的包名,都应该按照相应的需求进行命名,一些常见的划分类型如下所示:
- 按照组成方式:命名方式为
com.itheima.db
- 按照业务逻辑方式:命名方式为
com.icbc.money
、com.icbc.meeting
- 按照组件划分:主要以四大组件为主,包括Activity、Service、ContentProvider、BroadCastReceiver,命名方式为
com.itheima.activity
、com.itheima.service
该工程我们将按组件划分的方式进行项目的实现
4.SVN提交代码 & 代码的下载
虽然目前主流的版本管理工具都使用Git,但这里我们尝试使用SVN,来实现代码仓库的托管以及相应的管理
具体的操作流程可以参考网上,这里不再赘述
5.SplashActivity布局分析
- 为了在后期维护中加上版本号等相关属性名,我们需要在manifest.xml中进行相应的配置,修改androidmanifest.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.mobilesafe"android:versionCode="1"android:versionName="1.0.0"><!-- android:versionCode 本地应用版本号是1,版本号是2,有必要提示用户更新 --><!-- android:versionName 本地应用版本名,假设版本名为1.0.0,它们所代表的意义如下:第一位:项目有重大更新(代码重构,大部分功能添加,界面整体添加)第二位:更新部分功能第三位:一般代表修复原有版本的bug --><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".activity.SplashActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
- 在项目下新建activity包,存放activity,然后将MainActivity改名为SplashActivity,同时将布局文件修改为activity_splash.xml,作为闪屏页面
- 修改activity_splash.xml,使用相对布局,并使用相应的背景图片,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/launcher_bg"tools:context=".activity.SplashActivity"><TextViewandroid:id="@+id/tv_version_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="版本名称"/></RelativeLayout>
6.文本框阴影效果
为了美化闪屏页面的显示效果,这里我们进行一些文本框阴影效果的处理,预览图如下所示:
修改activity_splash.xml,给TextView控件添加实现阴影效果的标签(android:shadow),并且添加一个进度条组件,注意调整位置,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"android:background="@drawable/launcher_bg"tools:context=".activity.SplashActivity"><!-- android:shadowRadius="5" 表示阴影所在范围 --><TextViewandroid:id="@+id/tv_version_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:shadowDx="1"android:shadowDy="1"android:shadowColor="#f00"android:shadowRadius="5"android:text="版本名称"/><ProgressBarandroid:layout_below="@id/tv_version_name"android:layout_centerHorizontal="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/></RelativeLayout>
7.Activity去头操作 & 保留高版本主题
因为SplashActivity是没有标题栏的,所以需要进行相应的设置,实现的方式有很多:
- 代码中修改
- manifest中修改
- style中修改
由于前两种方式都不灵活,这里采用第三种方式进行修改
进入@style/Theme.AppCompat.Light.NoActionBar
查看源码,我们可以发现该样式中控制标题栏是否显示的语句为:
<item name="windowNoTitle">true</item>
因为当前Android应用默认使用@style/AppTheme
样式,所以只需要进入该样式并加入上面的语句,即可显示相同的效果,代码如下:
<resources><!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item><item name="windowNoTitle">true</item></style></resources>
这样,就可以实现在AppTheme样式中没有标题栏的效果了。与此同时,也可以保留高版本的样式主题,一举两得
8.获取版本名称并且展示
由于版本号是非固定的,而是随着后期开发逐渐变化的,所以想要显示版本号就需要编写相应的逻辑
- 修改SplashActivity,新增initUI()方法,作为UI初始化的方法,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;import com.example.mobilesafe.R;public class SplashActivity extends AppCompatActivity {private TextView tv_version_name;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}
}
- 修改SplashActivity,新增initData()方法,作为数据初始化的方法,并且再新增getVersionName()方法,作为获取版本号名称的逻辑封装,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.TextView;import com.example.mobilesafe.R;public class SplashActivity extends AppCompatActivity {private TextView tv_version_name;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}
}
注意:如果使用的是Android Studio作为IDE,这里要修改versionName时光修改manifest.xml时不生效,要同步修改build.gradle中的versionName才生效
9.构建服务器端json & 无BOM编码
这一步我们需要搭建服务器,以向服务器获取相应数据并进行更新,更新界面类似如下:
- 修改SplashActivity,添加getVersionCode(),作为获取本地版本号的方法,放到initData()方法中,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.TextView;import com.example.mobilesafe.R;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地版本号mLocalVersionCode = getVersionCode();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}
}
- 编写JSON文件,作为提醒客户端进行更新的相应文件,这里使用HiJson来进行相应的编写,注意最后要保存为json格式的不带BOM编码的UTF-8编码格式,因为该格式在Android2.2版本以后会出现解析上的问题,JSON代码如下:
{"versionCode": "2","versionDes": "2.0版本发布了!","versionName": "2.0","downloadUrl": "www.abc.com"
}
- 将刚刚编写好的JSON文件放到Tomcat服务器中,这里为了方便测试,直接将该文件(作者这里将该文件起名为update74.json)放到webapps/ROOT目录下即可
- 启动Tomcat服务器,直接到bin目录下双击startup.bat,出现以下界面即可说明服务器启动成功,如图所示:
10.请求网络数据 & 测试
上一节中我们编写了JSON文件,这里需要对该文件进行一个简单的测试,看是否能够调用
- 修改SplashActivity,添加checkVersion(),作为获取服务端版本号的方法,放到initData()方法中,注意这里的请求地址要改成你的电脑的IP地址或者Google推荐的10.0.2.2(仅限模拟器访问电脑上的TomCat),而非localhost,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {new Thread(){@Overridepublic void run() {// 发送请求,获取数据,参数则为请求json的链接地址try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);}}catch (MalformedURLException e) {e.printStackTrace();}catch (IOException e) {e.printStackTrace();}}}.start();}
}
- 在包下新建utils包,并在该包下新建StreamUtil,作为将流转换为字符串的工具类,代码如下:
package com.example.mobilesafe.utils;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;public class StreamUtil {/*** 将流转换成字符串* @param is 流对象* @return 流转换成的字符串,返回null代表异常*/public static String streamToString(InputStream is) {// 1.在读取的过程中,将读取的内容存储至缓存中,然后一次性的转换成字符串返回ByteArrayOutputStream bos = new ByteArrayOutputStream();// 2.读取流,读到没有为止(循环)byte[] buffer = new byte[1024];// 3.记录读取内容的临时变量int temp = -1;try {while ((temp = is.read(buffer)) != -1){bos.write(buffer,0,temp);}// 4.返回读取的数据return bos.toString();}catch (IOException e){e.printStackTrace();}finally {try {is.close();bos.close();} catch (IOException e) {e.printStackTrace();}}return null;}
}
- 由于涉及到网络操作,需要在manifest.xml中声明权限,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.mobilesafe"android:versionCode="1"android:versionName="1.0.0"><!-- android:versionCode 本地应用版本号是1,版本号是2,有必要提示用户更新 --><!-- android:versionName 本地应用版本名,假设版本名为1.0.0,它们所代表的意义如下:第一位:项目有重大更新(代码重构,大部分功能添加,界面整体添加)第二位:更新部分功能第三位:一般代表修复原有版本的bug --><!-- 添加网络权限 --><uses-permission android:name="android.permission.INTERNET"/><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".activity.SplashActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
- 运行应用,进行相应测试(可以使用Log类),输出结果如图所示:
11.json解析过程
之前我们已经拿到了从流转化为字符串的文本,现在需要将这段文本转换为json格式
修改SplashActivity,添加json解析的相应逻辑,这里使用JSONObject来解析,建议使用Log类打印日志,观察是否能够解析成功,代码如下:
package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {new Thread(){@Overridepublic void run() {// 发送请求,获取数据,参数则为请求json的链接地址try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");String versionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");}}catch (MalformedURLException e) {e.printStackTrace();}catch (IOException e) {e.printStackTrace();}catch (JSONException e) {e.printStackTrace();}}}.start();}
}
12.断点调试json解析错误过程
如果在解析json数据中,存在一些问题,那么可能会导致json的解析出错,这时候我们就需要通过断点调试的方式来进行排错
具体排错过程可参考百度,这里不再详述
13.消息机制 & 发送不同类型的消息
因为在比对版本号之后需要弹出“更新”的对话框,由于是UI操作,而Android规定不能在子线程更新UI,所以这里我们需要使用消息机制来发送消息,通知主线程进行UI操作
- 修改SplashActivity,添加消息机制,并在checkVersion()方法中处理相应逻辑,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新break;case ENTER_HOME:// 2.直接进入应用程序主界面break;case URL_ERROR:// 3.弹出URL错误break;case IO_ERROR:// 4.弹出IO错误break;case JSON_ERROR:// 5.弹出JSON错误break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");String versionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.发送消息mHandler.sendMessage(msg);}}}.start();}
}
- 修改SplashActivity,添加enterHome(),作为进入应用程序主界面的方法,同时在activity包下创建HomeActivity,作为主界面,暂时套用EmptyAcivity模板即可,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误break;case IO_ERROR:// 4.弹出IO错误break;case JSON_ERROR:// 5.弹出JSON错误break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");String versionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}
}
- 修改SplashActivity,修改checkVersion()方法,为了优化视觉效果,将引导页面跳转到主页面的时间设置为4秒,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误break;case IO_ERROR:// 4.弹出IO错误break;case JSON_ERROR:// 5.弹出JSON错误break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");String versionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}
}
14.弹出对话框
上面我们增加了相应的消息机制,这一节来继续完善机制,并添加相应的UI操作
- 在utils包下新建ToastUtil,作为弹出对话框的工具类,方便之后调用,代码如下:
package com.example.mobilesafe.utils;import android.content.Context;
import android.widget.Toast;public class ToastUtil {/*** Toast打印* @param ctx 上下文* @param msg 打印文本内容*/public static void show(Context ctx,String msg){Toast.makeText(ctx, msg,Toast.LENGTH_SHORT).show();}
}
- 修改SplashActivity,在Handler中添加每个分支的处理逻辑,使用刚刚编写好的工具类,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");String versionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}
}
- 修改SplashActivity,添加showUpdateDialog(),作为显示更新对话框的方法,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;import org.json.JSONException;
import org.json.JSONObject;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");String downloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrl}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.show();}
}
15.xUtils说明 & 下载方法使用
完善了消息机制后,接下来我们就需要处理“下载”逻辑的相关代码,这里的下载逻辑使用“多线程下载”,即将某个文件划分成3份,每一份都有相应的线程去做下载,记录当前的下载位置,下一次就在当前记录的下载位置继续下载
为了便于下载逻辑的实现,这里还使用到一个框架:xUtils,这是aFinal框架的前身,感兴趣的读者可以去百度查阅
xUtils的集成只需要直接导入jar包,并且添加相应权限即可,这里不再详述
修改SplashActivity,修改showUpdateDialog()方法,新建downloadApk()方法来完善下载逻辑,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;
import org.json.JSONObject;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mobilesafe74.apk";// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}
}
16.打包生成apk & 维护到服务器
下载逻辑完善的同时,我们需要将该(高版本)的应用打包成apk,并且维护到服务器上
打包的过程可以参考百度,这里仅作简单陈述:
- 生成签名文件,并且指定所在位置
- 使用生成的签名文件,给工程打包生成一个apk
打包成功后,只需要放置到跟之前的json文件同一个tomcat中的目录,即可将apk文件维护到服务器上
随后,修改json文件,将路径改成如图所示(apk文件因人而异,最好配置完成后可以去网页上测试一下,看能否访问到这个文件),运行文件,进行相应测试:
17.签名文件说明 & 包名说明
下载完成后,就应该提示用户安装这个apk,但是在安装后会提示“已安装了存在签名冲突的同名数据包”,所以我们需要分析签名文件和包名
- 修改SplashActivity,修改downloadApk()方法,新建installApk()方法来完善安装apk逻辑(使用隐式意图来启动系统的apk安装界面),代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;
import org.json.JSONObject;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "app-release.apk";Log.i(tag,"路径为:" + path);// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,回调方法)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;installApk(file);}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");e.printStackTrace();}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}/*** 9.APK安装* @param file 安装文件*/private void installApk(File file) {// 系统应用界面,源码,安装apk入口Intent intent = new Intent("android.intent.action.VIEW");intent.addCategory("android.intent.category.DEFAULT");// 文件作为数据源// intent.setData(Uri.fromFile(file));// 设置安装的类型// intent.setType("application/vnd.android.package-archive");intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");startActivity(intent);}
}
-
运行后,安装时会报错,原因如下:
- 包名一致(√):由于该下载会下载一个同签名,同包名的APK文件,就会导致之前提示的错误,这里需要的更新操作要覆盖掉旧的apk,所以需要保持包名一致
- 签名一致(×):直接在模拟器上运行的应用(旧版本),签名文件为debug.keystore,而新版本的签名文件在打包的过程中使用的是新的,所以实质上是不一致的
-
所以,为了保持签名一致,我们需要让旧版本的签名文件也跟新版本的签名文件一致,所以对旧版本专门进行一次打包,然后再安装到模拟器上,即可完成正常的安装
18.安装过程中点击取消
为了防止用户点击回退后出现bug,所以需要进一步完善相关逻辑
- 修改SplashActivity,修改showUpdateDialog()方法,添加按下回退按钮的事件监听,代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;
import org.json.JSONObject;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialog) {// 按下回退后,进入主界面,然后隐藏对话框enterHome();dialog.dismiss();}});// 回退按钮builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "app-release.apk";Log.i(tag,"路径为:" + path);// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,回调方法)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;installApk(file);}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");e.printStackTrace();}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}/*** 9.APK安装* @param file 安装文件*/private void installApk(File file) {// 系统应用界面,源码,安装apk入口Intent intent = new Intent("android.intent.action.VIEW");intent.addCategory("android.intent.category.DEFAULT");// 文件作为数据源// intent.setData(Uri.fromFile(file));// 设置安装的类型// intent.setType("application/vnd.android.package-archive");intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");startActivity(intent);}
}
-
修改SplashActivity,若在安装新的apk时点击“取消”按钮,就应该回到原来的activity(SplashActivity),然后再跳转到HomeActivity,完善这个逻辑,逻辑如图所示:
代码如下:
package com.example.mobilesafe.activity;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;import com.example.mobilesafe.R;
import com.example.mobilesafe.utils.StreamUtil;
import com.example.mobilesafe.utils.ToastUtil;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;
import org.json.JSONObject;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialog) {// 按下回退后,进入主界面,然后隐藏对话框enterHome();dialog.dismiss();}});// 回退按钮builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "app-release.apk";Log.i(tag,"路径为:" + path);// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,回调方法)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;installApk(file);}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");e.printStackTrace();}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}/*** 9.APK安装* @param file 安装文件*/private void installApk(File file) {// 系统应用界面,源码,安装apk入口Intent intent = new Intent("android.intent.action.VIEW");intent.addCategory("android.intent.category.DEFAULT");// 文件作为数据源// intent.setData(Uri.fromFile(file));// 设置安装的类型// intent.setType("application/vnd.android.package-archive");intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");startActivityForResult(intent,0);}/*** 10.开启一个Activity后,返回结果* @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {enterHome();super.onActivityResult(requestCode, resultCode, data);}
}
整个更新的流程如图所示: