安卓apk版本检测下载升级全过程

一般情况下,app要有在线升级的功能,以便app的功能不断完善改进。不同Android版本app升级的方法也不尽相同,版本不对,升级时,要么无法下载apk,要么下载后无法安装。今天介绍的是Android studio3.6.3版本,compileSdkVersion 为29,gradle版本为gradle-4.1-all。要想完成apk在线升级,首先需要必备的条件有四个:

第一、app具有检测自身版本的功能

第二、app具有检测服务器上存储的最新版本apk功能

第三、app具有能从网上下载最新版本apk功能

第四、app具有能自动安装最新版本apk功能,且能覆盖原有版本

根据以上4个条件,实现app在线升级的步骤如下:

目录

一、本例的layout设计

1、layout文件

2、显示效果

二、检测当前版本

1、当前版本信息在哪里?

(1)在AndroidManifest.xml文件中

(2)在build.gradle(Moudle:app)中

2、怎么检测当前版本?

三、获取服务器最新版本信息

1.读取服务器上最新版本apk版本信息、链接地址

2、解析版本信息中版本名称、版本号函数

四、比较当前版本与最新版本

1、版本号的作用

2、版本名的意义

3、比较版本高低和红点显示

五、点击红点所在TextView下载服务器最新版本apk

1、显示程序更新对话框

2、版本更新对话框函数

3、下载程序进度条

4、从服务器下载程序并存储到手机Download文件夹内

5、覆盖安装

6、xm文件夹下file_paths.xml文件

7、AndroidManifest.xml中添加

8、动态授权函数

9、在oncreat中的一些操作

10、本例中用到的一些变量

六、生成release版apk

七、测试


一、本例的layout设计

1、layout文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".VersionUpdateActivity">
    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_stoke_black"
        android:layout_marginTop="0dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        android:gravity="center"
        android:orientation="vertical">
        <LinearLayout
            android:id="@+id/app_version"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_stoke_black"
            android:layout_marginTop="20dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintLeft_toLeftOf="parent"
            android:gravity="center"
            android:orientation="horizontal">
            <!--        tools:ignore="MissingConstraints"-->

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:orientation="vertical" >
                <TextView
                    android:id="@+id/app_version_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:textColor="@color/black"
                    android:textSize="20dp"
                    android:layout_gravity="center_vertical"
                    android:layout_marginTop="0dp"
                    android:layout_marginLeft="0dp"
                    android:layout_marginRight="0dp"
                    android:layout_marginBottom="20dp"
                    android:text="版本信息"/>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dp"
                    android:orientation="vertical" >
                    <TextView
                        android:id="@+id/app_version_name"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:gravity="left"
                        android:textColor="@color/black"
                        android:textSize="16dp"
                        android:layout_gravity="center_vertical"
                        android:layout_marginTop="5dp"
                        android:layout_marginLeft="0dp"
                        android:layout_marginRight="0dp"
                        android:layout_marginBottom="20dp"
                        android:background="@drawable/text"
                        android:text="现有版本"/>

                    <TextView
                        android:id="@+id/app_version_content"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:gravity="left"
                        android:textColor="@color/black"
                        android:textSize="16dp"
                        android:layout_gravity="center_vertical"
                        android:layout_marginTop="5dp"
                        android:layout_marginLeft="0dp"
                        android:layout_marginRight="0dp"
                        android:layout_marginBottom="20dp"
                        android:background="@drawable/text"
                        android:text="功能介绍"/>
                    <TextView
                        android:id="@+id/app_version_update"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:gravity="left"
                        android:textColor="@color/black"
                        android:textSize="16dp"
                        android:layout_gravity="center_vertical"
                        android:layout_marginTop="5dp"
                        android:layout_marginLeft="0dp"
                        android:layout_marginRight="0dp"
                        android:layout_marginBottom="20dp"
                        android:background="@drawable/text"
                        android:text="版本更新"/>

                    <com.jauker.widget.BadgeView
                        android:id="@+id/badge_install"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentRight="true"
                        android:layout_alignParentTop="true"
                        android:gravity="center"
                        />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

2、显示效果

  

二、检测当前版本

1、当前版本信息在哪里?

设置apk版本信息的地方有两处:

(1)在AndroidManifest.xml文件中

在application下

android:versionCode="2"

“2”就是版本号

(2)在build.gradle(Moudle:app)中

例如:

android {
    compileSdkVersion 29
    buildToolsVersion "28.0.3"
    defaultConfig {
        applicationId "com.update"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 2
        versionName "2.0.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }

 versionCode为2, versionName为 "2.0.0"。

2、怎么检测当前版本?

如下代码,函数getVersionName() 为获取当前apk版本名versionName,函数getVersionCode()为获取当前apk版本号。

/*
     * 获取当前apk版本名
     */
    private String getVersionName() throws Exception {
        
        PackageManager packageManager = getPackageManager();       
        PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
        return packInfo.versionName;

    }

    /*
     * 获取当前当前apk版本号
     */
    private int getVersionCode() {

            PackageManager packageManager = getPackageManager();           
            PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);            
            return packInfo.versionCode;
    }

三、获取服务器最新版本信息

假如服务器上apk版本存放的数据库为mysql,存放的表名为version,表设计如下

idversionversion_informationversion_link
1

[{"appname":"app_name","apkname":"apkname-2.0-updateapk.apk","verName":2.0.1"verCode":3}]

https:/******************/app-release201.apk
 

idversion是表的主键,version_information是存放版本信息的json格式数据包,数据包里包含appname、apkname和verName,verCode四项内容。version_link存放的是apk上传服务器的ip地址。每当有新版本发布,就把版本信息和链接存入表中最后一行,所以每次只需读取表中最后一行,即是目前最新版本。

1.读取服务器上最新版本apk版本信息、链接地址

 new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    Class.forName("com.mysql.jdbc.Driver");
                    //url,db_name,db_passwd为登录mysql的地址、用户名和密码
                    Connection connection = DriverManager.getConnection(url, db_name, db_passwd);
                    //取最后一条记录
                    String sql = "select * from pir_app_version order by idpir_app_version desc limit 1";
                    PreparedStatement preparedStatement = connection.prepareStatement(sql);
                    ResultSet set1 = preparedStatement.executeQuery();

                    while (set1.next()) {
                        //获得apk最新版本信息
                        version_information = set1.getObject("version_information").toString();
                        // 获得最新版本的链接
                        apkurl=set1.getObject("version_link").toString();
                    }
                    //解析出version_information的版本号
                    newVerCode=getServerVersionCode(version_information);
                    //解析出version_information的版本名
                    newVerName=getServerVersionName(version_information);
                    System.out.println("新版本号");
                    System.out.println(newVerCode);
                    set1.close();
                    preparedStatement.close();
                    connection.close();
                    mHandler.sendEmptyMessage(0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

2、解析版本信息中版本名称、版本号函数

  private String getServerVersionName(String verjson) {
        String  ServerVersionName = null;
        try {

            JSONArray array = new JSONArray(verjson);
            if (array.length() > 0) {
                JSONObject obj = array.getJSONObject(0);
                try {
                    ServerVersionName = obj.getString("verName");
                } catch (Exception e) {
                    ServerVersionName = "";

                }
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            ServerVersionName = "false";
        }
        return ServerVersionName;
    } 


    private int getServerVersionCode(String verjson) {

        int   ServerVersionCode = 0;
        try {

            JSONArray array = new JSONArray(verjson);
            if (array.length() > 0) {
                JSONObject obj = array.getJSONObject(0);
                try {
                    ServerVersionCode = Integer.parseInt(obj.getString("verCode"));

                } catch (Exception e) {
                    ServerVersionCode = -1;
                }
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            ServerVersionCode = -2;
        }
        return ServerVersionCode;
    }

四、比较当前版本与最新版本

1、版本号的作用

版本号的作用就是为了区别版本的高低,版本号是一个Integer类型的值,版本每升级一次,版本号就递增1。

2、版本名的意义

版本名只是为了描述该版本的具体序列信息的描述,这是一个值为String类型的属性,一般格式为:1.1.2(major.minor.point)的形式。

如果是一个UI风格或者业务逻辑有较大调整的应用,那么major递增1;

如果是在程序中的一些判断及算法方面做出修改,那么minor递增1;

如果只是在界面及一些性能方面做出调整及优化,那么point递增1。

3、比较版本高低和红点显示

检测完当前版本curVerCode和服务器最新版本newVerCode后,需要将两个版本进行比较,如果当前版本低于最新版本,那么需要在app中用红点进行显示,提醒用户更新版本。本例红点显示是在读取服务器版本信息之后,放在Handle中进行比较和显示。badgeView为BadgeView红点类。app_version_update为Textview类,其文字内容为“版本更新”,badgeView显示在这个Textview上。

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    try {
                        curVerCode = getVersionCode();
                        System.out.println("现版本号");
                        System.out.println(curVerCode);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (curVerCode < newVerCode) {
                        badgeView.setTargetView(app_version_update);    //设置哪个控件显示数字提醒,参数就是一个view对象
                        badgeView.setText("New");//显示“New”
                        badgeView.setBadgeGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL);  //设置badgeview的显示位置
                        badgeView.setBackground(12, Color.parseColor("#ff0000")); //设置badgeview的背景色
                    }

                    break;
            }
        }
    };

五、点击红点所在TextView下载服务器最新版本apk

1、显示程序更新对话框

        app_version_update.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //如果有最新版本
                if (curVerCode < newVerCode) {
                showDialogUpdate();//弹出提示版本更新的对话框

                } else {
                //否则显示当前是最新版本
                Toast.makeText(this, "当前已经是最新的版本", Toast.LENGTH_SHORT).show();

        }
            }
        });

2、版本更新对话框函数

    /**
     * 提示版本更新对话框
     */
    private void showDialogUpdate() {
        
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        // 设置提示框的标题
        builder.setTitle("版本升级").
                // 设置提示框的图标
                        setIcon(R.mipmap.ic_launcher).
                // 设置要显示的信息
                        setMessage("发现新版本!请及时更新").
                // 设置确定按钮
                        setPositiveButton("确定", new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        
                        loadNewVersionProgress();//下载最新的版本程序

                    }
                }).

                // 设置取消按钮,null是什么都不做,并关闭对话框
                        setNegativeButton("取消", null);

        // 创建对话框
        AlertDialog alertDialog = builder.create();
        // 显示对话框
        alertDialog.show();


    }

