Android开发 应用软件更新通用方式--强制/非强制/远程控制/浏览器 更新

一、通知栏自动下载更新

XVersionUpdate是VersionUpdate的升级版,全面优化代码,提高兼容性和稳定性,增强用户体验,帮助我们快速实现版本更新功能。

(1).效果图:

在这里插入图片描述
更新内容:

最近更新内容:
1.修复重复下载和进度条显示异常的bug
2.新增取消下载功能
3.兼容Android8.0
4.修复优化评论中的其他问题。

(2).实现过程:

build. gradle (app)文件中注入以下依赖

dependencies {
    ......
    implementation 'com.androidkun:xversionupdate:1.0.6'
}

可以在首次启动界面的Oncreate 方法下调用API检测版本更新,让其有新版本,在启动APP后自动下载更新。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        VersionUpdateConfig.getInstance()//获取配置实例
                .setContext(LoginActivit这里写界面.this)//设置上下文
                .setDownLoadURL("https://www.armpro.cn/assets/这里是下载地址.apk")//设置文件下载链接
                .setNewVersion("3")//设置即将下载的APK的版本号,避免重复下载
                .setFileSavePath("/storage/emulated/0/")//设置文件保存路径(可不设置)
                .setNotificationIconRes(R.drawable.ic_launcher)//设置通知图标
                .setNotificationSmallIconRes(R.drawable.ic_launcher)//设置通知小图标
                .setNotificationTitle("软件有新版本,正在下载中...")//设置通知标题
                .startDownLoad();//开始下载
    }

接下来就不用再做任何处理了,有新版本时启动APP,就会自动在通知栏显示下载进度,在下载完成后会自动跳到安装页面。

项目源码地址:https://github.com/AndroidKun/XVersionUpdate


二、主界面弹窗选择 更新/强制更新/浏览器更新(通用更新)

这个版本的代码通用性很高,代码也很少,理论适用与任意的安卓系统(Android 10版本已测试可用),最简单的源代码实现的更新服务,检测远程更新代码多种更新方式的选择,代码可修改定制性很强,支持同时多个更新链接配置进行容错处理,应用内直链直接下载时失败时会弹出提示错误弹窗,并可选择浏览器下载。可以简单使用gitee搭建静态网页当后台更新服务(源码见下方↓)。

(1).弹窗效果图:

强制与非强制更新效果
应用内更新图
应用内直链直接下载失败时

(2).实现过程:

1. 搭建json格式网页

这里提供源码和搭建后数据展示情况,具体详细的搭建教程很简单,随便百度就可以找到,这里不再重复搭建过程。

搭建源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>https://yirj.gitee.io/项目名称</title>
</head>
<body>
<pre><code id="json"></code></pre>
</body>
<script type="text/javascript">
let btn = document.querySelector('#json');
let data = {"hasUpdate": true,"NoIgnorable": true,"versionCode": 51,"versionName": "2.4.1","updateLog": "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。","apkUrl": "https://www.yuming.com/assets/a某o130.apk","webUrl": "https://yirj.gitee.io/update111","apkSize": "29.5MB"};
btn.textContent = JSON.stringify(data, null, 2);
</script>
</html>
搭建成的链接
https://yirj.gitee.io/项目名称
访问相关展示
{
    hasUpdate: true,  //是否有更新 默认true
    NoIgnorable: true, //不 可忽略更新  强制:true 非强制:false
    versionCode: 51,   //服务端的版本号  
    versionName: "2.4.1", //服务端的版本名
    updateLog: "\n1、更改保存图片的存储路径。\n2、更改软件更新的提示模式。\n3、调整非强制更新控制方式。\n4、新增存储权限的申请授权位置。", //更新提示内容
    apkUrl: "https://www.yuming.com/assets/a某o130.apk",//新版本APK直链下载地址
    webUrl: "https://yirj.gitee.io/update111",//浏览器更新链接,随意放(直链、蓝奏、官网均可)
    apkSize: "29.5MB" //新版本的大小 随意写就好
}

2. 创建file_paths.xml及修改AndroidManifest.xml

创建file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
修改AndroidManifest.xml
    <!-- 拥有完全的网络访问权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 修改或删除您的USB存储设备中的内容 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- 查看网络连接 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
        .....
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true">

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        .........
</application>

3. 启动主界面加入检测更新代码

在软件的检测更新界面的Oncreate方法下,加入检测更新的代码。
更新弹窗出现条件hasUpdate为true且用户使用的APP的版本号<服务端的版本号,就会提示更新。
强制更新出现条件:有更新后跳转到showDialogUpdate方法,判断 NoIgnorable为true则就是强制更新。

