Android自动更新的实现

Android自动更新的实现

需求:

在我们开发App的过程中,总会遇到这样的需求。在App运行时检查是否有新版本出现,并对其进行更新。今天就来介绍一下,如何实现App的内部自动更新。

获取接口

首先,我们需要从后端获取关于版本信息数据,通过后端提供的接口返回的数据。数据格式采用Json,示例文本如下:

{
    "appname":"Will.apk",
    "serverVersion":"1.0.2",
    "serverFlag":"1",
    "lastForce":"1",
    "updateurl":"http://www.baidu.com",
    "upgrateinfo":"V1.0.2全新上线,赶快来体验吧!"
}

如果在学习过程中,你没有后端为你提供接口,那么参考我的另一篇文章,使用Jhipster生成一个简单的API接口。
Jhipster简单使用

实现过程

接下来开始进行我们Android端的实现。
思路:
1.实现bean用于对接后端接口实现app的更新
2.使用retrofit来请求版本更新的接口
3.下载apk
4.通过BroadcastReceiver来监听是否下载完成

步骤一:创建一个接受Json数据返回的类

我们从接口获取的Json数据需要一个类来解析,代码如下。

public class UpdateAppInfo {

    private String appname;
    private String serverVersion;
    private String serverFlag;
    private String lastForce;
    private String updateurl;
    private String upgradeinfo;

    public String getAppname() {
        return appname;
    }

    public void setAppname(String appname) {
        this.appname = appname;
    }

    public String getServerVersion() {
        return serverVersion;
    }

    public void setServerVersion(String serverVersion) {
        this.serverVersion = serverVersion;
    }

    public String getServerFlag() {
        return serverFlag;
    }

    public void setServerFlag(String serverFlag) {
        this.serverFlag = serverFlag;
    }

    public String getLastForce() {
        return lastForce;
    }

    public void setLastForce(String lastForce) {
        this.lastForce = lastForce;
    }

    public String getUpdateurl() {
        return updateurl;
    }

    public void setUpdateurl(String updateurl) {
        this.updateurl = updateurl;
    }

    public String getUpgradeinfo() {
        return upgradeinfo;
    }

    public void setUpgradeinfo(String upgradeinfo) {
        this.upgradeinfo = upgradeinfo;
    }

    @Override
    public String toString() {
        return "UpdateAppInfo{" +
            "appname='" + appname + '\'' +
            ", serverVersion='" + serverVersion + '\'' +
            ", serverFlag='" + serverFlag + '\'' +
            ", lastForce='" + lastForce + '\'' +
            ", updateurl='" + updateurl + '\'' +
            ", upgradeinfo='" + upgradeinfo + '\'' +
            '}';
    }
}

步骤二:Retrofit+RxJava实现网络接口

先加入依赖:

// Android 支持 Rxjava
// 此处一定要注意使用RxJava2的版本
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Android 支持 Retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
// 衔接 Retrofit & RxJava
// 此处一定要注意使用RxJava2的版本
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

接下来创建网络接口:

public interface NetService{
    @GET("/api/update-infos/1")
    Observable getUpdateInfo();
}

通过工厂模式来创建NetService

public class ServiceFactory{
    private static final String BaseUrl = "http://10.10.2.87:8080"
    public static  T createServiceFrom(final Class serviceClass){
        Retrofit adapter = new Retrofit.Builder()
            .baseUrl(BaseUrl)
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器
                .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
                .build();
        return adapter.create(serviceClass);
    }
}

步骤三:创建检测版本更新的接口

public class CheckUpdateUtils {

    @SuppressWarnings("unused")
    public static void checkUpdate(String appCode,String curVersion,final CheckCallBack updateCallback){
        ApiService apiService = ServiceFactory.createServiceFrom(ApiService.class);
        Log.e("check","开始这一步了吗1");
        String appName = "hoolay";
        apiService.getCall()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subject() {
                    @Override
                    public boolean hasObservers() {
                        return false;
                    }

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

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

                    @Override
                    public Throwable getThrowable() {
                        return null;
                    }

                    @Override
                    protected void subscribeActual(Observer observer) {

                    }

                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(UpdateAppInfo value) {
                        if(value.equals(null)||value.getUpdateurl().equals(null)){
                            Log.e("check","开始这一步了吗2");
                            updateCallback.onError();
                        }else {
                            Log.e("check","开始这一步了吗3");
                            updateCallback.onSuccess(value);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}

这里还需要结果回调监听:

//检测成功或失败的相关接口
public interface CheckAllBack{
    void onSuccess(UpdateAppInfo updateInfo);
    void onError();
}

步骤四:在Activity中完成的部分

  • 创建一个获取当前版本号及版本名称的类
/**
 * 获取当前的版本号及版本名称的工具类
 */

public class ApkVersionCodeUtils {
    /**
     * 获取当前本地apk的版本
     *
     * @param mContext
     * @return
     */
    public static int getVersionCode(Context mContext) {
        int versionCode = 0;
        try {
            //获取软件版本号,对应AndroidManifest.xml下android:versionCode
            versionCode = mContext.getPackageManager().
                    getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionCode;
    }

    /**
     * 获取版本号名称
     *
     * @param context 上下文
     * @return
     */
    public static String getVerName(Context context) {
        String verName = "";
        try {
            verName = context.getPackageManager().
                    getPackageInfo(context.getPackageName(), 0).versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return verName;
    }
}
  • 连接网络检查是否需要更新
CheckUpdateUtils.checkUpdate("apk", "1.0.0", new CheckCallBack() {
            @Override
            public void onSuccess(UpdateAppInfo updateAppInfo) {
                String isForce = updateAppInfo.getLastForce();
                String downUrl = updateAppInfo.getUpdateurl();
                String updateinfo = updateAppInfo.getUpgradeinfo();
                String appName = updateAppInfo.getAppname();
                Log.e("aaa",appName);
                if(isForce.equals("1")&& !TextUtils.isEmpty(updateinfo)){//强制更新
                    forceUpdate(MainActivity.this,appName,downUrl,updateinfo);
                }else{//非强制更新
                    //正常升级
                    normalUpdate(MainActivity.this,appName,downUrl,updateinfo);
                }
            }

            @Override
            public void onError() {
                noneUpdate(MainActivity.this);
            }
        });
  • 更新对话框的使用:
private void forceUpdate(final Context context, final String appName, final String downUrl, final String updateinfo) {
        mDialog = new AlertDialog.Builder(context);
        mDialog.setTitle(appName+"又更新咯!");
        mDialog.setMessage(updateinfo);
        mDialog.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!canDownloadState()) {
                    showDownloadSetting();
                    return;
                }
                //      DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName);
     AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName);
            }
        }).setCancelable(false).create().show();
    }

步骤五:使用HttpUrlConnection下载

public class AppInnerDownLoder {
    public final static String SD_FOLDER = Environment.getExternalStorageDirectory()+ "/VersionChecker/";
    private static final String TAG = AppInnerDownLoder.class.getSimpleName();