3、下载程序进度条

 /**
     * 下载新版本程序进度条
     */
    private void loadNewVersionProgress() {

        final String uri=apkurl;

        final ProgressDialog pd;    //进度条对话框
        pd = new ProgressDialog(this);
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        pd.setMessage("正在下载更新");
        pd.show();
        //启动子线程下载任务
        new Thread() {
            @Override
            public void run() {
                try {
                    File file = new File(String.valueOf(getFileFromServer(uri, pd)));
                    sleep(3000);
                    installApk(file);
                    pd.dismiss(); //结束进度条对话框
                } catch (Exception e) {
                    //下载apk失败
                    mHandler.sendEmptyMessage(0);
                    e.printStackTrace();
                }
            }
        }.start();
    }

4、从服务器下载程序并存储到手机Download文件夹内

    /**
     * 从服务器获取apk文件的代码
     * 传入网址uri,进度条对象即可获得一个File文件
     * (要在子线程中执行哦)
     * @return
     */
    public 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();//当前时间的毫秒数
            File dir = new   
 File(Environment.getExternalStoragePublicDirectory("/Download").getAbsolutePath());

            System.out.println(dir);
            if (!dir.exists()){
                dir.mkdir();
                System.out.println("创建目录");

            }
            //创建文件
            File file = new File(dir+"/"+"SmartPir.apk");

            if (!file.exists()){
                file.createNewFile();               

            }
            else
            {
                file.delete();                
                file.createNewFile();               
            }
            System.out.println(file);

            FileOutputStream fos = new FileOutputStream(String.valueOf(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;
        }
    }

5、覆盖安装

    /**
     * 安装apk
     * @param apkPath
     * @return
     */
    protected boolean installApk(File apkPath) {

        Intent intent= new Intent(Intent.ACTION_VIEW);
            if (Build.VERSION.SDK_INT >=24) {

        File file1= (new File(String.valueOf(apkPath)));
        
           //设置intent的标签
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//参数1:上下文, 参数2:Provider主机地址 和配置文件中保持一致,参数3:共享的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

        Uri apkUri = FileProvider.getUriForFile(VersionUpdateActivity.this, "com.update.MyFileProvider", file1);//添加这一句表示对目标应用临时授权该Uri所代表的文件

        intent.setDataAndType(apkUri,"application/vnd.android.package-archive");
        
        }else{
            intent.setDataAndType(Uri.fromFile(new File(String.valueOf(apkPath))),"application/vnd.android.package-archive");
        }
        VersionUpdateActivity.this.startActivity(intent);
       
        return false;
    }

VersionUpdateActivity为本例程的class名称,com.update为包名。

6、xm文件夹下file_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths>

        <external-path path="Download/" name="download" />
</paths>

7、AndroidManifest.xml中添加

        <provider

        android:name="com.version.MyFileProvider"
        android:authorities="com.version.MyFileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
            <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
        </provider>

8、动态授权函数

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1000)
        {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                //同意申请权限
            } else
            {
                // 用户拒绝申请权限
                Toast.makeText(VersionUpdateActivity.this,"请同意写操作", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

9、在oncreat中的一些操作

      //设置标题栏
ActionBar actionBar=getSupportActionBar();
        if(actionBar!=null)
        {
            actionBar.setTitle("版本升级");
        }

       //开放写入外部存储器权限
        if (ContextCompat.checkSelfPermission(VersionUpdateActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager. PERMISSION_GRANTED) {
            ActivityCompat. requestPermissions( this, new String[]{Manifest.permission. WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE },
                    1000);
        }

        //读取string文件中mysql地址、用户名和密码信息
        url = getText(R.string.db_url).toString();
        db_name = getText(R.string.db_name).toString();
        db_passwd = getText(R.string.db_passwd).toString();

        //绑定控件
        app_version_name=findViewById(R.id.app_version_name);
        app_version_content=findViewById(R.id.app_version_content);
        app_version_update=findViewById(R.id.app_version_update);
        badgeView = findViewById(R.id.badge_install);

10、本例中用到的一些变量

    BadgeView badgeView;
    TextView app_version_name,app_version_content,app_version_update;
    String url,apkurl;
    String db_name;
    String db_passwd;
    String TAG = "myupdate";
    int newVerCode;
    String newVerName;
    int curVerCode;
    String curVerName;
    String version_information;

六、生成release版apk

注意,以上apk都需是release版本,如果是debug版本,则无法进行覆盖安装。

签名打包操作步骤可参考文章《Android 签名打包

注意,签名和打包是两个过程。

签名是将key store password、key alias、keypassword三项信息写入jks文件,生成一个签名文件。

打包,是将程序文件和jks签名文件,打包成一个包含签名的apk文件。要想完成高版本覆盖低版本的安装,必须是带有签名的apk,否则无法安装。

七、测试

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值