import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
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.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class MainActivity extends Activity {
    private String TAG;
    public static JSONObject jSONObject = null;
    private static boolean hasUpdate, NoIgnorable = true; //是否有更新。 不可忽略的更新
    private static int versionCode = 0;
    private static String versionName, updateLog, apkSize, apkUrl, webUrl = "";
    private static String[] upl = new String[]{"https://yirj.gitee.io/更新链接1", "https://yirj.gitee.io/更新链接2"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //忽略证书校验的相关方法,http报证书异常或者不安全链接就启用它
        //handleSSLHandshake();
        //应用内直链更新,要先动态申请获取存储权限
        getReadPermissions();

        //启动子线程--更新功能实现调用
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                try {
                    GetServerJson();
                    //hasUpdate为true 且 本程序的版本号小于服务器的版本号,那么提示用户更新
                    if (hasUpdate & getVersionCode() < versionCode) {
                        showDialogUpdate();//弹出提示版本更新的对话框
                    } else {
                        //Toast.makeText(MainActivity.this, "当前已是最新版本", Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Looper.loop();
            }
        }.start();
    }


    /**
     * 获取当前使用的软件包的版本号
     */
    public int getVersionCode() {
        try {
            //获取packagemanager的实例
            PackageManager packageManager = getPackageManager();
            //getPackageName()是你当前类的包名,0代表是获取版本信息
            PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
            Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号
            return packInfo.versionCode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 1;
    }

    /**
     * 提示版本更新的对话框
     */
    public void showDialogUpdate() {
        //hasUpdate为true且程序版本号<服务端版本号,提示用户更新
        if (NoIgnorable) { //NoIgnorable为true 就是强制更新,无“取消”按钮
            // 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setCancelable(false); //开启强制更新,无法触摸外部关闭
            builder.setTitle("是否升级到" + versionName + "版本?").
                    // 设置提示框的图标
                    setIcon(R.drawable.ic_launcher).
                    // 设置要显示的信息
                    setMessage("新版本大小:" + apkSize + "\n" + updateLog).
                    // 设置确定按钮
                    setPositiveButton("更新", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            loadNewVersionProgress();//程序内直接下载最新的版本程序
                        }
                    }).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新 
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Uri uri = Uri.parse(webUrl);
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    startActivity(intent);
                    finish(); //强制更新,点击后销毁应用
                }
            });
            // 显示对话框
            builder.create().show();
        } else {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
//            builder.setCancelable(false); //非强制更新,屏蔽此行,触摸外部或退出键可关闭
            builder.setTitle("是否升级到" + versionName + "版本?").
                    setMessage("新版本大小:" + apkSize + "\n" + updateLog).
                    setPositiveButton("更新", new DialogInterface.OnClickListener() {//正按钮
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            loadNewVersionProgress();//下载最新的版本程序
                        }
                    }).setNegativeButton("取消", new DialogInterface.OnClickListener() {//负按钮
                @Override
                public void onClick(DialogInterface dialog, int which) {
//                        finish(); //屏蔽销毁,不做任何处理
                }
            }).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Uri uri = Uri.parse(webUrl);
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    startActivity(intent);
                    //finish(); //屏蔽销毁,访问浏览器,程序不会退出
                }
            });
            // 显示对话框
            builder.create().show();
        }
    }

   //轮询验证两个更新链接,返回有效链接
    public static String checkUrl(String[] ltl) {
        String resultUrl = null;
        for (String url : ltl) {
            resultUrl = url;
            try {
               //调用检查链接是否有效的方法
                String result = get(url);
                if (result != null && result.length() != 0) {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resultUrl;
    }

    //检查更新链接是否有效的方法
    public static String get(String url) {
        URL infoUrl = null;
        InputStream inStream = null;
        String line = "";
        try {
            infoUrl = new URL(url);
            URLConnection connection = infoUrl.openConnection();
            HttpURLConnection httpConnection = (HttpURLConnection) connection;
            int responseCode = httpConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inStream = httpConnection.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
                StringBuilder strber = new StringBuilder();
                while ((line = reader.readLine()) != null)
                    strber.append(line + "\n");
                inStream.close();
                int start = strber.indexOf("{");
                int end = strber.indexOf("}");
                String json = strber.substring(start, end + 1);
                return json;
            }
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        }
        return "";
    }

    /**
     * 使用检查过的有效链接,获取服务端json数据
     */
    public static JSONObject GetServerJson() {
        URL infoUrl = null;
        InputStream inStream = null;
        String line = "";
        try {
            String uurl = checkUrl(upl);
            infoUrl = new URL(uurl); //json格式信息的API,使用案例。
            URLConnection connection = infoUrl.openConnection();
            HttpURLConnection httpConnection = (HttpURLConnection) connection;
            int responseCode = httpConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inStream = httpConnection.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
                StringBuilder strber = new StringBuilder();
                while ((line = reader.readLine()) != null)
                    strber.append(line + "\n");
                inStream.close();
                int start = strber.indexOf("{");
                int end = strber.indexOf("}");
                String json = strber.substring(start, end + 1);
                if (json != null) {
                    try {
                        jSONObject = new JSONObject(json);
                        hasUpdate = jSONObject.getBoolean("hasUpdate");
                        NoIgnorable = jSONObject.getBoolean("NoIgnorable");
                        versionCode = jSONObject.getInt("versionCode");
                        versionName = jSONObject.getString("versionName");
                        updateLog = jSONObject.getString("updateLog");
                        apkSize = jSONObject.getString("apkSize");
                        apkUrl = jSONObject.getString("apkUrl");
                        webUrl = jSONObject.getString("webUrl");
                        return jSONObject;
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 应用内直链升级方法,下载新版本程序
     */
    private void loadNewVersionProgress() {
        ProgressDialog pd = new ProgressDialog(this);
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        //获取手机的根目录存储位置,以及直链链接最后一个“/”后文字作为文件名展示在界面
        pd.setMessage("下载最新版本安装包到:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + apkUrl.substring(apkUrl.lastIndexOf("/") + 1));
        pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭
        pd.show();

        //启动子线程下载任务
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                try {
                    File file = getFileFromServer(apkUrl, pd);  //调用下载服务方法动态显示进度
                    //sleep(2000);//设置休眠两秒之后再启动安装
                    installApk(file);  //下载完成直接安装
//                    pd.dismiss(); //屏蔽,结束掉进度条对话框,防止强制更新出Bug
                } catch (Exception e) {
                    //直链下载apk异常失败提示容错。
                    AlertDialog.Builder builder = new AlertDialog.Builder(LoginActivity.this);
                    builder.setCancelable(false);//开启强制更新,触摸屏幕其他位置无法关闭
                    builder.setTitle("下载失败:").setMessage("1.请检查存储权限是否开启。\n2.请检查网络连接是否正常。\n3.使用浏览器下载新版本。");
                    builder.setPositiveButton("退出", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface arg0, int arg1) {
                           finish();
                        }
                    }).setNeutralButton("浏览器下载", new DialogInterface.OnClickListener() {//中性按钮 应用内下载失败可用它更新
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Uri uri = Uri.parse(webUrl);
                            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                            startActivity(intent);
                            if (NoIgnorable) {
                                finish(); //强制更新,点击后销毁应用
                            }else{
                                pd.dismiss(); //不强制更新,跳转到浏览器并销毁应用内下载失败的进度条
                            }
                        }
                    });
                    builder.create().show();
                }
                Looper.loop();
            }
        }.start();
    }

    /**
     * 安装apk
     */
    protected void installApk(File file) {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);

        if (Build.VERSION.SDK_INT >= 24) {
            Uri apkUri = FileProvider.getUriForFile(this, "com.bysj.yrj(你的包名).fileprovider", file); //这里要写你程序的包名,已实验不可使用${applicationId}
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        this.startActivity(intent);
    }


    /**
     * 从服务器获取apk文件的代码
     * 传入网址uri,进度条对象即可获得一个File文件
     * (要在子线程中执行哦)
     */
    public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            URL url = new URL(uri);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            //获取到文件的大小
            pd.setMax(conn.getContentLength());  //字节的方式显示下载进度
            InputStream is = conn.getInputStream();
            //获取直链链接最后一个“/”后文字作为文件名,下载存储到手机
            File file = new File(Environment.getExternalStorageDirectory(), apkUrl.substring(apkUrl.lastIndexOf("/", apkUrl.lastIndexOf("")) + 1));
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            int len;
            int total = 0;
            while ((len = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
                total += len;
                //获取当前下载量
                pd.setProgress(total);//字节方式显示下载量
            }
            fos.close();
            bis.close();
            is.close();
            return file;
        } else {
            return null;
        }
    }

    /**
     * 权限的验证及处理,相关方法
     */
    private void getReadPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                    Manifest.permission.READ_EXTERNAL_STORAGE}, 10001);
                } else {//没有则请求获取权限,示例权限是:存储权限,需要其他权限请更改或者替换
                    ActivityCompat.requestPermissions(this,
                            new String[]{
                                    Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10001);
                }
            } else {//如果已经获取到了权限则直接进行下一步操作
                Log.e(TAG, "全部权限已经授权成功");
            }
        }

    }

    /**
     * 一个或多个权限请求结果回调
     * 循环回调获取权限,除非勾选禁止后不再询问,之后提示用户引导用户去设置
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case 10001:
                for (int i = 0; i < grantResults.length; i++) {
//                   如果拒绝获取权限
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        //判断是否勾选禁止后不再询问
                        boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
                        if (flag) {
                            getReadPermissions();
                            return;//用户权限是一个一个的请求的,只要有拒绝,剩下的请求就可以停止,再次请求打开权限了
                        } else { // 勾选不再询问,并拒绝
                            Toast.makeText(this, "请到设置中打开权限", Toast.LENGTH_LONG).show();
                            return;
                        }
                    }
                }
                //Toast.makeText(this, "权限开启完成", Toast.LENGTH_LONG).show();
                break;
            default:
                break;
        }

    }

    /**
     * 忽略https的证书校验 的相关方法
     */
    public static void handleSSLHandshake() {
        try {
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }};

            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
        } catch (Exception ignored) {
        }
    }

}

三、进入界面弹窗进度条选择/强制更新

这个版本的代码好像是只适用于Android 8.0以下的手机APP,检测远程更新代码。

(1).效果图:

在这里插入图片描述

(2).实现过程:

build.gradle (:app)构建文件中添加相关的依赖

dependencies {
    ...
    implementation 'com.android.support:support-compat:28.0.0'
}

在项目的AndroidManifest.xml文件中添加相关的提供者等等信息。

    <application
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            <!--android:name="androidx.core.content.FileProvider"-->
            android:authorities="com.bysj.yrj自己的包名.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        ...
    </application>

在项目的xml文件夹中创建file_paths.xml文件,并在文件中加入以下代码:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

在要检测更新界面的Activity界面的Oncreate方法下写检测更新的代码,如下所示:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //检测本程序的版本,这里假设从服务器中获取到最新的版本号为3
        //如果检测本程序的版本号小于服务器的版本号,那么提示用户更新
        if (getVersionCode() < 3) {
            showDialogUpdate();//弹出提示版本更新的对话框
        } else {
            //否则弹窗,说现在是最新的版本
            Toast.makeText(this, "当前已是最新版本", Toast.LENGTH_SHORT).show();
        }
    }

之后在要检测更新界面的Activity界面下写 相关代码上面的Oncreate下的代码直接调用了这块,如下所示:

 /*
    //检测本程序的版本,这里假设从服务器中获取到最新的版本号为3
    public void checkVersion() {
        //如果检测本程序的版本号小于服务器的版本号,那么提示用户更新
        if (getVersionCode() < 3) {
            showDialogUpdate();//弹出提示版本更新的对话框
        } else {
            //否则弹窗,说现在是最新的版本
            Toast.makeText(this, "当前已经是最新的版本", Toast.LENGTH_LONG).show();
        }
    }*/

    /**
     * 提示版本更新的对话框
     */
    private void showDialogUpdate() {
        // 这里的属性可以一直设置,因为每次设置后返回的是一个builder对象
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(false); //开启强制更新,无法关闭
        // 设置提示框的标题
        builder.setTitle("版本升级").
                // 设置提示框的图标
//                        setIcon(R.mipmap.ic_launcher).
                // 设置要显示的信息
                        setMessage("发现新版本!更新新版本?").
                // 设置确定按钮
                        setPositiveButton("更新", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Toast.makeText(MainActivity.this, "选择确定哦", 0).show();
                        loadNewVersionProgress();//下载最新的版本程序
                    }
                }).                .
                // 设置取消按钮,null是什么都不做,并关闭对话框
                // setNegativeButton("取消", null);
        setNegativeButton("取消", new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        finish();
    }
});
        // 生产对话框
        AlertDialog alertDialog = builder.create();
        // 显示对话框
        alertDialog.show();
    }

    /**
     * 下载新版本程序
     */
    private void loadNewVersionProgress() {
        final String uri = "https://www.armpro.cn/assets/armpro130.apk";  //测试使用的下载APP直链
        final ProgressDialog pd;    //进度条对话框
        pd = new ProgressDialog(this);
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        pd.setMessage("正在下载安装包,请稍后");
        pd.setCancelable(false); //开启强制更新,触摸屏幕其他位置无法关闭
        pd.show();

        //启动子线程下载任务
        new Thread() {
            @Override
            public void run() {
                try {
                    Looper.prepare();
                    File file = getFileFromServer(uri, pd);  //从服务器下载相关的APP流动态显示进度
                    sleep(3000);
                    installApk(file);  //下载完成之后,直接休眠3秒后进行安装
//                    pd.dismiss(); //结束掉进度条对话框
                    Looper.loop();
                } catch (Exception e) {
                    //下载apk失败
                    Toast.makeText(getApplicationContext(), "下载新版本失败", Toast.LENGTH_LONG).show();
                    e.printStackTrace();
                }
            }
        }.start();

    }
    /**
     * 安装apk
     */
    protected void installApk(File file) {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);

        if (Build.VERSION.SDK_INT >= 24) {
            Uri apkUri = FileProvider.getUriForFile(this, "com.bysj.yrj这里是自己的包名.fileprovider", file);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        this.startActivity(intent);
    }


    /**
     * 从服务器获取apk文件的代码
     * 传入网址uri,进度条对象即可获得一个File文件
     * (要在子线程中执行哦)
     */
    public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {
        //如果相等的话表示当前的sdcard挂载在手机上并且是可用的
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            URL url = new URL(uri);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            //获取到文件的大小
            pd.setMax(conn.getContentLength());
            InputStream is = conn.getInputStream();

//            long time = System.currentTimeMillis();//当前时间的毫秒数
            Date date = new Date();// 创建一个时间对象,获取到当前的时间
            SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss");// 设置时间显示格式
            String time = sdf.format(date);

            File file = new File(Environment.getExternalStorageDirectory(), time + "信息管理系统_update.apk");
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            int len;
            int total = 0;
            while ((len = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
                total += len;
                //获取当前下载量
                pd.setProgress(total);
            }
            fos.close();
            bis.close();
            is.close();
            return file;
        } else {
            return null;
        }
    }

    /*
     * 获取当前程序的版本号
     */
    private int getVersionCode() {
        try {
            //获取packagemanager的实例
            PackageManager packageManager = getPackageManager();
            //getPackageName()是你当前类的包名,0代表是获取版本信息
            PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
            Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号
            Log.e("TAG", "版本名" + packInfo.versionName);  //给用户看得叫做版本名
            return packInfo.versionCode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 1;
    }


    /*
     * 获取当前程序的版本名
     */
    private String getVersionName() throws Exception {
        //获取packagemanager的实例
        PackageManager packageManager = getPackageManager();
        //getPackageName()是你当前类的包名,0代表是获取版本信息
        PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
        Log.e("TAG", "版本号" + packInfo.versionCode);  //更新软件用的是版本号
        Log.e("TAG", "版本名" + packInfo.versionName);  //给用户看得叫做版本名
        return packInfo.versionName;
    }

四、使用更新框架进入检测更新/强制/远程

这个版本的代码已测试适用于Android系统 6、7、8、9、10、11手机APP,检测远程更新代码,之前出现过项目中运行代码在模拟器更新提示没有相关问题,但是打包安装在真机上检测更新就有问题,经过排查后原因发现是因为我在之前学习混淆时项目中配置使用了ProGuard,打包时自动优化掉了一些代码导致的。

(1).基础使用篇

1.效果图:

可根据Api形式自定义的选择是否更新、是否强制更新、也可配置断点续传下载,
更新后的APP安装包会放在/storage/emulated/0/Android/data/你的应用包名/cache/xupdate/版本号/

    public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
    public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
    public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";

在这里插入图片描述

2.实现过程:

添加Gradle依赖
1.先在项目根目录的 build.gradle 的 repositories 添加:

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2.然后在build.gradle(app)的dependencies中添加:

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
   ...
   // androidx project
   implementation 'com.github.xuexiangjys:XUpdate:2.0.6'
   // support project
   implementation 'com.github.xuexiangjys:XUpdate:1.1.6'
   implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.0'
   // 如果需要使用断点续传下载功能的话添加该依赖(可选)
   implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.0'
   }

