首先应用要有一个闪屏页面,闪屏页面(SplashActivity)会有以下的功能:
而手机防盗功能,需要有以下的功能:
防盗模块大体功能图如下:
其他模块包括黑名单管理,软件管理、进程管理、流量统计、手机杀毒、缓存清理、高级工具、设置中心等模块,模块图大概如下所示:
高级工具的功能包括:
每一个类的包名,都应该按照相应的需求进行命名,一些常见的划分类型如下所示:
com.itheima.db
com.icbc.money
、com.icbc.meeting
com.itheima.activity
、com.itheima.service
该工程我们将按组件划分的方式进行项目的实现
虽然目前主流的版本管理工具都使用Git,但这里我们尝试使用SVN,来实现代码仓库的托管以及相应的管理
具体的操作流程可以参考网上,这里不再赘述
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobilesafe"
android:versionCode="1"
android:versionName="1.0.0">
<application
android: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>
<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">
<TextView
android:id="@+id/tv_version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="版本名称"/>
RelativeLayout>
为了美化闪屏页面的显示效果,这里我们进行一些文本框阴影效果的处理,预览图如下所示:
修改activity_splash.xml,给TextView控件添加实现阴影效果的标签(android:shadow),并且添加一个进度条组件,注意调整位置,代码如下:
<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">
<TextView
android: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="版本名称"/>
<ProgressBar
android:layout_below="@id/tv_version_name"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
RelativeLayout>
因为SplashActivity是没有标题栏的,所以需要进行相应的设置,实现的方式有很多:
由于前两种方式都不灵活,这里采用第三种方式进行修改
进入@style/Theme.AppCompat.Light.NoActionBar
查看源码,我们可以发现该样式中控制标题栏是否显示的语句为:
<item name="windowNoTitle">trueitem>
因为当前Android应用默认使用@style/AppTheme
样式,所以只需要进入该样式并加入上面的语句,即可显示相同的效果,代码如下:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- "colorPrimary"
>@color/colorPrimary
- "colorPrimaryDark">@color/colorPrimaryDark
- "colorAccent">@color/colorAccent
- "windowNoTitle">true
style>
resources>
这样,就可以实现在AppTheme样式中没有标题栏的效果了。与此同时,也可以保留高版本的样式主题,一举两得
由于版本号是非固定的,而是随着后期开发逐渐变化的,所以想要显示版本号就需要编写相应的逻辑
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
}
/**
* 1.初始化UI
*/
private void initUI() {
tv_version_name = findViewById(R.id.tv_version_name);
}
}
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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才生效
这一步我们需要搭建服务器,以向服务器获取相应数据并进行更新,更新界面类似如下:
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.获取包管理对象packageManager
PackageManager pm = getPackageManager();
// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息
try {
PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);
// 3.获取并返回版本编号
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
}
{
"versionCode": "2",
"versionDes": "2.0版本发布了!",
"versionName": "2.0",
"downloadUrl": "www.abc.com"
}
上一节中我们编写了JSON文件,这里需要对该文件进行一个简单的测试,看是否能够调用
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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();
}
}
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 xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobilesafe"
android:versionCode="1"
android:versionName="1.0.0">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android: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>
之前我们已经拿到了从流转化为字符串的文本,现在需要将这段文本转换为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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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();
}
}
如果在解析json数据中,存在一些问题,那么可能会导致json的解析出错,这时候我们就需要通过断点调试的方式来进行排错
具体排错过程可参考百度,这里不再详述
因为在比对版本号之后需要弹出“更新”的对话框,由于是UI操作,而Android规定不能在子线程更新UI,所以这里我们需要使用消息机制来发送消息,通知主线程进行UI操作
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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();
}
}
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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(); // 开启新界面后,将导航界面销毁掉
}
}
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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(); // 开启新界面后,将导航界面销毁掉
}
}
上面我们增加了相应的消息机制,这一节来继续完善机制,并添加相应的UI操作
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();
}
}
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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(); // 开启新界面后,将导航界面销毁掉
}
}
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 下载apk,需要apk的链接地址,即downloadUrl
}
});// 积极按钮,“是”
builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 取消对话框,进入主界面
enterHome();
}
});// 消极按钮,“否”
builder.show();
}
}
完善了消息机制后,接下来我们就需要处理“下载”逻辑的相关代码,这里的下载逻辑使用“多线程下载”,即将某个文件划分成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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 下载apk,需要apk的链接地址,即downloadUrl
downloadApk();
}
});// 积极按钮,“是”
builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
@Override
public 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>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
// 下载成功(下载过后的放置在sd卡中apk)
Log.i(tag,"下载成功!");
File file = responseInfo.result;
}
@Override
public void onFailure(HttpException e, String s) {
// 下载失败
Log.i(tag,"下载失败!");
}
@Override
public void onStart() {
// 刚刚开始下载
Log.i(tag,"刚刚开始下载!");
super.onStart();
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
// 下载过程(下载文件大小,当前的下载位置,是否正在下载)
Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);
super.onLoading(total, current, isUploading);
}
});
}
}
}
下载逻辑完善的同时,我们需要将该(高版本)的应用打包成apk,并且维护到服务器上
打包的过程可以参考百度,这里仅作简单陈述:
打包成功后,只需要放置到跟之前的json文件同一个tomcat中的目录,即可将apk文件维护到服务器上
随后,修改json文件,将路径改成如图所示(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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 下载apk,需要apk的链接地址,即downloadUrl
downloadApk();
}
});// 积极按钮,“是”
builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
@Override
public 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>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
// 下载成功(下载过后的放置在sd卡中apk)
Log.i(tag,"下载成功!");
File file = responseInfo.result;
installApk(file);
}
@Override
public void onFailure(HttpException e, String s) {
// 下载失败
Log.i(tag,"下载失败!");
e.printStackTrace();
}
@Override
public void onStart() {
// 刚刚开始下载
Log.i(tag,"刚刚开始下载!");
super.onStart();
}
@Override
public 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);
}
}
运行后,安装时会报错,原因如下:
所以,为了保持签名一致,我们需要让旧版本的签名文件也跟新版本的签名文件一致,所以对旧版本专门进行一次打包,然后再安装到模拟器上,即可完成正常的安装
为了防止用户点击回退后出现bug,所以需要进一步完善相关逻辑
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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 下载apk,需要apk的链接地址,即downloadUrl
downloadApk();
}
});// 积极按钮,“是”
builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 取消对话框,进入主界面
enterHome();
}
});// 消极按钮,“否”
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public 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>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
// 下载成功(下载过后的放置在sd卡中apk)
Log.i(tag,"下载成功!");
File file = responseInfo.result;
installApk(file);
}
@Override
public void onFailure(HttpException e, String s) {
// 下载失败
Log.i(tag,"下载失败!");
e.printStackTrace();
}
@Override
public void onStart() {
// 刚刚开始下载
Log.i(tag,"刚刚开始下载!");
super.onStart();
}
@Override
public 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(){
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// 初始化UI
initUI();
// 初始化数据
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.更新版本的名称 versionName
2.新版本的描述信息 versionDes
3.服务器的版本号 versionCode
4.新版本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.获取包管理对象packageManager
PackageManager 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(){
@Override
public 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() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 下载apk,需要apk的链接地址,即downloadUrl
downloadApk();
}
});// 积极按钮,“是”
builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 取消对话框,进入主界面
enterHome();
}
});// 消极按钮,“否”
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public 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>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
// 下载成功(下载过后的放置在sd卡中apk)
Log.i(tag,"下载成功!");
File file = responseInfo.result;
installApk(file);
}
@Override
public void onFailure(HttpException e, String s) {
// 下载失败
Log.i(tag,"下载失败!");
e.printStackTrace();
}
@Override
public void onStart() {
// 刚刚开始下载
Log.i(tag,"刚刚开始下载!");
super.onStart();
}
@Override
public 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
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
enterHome();
super.onActivityResult(requestCode, resultCode, data);
}
}
整个更新的流程如图所示: