[Andrioid开发] 实现一个简单通用的APP更新服务

[Andrioid开发] 实现一个简单通用的APP更新服务

已测试适用于安卓6、7、8、9、10。(11、12未测,可自测)
支持可json远程控制的强制更新和非强制更新。
支持浏览器下载和应用内直接下载并跳转安装。
支持进入界面检测升级和点击按钮检测升级。
支持根据自己需求自定义修改检测更新代码。

名称地址
小Demo源码https://gitee.com/yiruijie/update.git
Update_V1.0.apkhttps://gitee.com/yiruijie/update/attach_files/955256/download/Update_V1.0.apk

一.更新弹窗效果图

强制升级
点击升级弹窗外的其他屏幕部分,不可取消升级弹窗。
应用内下载完成后自动跳转安装,不可取消进度弹窗。
点击浏览器下载时销毁软件界面,并直接跳转到浏览器下载。
非强制升级
点击升级弹窗外的其他屏幕部分,可取消弹窗。

在这里插入图片描述

二. 搭建json格式网页及源码

https://yirj.gitee.io/json1
{
  "hasUpdate": true,  //是否有更新
  "NoIgnorable": false, //不可忽略的更新
  "versionCode": 2,   //服务端的版本号
  "versionName": "1.0.2", //服务端的版本名
  "updateLog": "\n1、系统弹窗样式升级。\n2、进入界面检测升级。\n3、点击按钮检测升级。\n4、Json远程控制升级。\n5、远程控制非/强制升级。\n6、应用内直接下载升级。\n7、跳转到浏览器下载升级。", //更新内容
  "apkUrl": "https://gitee.com/yiruijie/update/attach_files/955257/download/Update_V1.0.2.apk",//新版本下载的地址
  "webUrl":"https://gitee.com/yiruijie/update/attach_files/955257/download/Update_V1.0.2.apk",//浏览器下载APK的网址
  "apkSize": "2.61MB" //新版本的大小
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>更新检查</title>
</head>
<body>
	<pre id="json"></pre>
</body>
<script type="text/javascript">
    let btn = document.querySelector('#json');
    let data =
    {
        "hasUpdate": true,  //是否有更新
        "NoIgnorable": false, //不可忽略的更新
        "versionCode": 2,   //服务端的版本号
        "versionName": "1.0.2", //服务端的版本名
        "updateLog": "\n1、系统弹窗样式升级。\n2、进入界面检测升级。\n3、点击按钮检测升级。\n4、Json远程控制升级。\n5、远程控制非/强制升级。\n6、应用内直接下载升级。\n7、跳转到浏览器下载升级。", //更新内容
        "apkUrl": "https://gitee.com/yiruijie/update/attach_files/955257/download/Update_V1.0.2.apk",//新版本下载的地址
        "webUrl":"https://gitee.com/yiruijie/update/attach_files/955257/download/Update_V1.0.2.apk",//浏览器下载APK的网址
        "apkSize": "2.61MB" //新版本的大小
    };
    btn.textContent = JSON.stringify(data, null, 2);
</script>
</html>

三、app/build.gradle添加

dependencies {
    ......
    //app/build.gradle添加androidx支持
    implementation 'androidx.appcompat:appcompat:1.4.1'
}

四、gradle.properties

//开启AndroidX的使用
android.useAndroidX=true

五、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" />
<!--请求安装包权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        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>

六、创建xml/file_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

七、主界面加入检测升级代码

public class MainActivity extends AppCompatActivity {
    
    private static boolean hasUpdate, NoIgnorable = true; //是否有更新//不可忽略的更新
    private static int versionCode = 0;
    private static String versionName, updateLog, apkSize, apkUrl, webUrl = "";
    private static final String[] upl = new String[]{"https://yirj.gitee.io/json1111", "https://yirj.gitee.io/json1"};//无效 有效轮询


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //解决网络安全异常问题
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
        //进入界面调用检查更新的方
        checkUpdate();
        //申请存储权限
        getReadPermissions();
    }

    //点击检查更新按钮
    public void BtnCheckUpdate(View view) {
        Toast.makeText(this, "点击了检查更新按钮", Toast.LENGTH_SHORT).show();
        //点击按钮检查更新
        checkUpdate();
    }

    //检查更新的方法
    private void checkUpdate() {
        try {
            GetServerJson();
            //如果检测本程序的版本号小于服务器的版本号,那么提示用户更新
            if (hasUpdate & getVersionCode() < versionCode) {
                showDialogUpdate();//弹出提示版本更新的对话框
            } else {
                Toast.makeText(this, "当前已是最新版本", Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前使用的软件包的版本号
     */
    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() {
        android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(this);
        if (NoIgnorable) {
            builder.setCancelable(false);//开启强制更新,无法关闭
        }

        builder.setTitle("是否升级到" + versionName + "版本?").
                //设置提示框的图标
                setIcon(R.mipmap.ic_launcher_round).
                setMessage("新版本大小:" + apkSize + "\n" + updateLog)
                .setPositiveButton("更新", (dialog, which) -> {
                    loadNewVersionProgress();//下载最新的版本程序
                })
                /*.setNegativeButton("取消", (dialog, which) -> {
                    //finish();
                })*/
                .setNeutralButton("浏览器下载", (dialog, which) -> {
                    Uri uri = Uri.parse(webUrl);
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    startActivity(intent);
                    finish(); //销毁
                });
        // 生产对话框
        android.app.AlertDialog alertDialog = builder.create();
        // 显示对话框
        alertDialog.show();
    }

    /**
     * 获取服务端的json数据
     */
    public static void GetServerJson() {
        URL infoUrl;
        InputStream inStream;
        String line;
        try {
            String uurl = checkUrl(upl);//轮询验证可用url
            infoUrl = new URL(uurl);
            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, StandardCharsets.UTF_8));
                StringBuilder strber = new StringBuilder();
                while ((line = reader.readLine()) != null)
                    strber.append(line).append("\n");
                inStream.close();
                int start = strber.indexOf("{");
                int end = strber.indexOf("}");
                String json = strber.substring(start, end + 1);
                //System.out.println("更新JSON:\n" + json);
                try {
                    JSONObject 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");
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

	/*
     * 验证Url 单例模式
     */
    public static String checkUrl(String[] ltl) {
        String resultUrl = null;
        for (String url : ltl) {
            resultUrl = url;
            try {
                URL infoUrl = new URL(url);
                URLConnection connection = infoUrl.openConnection();
                HttpURLConnection httpConnection = (HttpURLConnection) connection;
                int code = httpConnection.getResponseCode();
//                System.out.println("Code:" + code+" 链接:"+resultUrl); //200 404
                if (code == 200) { //状态码为200证明网址正常,跳出for不再循环,如果不为200,就循环下一个网址。
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resultUrl;
    }

    /**
     * 下载新版本程序
     */
    private void loadNewVersionProgress() {
        final String uri = apkUrl;  //测试使用的下载APP直链
        final ProgressDialog pd;    //进度条对话框
        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(uri, pd);
                    installApk(file);
//                    pd.dismiss(); //结束掉进度条对话框
                } catch (Exception e) {
                    //下载apk失败
                    Toast.makeText(MainActivity.this, "下载更新失败:请使用浏览器下载", Toast.LENGTH_LONG).show();
                    e.printStackTrace();
                }
                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, getApplication().getPackageName() + ".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");
        }
        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;
        }
    }

    /**
     * 权限的验证及处理,相关方法
     */
    public void getReadPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
                    ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
                    ContextCompat.checkSelfPermission(this, Manifest.permission.REQUEST_INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,
                        new String[]{
                                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.REQUEST_INSTALL_PACKAGES}, 10001);
            }
        }
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值