在项目的gradle.properties文件下开启Android X的构建支持,如果没有的话,就在Project的目录下创建一个,并在其中加入以下代码即可。

org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true

3.在要检测更新界面的Activity下,加入以下成员变量信息,用来控制以后的远程更新、版本检测、版本强制更新等等信息。

public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";

上面的Api访问的后的内容以及相关解释如下:

{
  "Code": 0, //0代表请求成功,非0代表失败
  "Msg": "", //请求出错的信息
  "UpdateStatus": 1, //0代表不更新,1代表有版本更新,不需要强制升级,2代表有版本更新,需要强制升级
  "VersionCode": 3,
  "VersionName": "1.0.2",
  "ModifyContent": "1、优化api接口。\r\n2、添加使用demo演示。\r\n3、新增自定义更新服务API接口。\r\n4、优化更新提示界面。",
  "DownloadUrl": "https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk",
  "ApkSize": 2048
  "ApkMd5": "..."  //md5值没有的话,就无法保证apk是否完整,每次都会重新下载。
}

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {
    public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
    public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
    public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //使用检测更新的框架,检测APP是否有新版本
        EasyUpdate.checkUpdate(this, UPDATE_TEST_URL);
        
        //断点续传更新相关功能的使用
        EasyUpdate.create(this, UPDATE_TEST_URL).
                updateHttpService(AriaDownloader.getUpdateHttpService(this))
                .update();
        
        //让其可在后台更新--是否强制更新由Url控制
        XUpdate.newBuild(this)
                .updateUrl(UPDATE_TEST_URL)
                .supportBackgroundUpdate(true)
                .update();
        }
    }
}

使用XUpdate 的日志功能,更加详细的查看运行情况,如果出现任何问题,可开启debug模式来追踪问题,需要新创建一个类MyApp来初始化Application,代码可与上面的代码配合使用,例如:使用检测更新的框架,检测APP是否有新版本的代码,使用并配置下方代码,可以让其,开启Debug 和 断点续传的功能,代码如下所示:

public class MyApp extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        EasyUpdate.setUpdateConfigProvider(new IUpdateConfigProvider() {
            @Override
            public UpdateConfig getUpdateConfig(Context context) {
                return UpdateConfig.create().setIsDebug(true)  //开启Debug  日志功能,查看详细的日志信息
                        .setDownloadServiceProxy(new AriaDownloadServiceProxyImpl(context)); //设置断点续传功能
            }
        });
        super.attachBaseContext(base);
    }
}

在安卓的AndroidManifest.xmlapplication 标签下写入 android:name=".你的类名",我这里根据我创建的位置为:android:name=".dao.MyApp" ,具体代码如下所示:

    <application
        android:name=".dao.MyApp" 
        
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
        .....
        </activity>
   </application>

(2).高级使用篇

在基础使用篇的基础上,开始更加深入的研究高级使用篇,高级使用篇的使用包括但不限于:自定义主题自定义弹窗风格智能更新只使用下载功能静默安装(Root)等等。这里根据作者的教程,尝试在自己的小Demo中实现,边学边写文章,并且总结一些我使用过程中遇到的一些问题,以及相关解决办法。