    /**
     * 从服务器中下载APK
     */
    @SuppressWarnings("unused")
    public static void downLoadApk(final Context mContext,final String downURL,final String appName ) {

        final ProgressDialog pd; // 进度条对话框
        pd = new ProgressDialog(mContext);
        pd.setCancelable(false);// 必须一直下载完,不可取消
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        pd.setMessage("正在下载安装包,请稍后");
        pd.setTitle("版本升级");
        pd.show();
        new Thread() {
            @Override
            public void run() {
                try {
                    File file = downloadFile(downURL,appName, pd);
                    sleep(3000);
                    installApk(mContext, file);
                    // 结束掉进度条对话框
                    pd.dismiss();
                } catch (Exception e) {
                    pd.dismiss();

                }
            }
        }.start();
    }

    /**
     * 从服务器下载最新更新文件
     * 
     * @param path
     *            下载路径
     * @param pd
     *            进度条
     * @return
     * @throws Exception
     */
    private static File downloadFile(String path,String appName ,ProgressDialog pd) throws Exception {
        // 如果相等的话表示当前的sdcard挂载在手机上并且是可用的
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            URL url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            // 获取到文件的大小
            pd.setMax(conn.getContentLength());
            InputStream is = conn.getInputStream();
            String fileName = SD_FOLDER
                     + appName+".apk";
            File file = new File(fileName);
            // 目录不存在创建目录
            if (!file.getParentFile().exists())
                file.getParentFile().mkdirs();
            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 {
            throw new IOException("未发现有SD卡");
        }
    }

    /**
     * 安装apk
     */
    private static void installApk(Context mContext, File file) {
        Uri fileUri = Uri.fromFile(file);
        Intent it = new Intent();
        it.setAction(Intent.ACTION_VIEW);
        it.setDataAndType(fileUri, "application/vnd.android.package-archive");
        it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不开应用
        mContext.startActivity(it);
    }

    /**
     * 获取应用程序版本(versionName)
     * 
     * @return 当前应用的版本号
     */

    private static double getLocalVersion(Context context) {
        PackageManager manager = context.getPackageManager();
        PackageInfo info = null;
        try {
            info = manager.getPackageInfo(context.getPackageName(), 0);
        } catch (NameNotFoundException e) {
            Log.e(TAG, "获取应用程序版本失败,原因:" + e.getMessage());
            return 0.0;
        }

        return Double.valueOf(info.versionName);
    }
    /** 
     * byte(字节)根据长度转成kb(千字节)和mb(兆字节) 
     *  
     * @param bytes 
     * @return 
     */  
    public static String bytes2kb(long bytes) {  
        BigDecimal filesize = new BigDecimal(bytes);  
        BigDecimal megabyte = new BigDecimal(1024 * 1024);  
        float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP)  
                .floatValue();  
        if (returnValue > 1)  
            return (returnValue + "MB");  
        BigDecimal kilobyte = new BigDecimal(1024);  
        returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP)  
                .floatValue();  
        return (returnValue + "KB");  
    }  
}

步骤六:监听App是否安装完成

public class ApkInstallReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
              long downloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
              installApk(context, downloadApkId);
        }
    }

    /**
     * 安装apk
     */
    private void installApk(Context context,long downloadApkId) {
        // 获取存储ID
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        long downId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
        if(downloadApkId == downId){
            DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId);
            if (downloadFileUri != null) {
            Intent install= new Intent(Intent.ACTION_VIEW);
            install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
            install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(install);
            }else{
                Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE 新建一个广播接收者即可,配置如下:


            
                
            
        

总结

本文介绍了Android如何简单实现更新的效果,主要参考了以下博文:
安卓开发实战之app之版本更新
更为详细的我没有提到的部分请详见原博。

你可能感兴趣的:(Android自动更新的实现)