1.自定义图片和主题弹窗

效果图

在这里插入图片描述

实现过程:

使用XUpdate 的高级功能,需要新创建一个类MyApp来初始化Application,使用并配置下方代码,代码如下所示:

/**
 * @Author: yirj
 * @Date: 2021/2/25 9:28
 * @Remark: 说明
 */
public class MyApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        XUpdate.get()
                .debug(true)
                .isWifiOnly(true)                                               //默认设置只在wifi下检查版本更新
                .isGet(true)                                                    //默认设置使用get请求检查版本
                .isAutoMode(false)                                              //默认设置非自动模式,可根据具体使用配置
                .param("versionCode", UpdateUtils.getVersionCode(this))         //设置默认公共请求参数
                .param("appKey", getPackageName())
                .setOnUpdateFailureListener(new OnUpdateFailureListener() {     //设置版本更新出错的监听
                    @Override
                    public void onFailure(UpdateError error) {
                        if (error.getCode() != CHECK_NO_NEW_VERSION) {          //对不同错误进行处理
//                            ToastUtils.toast(error.toString());
                        }
                    }
                })
                .supportSilentInstall(true)                                     //设置是否支持静默安装,默认是true
                .setIUpdateHttpService(new OkHttpUpdateHttpServiceImpl()) //这个必须设置!实现网络请求功能。  OKHttpUpdateHttpService()
                .init(this);                                                    //这个必须初始化
    }
}

出现的问题:
两处报红的情况,ToastUtils.toast(error.toString());OkHttpUpdateHttpService,第一处ToastUtils.直接的注释掉即可,不影响使用。第二处是关于网络请求库的OkHttpUpdateHttpService将其改为OkHttpUpdateHttpServiceImpl并找到 build.gradle (app)dependencies添加以下依赖,就可以解决这些报红问题了。

dependencies {
    .....
    //  MyApp中OKHttpUpdateHttpService 报红解决  添加网络请求库
    implementation 'com.zhy:okhttputils:2.6.2'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.squareup.okhttp3:okhttp:3.14.9'
}

在安卓的AndroidManifest.xmlapplication 标签下写入 android:name=".你的类名",我这里根据我创建的位置为:android:name=".dao.MyApp" ,具体代码如下所示:

    <application
        android:name=".dao.MyApp" 
        
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
        .....
        </activity>
   </application>

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

@Override
public class LoginActivity extends Activity {
    public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
    public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
    public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
                XUpdate.newBuild(this)
                .updateUrl(UPDATE_TEST_URL)
                //这里的ResUtils工具类报红的话,直接使用getResources()替代
                //R.color.update_theme_color 需要创建颜色文件,继续往下看...
                .promptThemeColor(getResources().getColor(R.color.update_theme_color))
                .promptButtonTextColor(Color.WHITE)
                .promptTopResId(R.drawable.bg_update_top) //这里和你项目的图片存储一致
                .promptWidthRatio(0.7F)
                .update();
    }
}

接下来是找到弹窗背景的主题图片 bg_update_top.png(原图可以到作者的项目中找),注意为了图片的比例问题,请务必将其添加到你项目的drawable-xxhdpi或者mipmap-xxhdpi文件夹下,而不是其他的drawable或者mipmap文件夹,容易造成图片主题大小的一些兼容问题。然后就在 res/values/ 下创建colors.xml文件,并写入相关颜色的配置,这样上面的代码报红就完全解决了。这样高级使用篇的自定义主题功能就搞定了。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="update_theme_color">#FFFFAC5D</color>
</resources>

2.使用原生系统的更新弹窗

自定义图片和主题弹窗的基础上,只需要配置两个类HProgressDialogUtilsCustomUpdatePrompter,以及稍微Oncreate修改一些调用就可以实现了。

效果图:

在这里插入图片描述

实现过程:

HProgressDialogUtils.java 代码如下所示:

/**
 * Created by Vector on 2016/8/12 0012.
 */
public class HProgressDialogUtils {
    private static ProgressDialog sHorizontalProgressDialog;

    private HProgressDialogUtils() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    @SuppressLint("NewApi")
    public static void showHorizontalProgressDialog(Context context, String msg, boolean isShowSize) {
        cancel();

        if (sHorizontalProgressDialog == null) {
            sHorizontalProgressDialog = new ProgressDialog(context);
            sHorizontalProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            sHorizontalProgressDialog.setCancelable(false);
            if (isShowSize) {
                sHorizontalProgressDialog.setProgressNumberFormat("%2dMB/%1dMB");
            }

        }
        if (!TextUtils.isEmpty(msg)) {
            sHorizontalProgressDialog.setMessage(msg);
        }
        sHorizontalProgressDialog.show();

    }

    public static void setMax(long total) {
        if (sHorizontalProgressDialog != null) {
            sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
        }
    }

    public static void cancel() {
        if (sHorizontalProgressDialog != null) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void setProgress(int current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        sHorizontalProgressDialog.setProgress(current);
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void setProgress(long current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void onLoading(long total, long current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        if (current == 0) {
            sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
        }
        sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }
}

CustomUpdatePrompter.java 代码如下所示:

/**
 * 自定义版本更新提示器
 *
 * @author xuexiang
 * @since 2018/7/12 下午3:48
 */
public class CustomUpdatePrompter implements IUpdatePrompter {

    /**
     * 显示自定义提示
     *
     * @param updateEntity
     * @param updateProxy
     */
    private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {
        String updateInfo = UpdateUtils.getDisplayUpdateInfo(updateProxy.getContext(), updateEntity);

        AlertDialog.Builder builder = new AlertDialog.Builder(updateProxy.getContext())
                .setTitle(String.format("是否升级到%s版本?", updateEntity.getVersionName()))
                .setMessage(updateInfo)
                .setPositiveButton("升级", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
                            @Override
                            public void onStart() {
                                HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);
                            }

                            @Override
                            public void onProgress(float progress, long total) {
                                HProgressDialogUtils.setProgress(Math.round(progress * 100));
                            }

                            @Override
                            public boolean onCompleted(File file) {
                                HProgressDialogUtils.cancel();
                                return true; //这个true表示是直接安装,需要Root权限,否则就是只下载不提示安装
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                HProgressDialogUtils.cancel();
                            }
                        });
                    }
                });
        if (updateEntity.isIgnorable()) {
            builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    UpdateUtils.saveIgnoreVersion(updateProxy.getContext(), updateEntity.getVersionName());
                }
            }).setCancelable(true);
        } else  {
            builder.setCancelable(false);
        }
        builder.create().show();
    }

    /**
     * 显示版本更新提示
     *
     * @param updateEntity 更新信息
     * @param updateProxy  更新代理
     * @param promptEntity 提示界面参数
     */
    @Override
    public void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {
        showUpdatePrompt(updateEntity, updateProxy);
    }
}

在当前界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {
    public static final String UPDATE_TEST_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_test.json";
    public static final String UPDATE_FORCE_URL = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_forced.json";
    public static final String DOWNLOAD_TEST_URL = "https://gitee.com/xuexiangjys/Resource/raw/master/jsonapi/download_test.json";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //原生系统弹窗的使用,好像默认是强制更新
        XUpdate.newBuild(this)
                .updateUrl(UPDATE_TEST_URL)
                .updatePrompter(new CustomUpdatePrompter())
                .update();
    }
}

3.默认App更新 + 自定义Api + 自定义提示弹窗(系统)

效果图:

在这里插入图片描述

实现过程:

添加Gradle依赖
1.先在项目根目录的 build.gradle 的 repositories 添加:

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2.然后在build.gradle(app)的dependencies中添加:

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
   ...
   //JsonUtil 报红的话 添加这个
   implementation 'com.github.xuexiangjys.XUtil:xutil-core:2.0.0'
   // androidx project
   implementation 'com.github.xuexiangjys:XUpdate:2.0.6'
   // support project
   //implementation 'com.github.xuexiangjys:XUpdate:1.1.6'
   implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.0'
   // 如果需要使用断点续传下载功能的话添加该依赖(可选)
   implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.0'
   }

在项目的gradle.properties文件下开启Android X的构建支持,如果没有的话,就在Project的目录下创建一个,并在其中加入以下代码即可。

org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true

3.在要检测更新界面的Activity下,加入以下成员变量信息,用来控制以后的远程更新、版本检测、版本强制更新等等信息。

//默认App更新 + 自定义Api + 自定义提示弹窗(系统)
private String mUpdateUrl3 = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_custom.json";

上面的Api访问的后的内容以及相关解释如下:

{
	hasUpdate: true, //控制是否有更新 true:开启  false: 关闭
	isIgnorable: true, //控制是否强制升级 true:不强制升级 false: 强制升级
	versionCode: 3, //这个服务版本号大于安装的版本号 会提示更新
	versionName: "1.0.2", //这个一般是给用户看的
	updateLog: " 1、优化api接口。 2、添加使用demo演示。 3、新增自定义更新服务API接口。 4、优化更新提示界面。", 
	apkUrl: "https://xuexiangjys.oss-cn-shanghai.aliyuncs.com/apk/xupdate_demo_1.0.2.apk",
	apkSize: 4096    //KB为单位的更新包大小
}

使用XUpdate 的高级功能,需要在你的程序中初始化Application,使用并配置下方代码,代码如下所示:

/**
 * @Author: yirj
 * @Date: 2021/2/25 9:28
 * @Remark: 说明
 */
public class 你的启动初始化类名 extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        
        //从这里开始的
        XUpdate.get()
                .debug(true)
                .isWifiOnly(true)                                               //默认设置只在wifi下检查版本更新
                .isGet(true)                                                    //默认设置使用get请求检查版本
                .isAutoMode(false)                                              //默认设置非自动模式,可根据具体使用配置
                .param("versionCode", UpdateUtils.getVersionCode(this))         //设置默认公共请求参数
                .param("appKey", getPackageName())
                .setOnUpdateFailureListener(new OnUpdateFailureListener() {     //设置版本更新出错的监听
                    @Override
                    public void onFailure(UpdateError error) {
                        if (error.getCode() != CHECK_NO_NEW_VERSION) {          //对不同错误进行处理
//                            ToastUtils.toast(error.toString());
                        }
                    }
                })
                .supportSilentInstall(true)                                     //设置是否支持静默安装,默认是true
                .setIUpdateHttpService(new OkHttpUpdateHttpServiceImpl()) //这个必须设置!实现网络请求功能。  OKHttpUpdateHttpService()
                .init(this);                                                    //这个必须初始化
    }
}

如果找不到你的启动初始化的类名,可以在项目的AndroidManifest.xmlapplication 标签找 android:name=".你的类名",找到后直接按住Ctrl+鼠标点击即可跳转,并将上面的代码加入Oncreate中;如果没有的话,就要自己创建类名并且按照下面代码的自己搞一个。

    <application
        android:name=".你创建的类所在的位置" 
        
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
        .....
        </activity>
   </application>

在要检测升级界面的Oncreate方法下合适的位置写检测更新的代码,让其进入这个Activity界面后,直接执行此代码检测版本是否更新。

public class LoginActivity extends Activity {
    //默认App更新 + 自定义Api + 自定义提示弹窗(系统)
    private String mUpdateUrl3 = "https://gitee.com/xuexiangjys/XUpdate/raw/master/jsonapi/update_custom.json";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //默认App更新 + 自定义Api + 自定义提示弹窗(系统)
        XUpdate.newBuild(this)
                .updateUrl(mUpdateUrl3)
                .updateChecker(new DefaultUpdateChecker() {
                    @Override
                    public void onBeforeCheck() {
                        super.onBeforeCheck();
                        CProgressDialogUtils.showProgressDialog(Login更新界面Activity.this, "查询中...");
                    }

                    @Override
                    public void onAfterCheck() {
                        super.onAfterCheck();
                        CProgressDialogUtils.cancelProgressDialog(Login更新界面Activity.this);
                    }

                    @Override
                    public void noNewVersion(Throwable throwable) {
                        super.noNewVersion(throwable);
                        // 没有最新版本的处理
                    }
                })
                .updateParser(new CustomUpdateParser())
                .updatePrompter(new CustomUpdatePrompter())
                .update();
        }
    }
}

使用默认App更新 + 自定义Api + 自定义提示弹窗(系统)需要自定义配置几个类CProgressDialogUtils.java CustomUpdateParser.java CustomUpdatePrompter.java HProgressDialogUtils.java CustomResult.java ,可以在源 Github项目寻找,只需要稍微简单改改即可!此时加入之后代码中一般只会有一个JsonUtil的工具类显示报红。使用这种方式实现更新弹窗,其文字并不仅限于国际化中的中英文了。
CProgressDialogUtils.java 代码如下所示:

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;

/**
 * Created by Vector on 2016/8/12 0012.
 */
public class CProgressDialogUtils {
    private static final String TAG = CProgressDialogUtils.class.getSimpleName();
    private static ProgressDialog sCircleProgressDialog;

    private CProgressDialogUtils() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    public static void showProgressDialog(Activity activity) {
        showProgressDialog(activity, "加载中", false, null);
    }

    public static void showProgressDialog(Activity activity, DialogInterface.OnCancelListener listener) {
        showProgressDialog(activity, "加载中", true, listener);
    }

    public static void showProgressDialog(Activity activity, String msg) {
        showProgressDialog(activity, msg, false, null);
    }

    public static void showProgressDialog(Activity activity, String msg, DialogInterface.OnCancelListener listener) {
        showProgressDialog(activity, msg, true, listener);
    }

    public static void showProgressDialog(final Activity activity, String msg, boolean cancelable, DialogInterface.OnCancelListener listener) {
        if (activity == null || activity.isFinishing()) {
            return;
        }


        if (sCircleProgressDialog == null) {
            sCircleProgressDialog = new ProgressDialog(activity);
            sCircleProgressDialog.setMessage(msg);
            sCircleProgressDialog.setOwnerActivity(activity);
            sCircleProgressDialog.setOnCancelListener(listener);
            sCircleProgressDialog.setCancelable(cancelable);
        } else {
            if (activity.equals(sCircleProgressDialog.getOwnerActivity())) {
                sCircleProgressDialog.setMessage(msg);
                sCircleProgressDialog.setCancelable(cancelable);
                sCircleProgressDialog.setOnCancelListener(listener);
            } else {
                //不相等,所以取消任何ProgressDialog
                cancelProgressDialog();
                sCircleProgressDialog = new ProgressDialog(activity);
                sCircleProgressDialog.setMessage(msg);
                sCircleProgressDialog.setCancelable(cancelable);
                sCircleProgressDialog.setOwnerActivity(activity);
                sCircleProgressDialog.setOnCancelListener(listener);
            }
        }

        if (!sCircleProgressDialog.isShowing()) {
            sCircleProgressDialog.show();
        }

    }


    public static void cancelProgressDialog(Activity activity) {
        if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {
            if (sCircleProgressDialog.getOwnerActivity() == activity) {
                sCircleProgressDialog.cancel();
                sCircleProgressDialog = null;
            }
        }
    }

    public static void cancelProgressDialog() {
        if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {
            sCircleProgressDialog.cancel();
            sCircleProgressDialog = null;
        }
    }

}

CustomUpdateParser.java 代码如下所示:

import androidx.annotation.NonNull;

import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.listener.IUpdateParseCallback;
import com.xuexiang.xupdate.proxy.IUpdateParser;
//import com.xuexiang.xupdatedemo.entity.CustomResult;
import com.xuexiang.xutil.net.JsonUtil;

/**
 * 自定义更新解析器
 *
 * @author xuexiang
 * @since 2018/7/12 下午3:46
 */
public class CustomUpdateParser implements IUpdateParser {
    @Override
    public UpdateEntity parseJson(String json) throws Exception {
        return getParseResult(json);
    }

    private UpdateEntity getParseResult(String json) {
        CustomResult result = JsonUtil.fromJson(json, CustomResult.class);
        if (result != null) {
            return new UpdateEntity()
                    .setHasUpdate(result.hasUpdate)
                    .setIsIgnorable(result.isIgnorable)
                    .setVersionCode(result.versionCode)
                    .setVersionName(result.versionName)
                    .setUpdateContent(result.updateLog)
                    .setDownloadUrl(result.apkUrl)
                    .setSize(result.apkSize);
        }
        return null;
    }

    @Override
    public void parseJson(String json, @NonNull IUpdateParseCallback callback) throws Exception {
        //当isAsyncParser为 true时调用该方法, 所以当isAsyncParser为false可以不实现
        callback.onParseResult(getParseResult(json));
    }


    @Override
    public boolean isAsyncParser() {
        return false;
    }
}

CustomUpdatePrompter.java 代码如下所示:

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.xuexiang.xupdate.XUpdate;
import com.xuexiang.xupdate.entity.CheckVersionResult;
import com.xuexiang.xupdate.entity.PromptEntity;
import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.proxy.IUpdatePrompter;
import com.xuexiang.xupdate.proxy.IUpdateProxy;
import com.xuexiang.xupdate.proxy.impl.AbstractUpdateParser;
import com.xuexiang.xupdate.service.OnFileDownloadListener;
import com.xuexiang.xupdate.utils.UpdateUtils;

import java.io.File;
import java.io.IOException;

/**
 * 自定义版本更新提示器
 *
 * @author xuexiang
 * @since 2018/7/12 下午3:48
 */
public class CustomUpdatePrompter implements IUpdatePrompter {


    /**
     * 显示自定义提示
     * 这里的弹窗内容除了“新版本大小”文字之外可以 自定义改
     * @param updateEntity
     * @param updateProxy
     */
    private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {
//        String updateInfo = UpdateUtils.getDisplayUpdateInfo(updateProxy.getContext(), updateEntity);
        String updateInfo = getVersionInfo(updateProxy.getContext(), updateEntity);

        AlertDialog.Builder builder = new AlertDialog.Builder(updateProxy.getContext())

                .setTitle(String.format("是否升级到%s版本?", updateEntity.getVersionName()))
                .setMessage(updateInfo)
                .setPositiveButton("升级", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
                            @Override
                            public void onStart() {
                                HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);
                            }

                            @Override
                            public void onProgress(float progress, long total) {
                                HProgressDialogUtils.setProgress(Math.round(progress * 100));
                            }

                            @Override
                            public boolean onCompleted(File file) {
                                HProgressDialogUtils.cancel();
                                return true; //这个表示是直接安装,需要Root权限
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                HProgressDialogUtils.cancel();
                            }
                        });
                    }
                });
        if (updateEntity.isIgnorable()) {  //默认为false就是不显示暂不升级 -- 达到强制更新效果
            builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    //注释这个不会保存 暂不升级的信息
//                    UpdateUtils.saveIgnoreVersion(updateProxy.getContext(), updateEntity.getVersionName());
                }
            }).setCancelable(false); //默认true,为false就是触摸屏幕其他位置不可关闭
        } else {
            builder.setCancelable(false);
        }
        builder.create().show();
    }


    /**
     * 获取版本更新展示信息
     * 这里是更改弹窗的“新版本大小”几个字 为自定义的文字
     * @param updateEntity
     * @return
     */
    @NonNull
    public static String getVersionInfo(Context context, @NonNull UpdateEntity updateEntity) {
        String targetSize = byte2FitMemorySize(updateEntity.getSize() * 1024);
        final String updateContent = updateEntity.getUpdateContent();

        String updateInfo = "";
        if (!TextUtils.isEmpty(targetSize)) {
            updateInfo = "测试new version大小:" + targetSize + "\n";
        }
        if (!TextUtils.isEmpty(updateContent)) {
            updateInfo += updateContent;
        }
        return updateInfo;
    }

    /**
     * 字节数转合适内存大小
     * <p>保留 1 位小数</p>
     *
     * @param byteNum 字节数
     * @return 合适内存大小
     */
    @SuppressLint("DefaultLocale")
    private static String byte2FitMemorySize(final long byteNum) {
        if (byteNum <= 0) {
            return "";
        } else if (byteNum < 1024) {
            return String.format("%.1fB", (double) byteNum);
        } else if (byteNum < 1048576) {
            return String.format("%.1fKB", (double) byteNum / 1024);
        } else if (byteNum < 1073741824) {
            return String.format("%.1fMB", (double) byteNum / 1048576);
        } else {
            return String.format("%.1fGB", (double) byteNum / 1073741824);
        }
    }

    /**
     * 显示版本更新提示
     *
     * @param updateEntity 更新信息
     * @param updateProxy  更新代理
     * @param promptEntity 提示界面参数
     */
    @Override
    public void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {
        showUpdatePrompt(updateEntity, updateProxy);
    }
}

HProgressDialogUtils.java 代码如下所示:

import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.text.TextUtils;

/**
 * Created by Vector on 2016/8/12 0012.
 */
public class HProgressDialogUtils {
    private static ProgressDialog sHorizontalProgressDialog;

    private HProgressDialogUtils() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    @SuppressLint("NewApi")
    public static void showHorizontalProgressDialog(Context context, String msg, boolean isShowSize) {
        cancel();

        if (sHorizontalProgressDialog == null) {
            sHorizontalProgressDialog = new ProgressDialog(context);
            sHorizontalProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            sHorizontalProgressDialog.setCancelable(false);
            if (isShowSize) {
                sHorizontalProgressDialog.setProgressNumberFormat("%2dMB/%1dMB");
            }

        }
        if (!TextUtils.isEmpty(msg)) {
            sHorizontalProgressDialog.setMessage(msg);
        }
        sHorizontalProgressDialog.show();

    }

    public static void setMax(long total) {
        if (sHorizontalProgressDialog != null) {
            sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
        }
    }

    public static void cancel() {
        if (sHorizontalProgressDialog != null) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void setProgress(int current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        sHorizontalProgressDialog.setProgress(current);
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void setProgress(long current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }

    public static void onLoading(long total, long current) {
        if (sHorizontalProgressDialog == null) {
            return;
        }
        if (current == 0) {
            sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
        }
        sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
        if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
            sHorizontalProgressDialog.dismiss();
            sHorizontalProgressDialog = null;
        }
    }
}

CustomResult.java 代码如下所示:

import java.io.Serializable;

/**
 * 自定义版本检查的结果
 *
 * @author xuexiang
 * @since 2018/7/11 上午1:03
 */
public class CustomResult implements Serializable {
    public boolean hasUpdate;
    public boolean isIgnorable;
    public int versionCode;
    public String versionName;
    public String updateLog;
    public String apkUrl;
    public long apkSize;
}

作者CSDN: https://blog.csdn.net/xuexiangjys
Github项目: https://github.com/xuexiangjys/XUpdate
快速使用XUpdateAPI: https://github.com/xuexiangjys/XUpdateAPI

  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
这些都是 Android NDK 内部的 `Android.mk` 文件。其中,`./android-ndk-r25c/sources/android/native_app_glue/Android.mk` 是用于编译 Native Activity 示例应用程序的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/support/Android.mk` 是包含一些 Android 支持库的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/ndk_helper/Android.mk` 是包含一些辅助函数和类的 `Android.mk` 文件;`./android-ndk-r25c/sources/android/cpufeatures/Android.mk` 是用于编译 `cpufeatures` 库的 `Android.mk` 文件,该库提供了一些 CPU 相关的信息和功能;`./android-ndk-r25c/sources/cxx-stl/llvm-libc++abi/Android.mk` 和 `./android-ndk-r25c/sources/cxx-stl/llvm-libc++/Android.mk` 是用于编译 C++ STL 库的 `Android.mk` 文件,分别对应 libc++abi 和 libc++ 两个 STL 库;`./android-ndk-r25c/sources/third_party/googletest/Android.mk` 是用于编译 Google Test 测试框架的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/Android.mk` 是用于编译 Shaderc 编译器的 `Android.mk` 文件,该编译器可以将 GLSL 代码编译成 SPIR-V 代码;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc/Android.mk` 是用于编译 Shaderc 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/libshaderc_util/Android.mk` 是用于编译 Shaderc Util 库的 `Android.mk` 文件,该库提供了一些辅助函数和类;`./android-ndk-r25c/sources/third_party/shaderc/third_party/Android.mk` 是用于编译 Shaderc 编译器依赖的第三方库的 `Android.mk` 文件,包括 glslang 和 spirv-tools 两个库;`./android-ndk-r25c/sources/third_party/shaderc/third_party/glslang/Android.mk` 是用于编译 glslang 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/shaderc/third_party/spirv-tools/Android.mk` 是用于编译 spirv-tools 库的 `Android.mk` 文件;`./android-ndk-r25c/sources/third_party/vulkan/src/build-android/jni/Android.mk` 是用于编译 Vulkan 库的 `Android.mk` 文件。 如果您要在 Android NDK 中编写自己的 `Android.mk` 文件,可以参考这些示例文件。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值