安卓app自動更新功能

app是我們在開發時候經常碰到的事情,一般解決一些bug,添加了需求,實現了新的功能,以讓用戶體驗新版本的功能,這些都是項目中用到的,今天就來總結一下

第一種:引用jjdxmashljjdxm_update

GitHub地址:jjdxmashl/jjdxm_update

這是大神jjdxmashl的開源項目,下載地址見上方。有版本更新、手動更新、靜默更新、自動更新4種情況。應用內更新,實現類是友盟自動更新sdk的模式,用戶使用前只需要配置自己的服務器更新檢查接口即可(必須接口),也可以擴展加入一個接口作爲在線參數配置來實現,可以實現下面的4種2更新方式和是否強制更新組合使用,支持get、post方式請求網絡,默認是get請求。

4種更新檢查類型

  1. 手動更新:手動檢測更新(所有網絡類型環境檢測並提示主要用於點擊檢測使用)
  2. 自動更新:自動檢測更新(所有網絡類型環境檢測並提示)
  3. 僅WiFi自動檢測更新(只有WiFi網絡類型環境檢測並提示)
  4. 靜默更新:僅WiFi自動檢測下載(只有WiFi網絡類型環境檢測、下載完才提示)

2種強制更新方式

  1. 在更新檢查返回後,直接設置update.setForce(true)
  2. 配合在線參數使用,通過在線參數返回的數據設置UpdateHelper.getInstance().setForced(true)

上述4中更新檢查 結合 2種強制更新,適用於:上一個app版本有重大漏洞,修改在線 參數統一控制所有的app用戶,不更新就不可以使用app。 

主要原理:服務器上修改參數值,app端獲取後進行判斷,如果是強制更新,則在打開應用時就提示有新版本的app,更新完成後纔可以使用該app;更新爲完成,則提示框不消失,點擊back鍵則退出應用。



第二種: 判斷VersionCode,xUtils實現下載

第一步 服務器端:
  • 服務端提供一個藉口,或者網址,我這裏就用的服務器是tomcat,這裏提供一個網址如下
    //也就是一個json數據接口
    public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json";
    • 第二步 客戶端需要實現:

      首先我們要去解析服務端給的json,那麼我們就要來創建一個model類了(代碼過多,這裏只有字段,getter和setter方法自己創建):
    //app名字
        private String appname;
        //服務器版本
        private String serverVersion;
        //服務器標誌
        private String serverFlag;
        //強制升級
        private String lastForce;
        //app最新版本地址
        private String updateurl;
        //升級信息
        private String upgradeinfo;
    //在這裏使用了一個輔助類,基本和model字段差不多:
    
    public class UpdateInformation {
        public static String appname = MyApplication.getInstance()
                .getResources().getString(R.string.app_name);
        public static int localVersion = 1;// 本地版本
        public static String versionName = ""; // 本地版本名
        public static int serverVersion = 1;// 服務器版本
        public static int serverFlag = 0;// 服務器標誌
        public static int lastForce = 0;// 之前強制升級版本
        public static String updateurl = "";// 升級包獲取地址
        public static String upgradeinfo = "";// 升級信息
    
        public static String downloadDir = "wuyinlei";// 下載目錄
    }
    我們知道,我們在進入app的時候,這個時候如果檢測到服務器端有了新的版本,就回彈出提示框,提示我們更新。這個我們在MainActivity裏面處理邏輯(onCreate()方法裏面):
    OkhttpManager.getAsync(Config.UPDATE_URL, new OkhttpManager.DataCallBack() {
                @Override
                public void requestFailure(Request request, Exception e) {
    
                }
                @Override
                public void requestSuccess(String result) {
                    try {
                        Log.d("wuyiunlei",result);
                        JSONObject object = new JSONObject(result);
                        UpdateInfoModel model = new UpdateInfoModel();
                        model.setAppname(object.getString("appname"));
                        model.setLastForce(object.getString("lastForce"));
                        model.setServerFlag(object.getString("serverFlag"));
                        model.setServerVersion(object.getString("serverVersion"));
                        model.setUpdateurl(object.getString("updateurl"));
                        model.setUpgradeinfo(object.getString("upgradeinfo"));
                        tmpMap.put(DeliverConsts.KEY_APP_UPDATE, model);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                    //發送廣播
                    sendBroadcast(new Intent(UpdateReceiver.UPDATE_ACTION));
                }
            });

    當然了,我們也要註冊和結束廣播:

     /**
         * 廣播註冊
         */
        private void registerBroadcast() {
            mUpdateReceiver = new UpdateReceiver(false);
            mIntentFilter = new IntentFilter(UpdateReceiver.UPDATE_ACTION);
            this.registerReceiver(mUpdateReceiver, mIntentFilter);
        }
     /**
         * 廣播卸載
         */
        private void unRegisterBroadcast() {
            try {
                this.unregisterReceiver(mUpdateReceiver);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    好了,接下來我們看下我們自定義的廣播接收者UpdateReceiver .java:

    /**
     * 版本更新升級 廣播接受者
     *
     */
    public class UpdateReceiver extends BroadcastReceiver {
        private AlertDialog.Builder mDialog;
        public static final String UPDATE_ACTION = "wuyinlei_aixinwen";
        private SharedPreferencesHelper mSharedPreferencesHelper;
        private boolean isShowDialog;
    
        public UpdateReceiver() {
        }
    
        public UpdateReceiver(boolean isShowDialog) {
            super();
            this.isShowDialog = isShowDialog;
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            mSharedPreferencesHelper = mSharedPreferencesHelper
                    .getInstance(MyApplication.getInstance());
            //當然了,這裏也可以直接new處hashmap
            HashMap<String, Object> tempMap = MyApplication.getInstance()
                    .getTempMap();
            UpdateInfoModel model = (UpdateInfoModel) tempMap
                    //就是一個標誌
                    .get(DeliverConsts.KEY_APP_UPDATE);
            try {
    
                /**
                 * 獲取到當前的本地版本
                 */
                UpdateInformation.localVersion = MyApplication
                        .getInstance()
                        //包管理獨享
                        .getPackageManager()
                        //包信息
                        .getPackageInfo(
                                MyApplication.getInstance()
                                        .getPackageName(), 0).versionCode;
                /**
                 * 獲取到當前的版本名字
                 */
                UpdateInformation.versionName = MyApplication
                        .getInstance()
                        .getPackageManager()
                        .getPackageInfo(
                                MyApplication.getInstance()
                                        .getPackageName(), 0).versionName;
            } catch (Exception e) {
                e.printStackTrace();
            }
            //app名字
            UpdateInformation.appname = MyApplication.getInstance()
                    .getResources().getString(R.string.app_name);
            //服務器版本
            UpdateInformation.serverVersion = Integer.parseInt(model
                    .getServerVersion());
            //服務器標誌
            UpdateInformation.serverFlag = Integer.parseInt(model.getServerFlag());
            //強制升級
            UpdateInformation.lastForce = Integer.parseInt(model.getLastForce());
            //升級地址
            UpdateInformation.updateurl = model.getUpdateurl();
            //升級信息
            UpdateInformation.upgradeinfo = model.getUpgradeinfo();
    
            //檢查版本
            checkVersion(context);
    
        }
    
        /**
         * 檢查版本更新
         * 
         * @param context
         */
        public void checkVersion(Context context) {
            if (UpdateInformation.localVersion < UpdateInformation.serverVersion) {
                // 需要進行更新
                mSharedPreferencesHelper.putIntValue(
                        //有新版本
                        SharedPreferencesTag.IS_HAVE_NEW_VERSION, 1);
                //更新
                update(context);
            } else {
                mSharedPreferencesHelper.putIntValue(
                        SharedPreferencesTag.IS_HAVE_NEW_VERSION, 0);
                if (isShowDialog) {
                    //沒有最新版本,不用升級
                    noNewVersion(context);
                }
                clearUpateFile(context);
            }
        }
    
        /**
         * 進行升級
         * 
         * @param context
         */
        private void update(Context context) {
            if (UpdateInformation.serverFlag == 1) {
                // 官方推薦升級
                if (UpdateInformation.localVersion < UpdateInformation.lastForce) {
                    //強制升級
                    forceUpdate(context);
                } else {
                    //正常升級
                    normalUpdate(context);
                }
    
            } else if (UpdateInformation.serverFlag == 2) {
                // 官方強制升級
                forceUpdate(context);
            }
        }
    
        /**
         * 沒有新版本
         * @param context
         */
        private void noNewVersion(final Context context) {
            mDialog = new AlertDialog.Builder(context);
            mDialog.setTitle("版本更新");
            mDialog.setMessage("當前爲最新版本");
            mDialog.setNegativeButton("確定", new DialogInterface.OnClickListener() {
    
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            }).create().show();
        }
    
        /**
         * 強制升級 ,如果不點擊確定升級,直接退出應用
         * 
         * @param context
         */
        private void forceUpdate(final Context context) {
            mDialog = new AlertDialog.Builder(context);
            mDialog.setTitle("版本更新");
            mDialog.setMessage(UpdateInformation.upgradeinfo);
    
            mDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Intent mIntent = new Intent(context, UpdateService.class);
                    mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    mIntent.putExtra("appname", UpdateInformation.appname);
                    mIntent.putExtra("appurl", UpdateInformation.updateurl);
                    //啓動服務
                    context.startService(mIntent);
                }
            }).setNegativeButton("退出", new DialogInterface.OnClickListener() {
    
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // 直接退出應用
                    //ManagerActivity.getInstance().finishActivity();
                    System.exit(0);
                }
            }).setCancelable(false).create().show();
        }
    
        /**
         * 正常升級,用戶可以選擇是否取消升級
         * 
         * @param context
         */
        private void normalUpdate(final Context context) {
            mDialog = new AlertDialog.Builder(context);
            mDialog.setTitle("版本更新");
            mDialog.setMessage(UpdateInformation.upgradeinfo);
            mDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Intent mIntent = new Intent(context, UpdateService.class);
                    mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    //傳遞數據
                    mIntent.putExtra("appname", UpdateInformation.appname);
                    mIntent.putExtra("appurl", UpdateInformation.updateurl);
                    context.startService(mIntent);
                }
            }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            }).create().show();
        }
    
        /**
         * 清理升級文件
         * 
         * @param context
         */
        private void clearUpateFile(final Context context) {
            File updateDir;
            File updateFile;
            if (Environment.MEDIA_MOUNTED.equals(Environment
                    .getExternalStorageState())) {
                updateDir = new File(Environment.getExternalStorageDirectory(),
                        UpdateInformation.downloadDir);
            } else {
                updateDir = context.getFilesDir();
            }
            updateFile = new File(updateDir.getPath(), context.getResources()
                    .getString(R.string.app_name) + ".apk");
            if (updateFile.exists()) {
                Log.d("update", "升級包存在,刪除升級包");
                updateFile.delete();
            } else {
                Log.d("update", "升級包不存在,不用刪除升級包");
            }
        }
    }

    接下最後我們來看下服務吧UpdateService .java:

    /**
     * 不要忘記註冊,在mainfest文件中
    */
    public class UpdateService extends Service {
        // BT字節參考量
        private static final float SIZE_BT = 1024L;
        // KB字節參考量
        private static final float SIZE_KB = SIZE_BT * 1024.0f;
        // MB字節參考量
        private static final float SIZE_MB = SIZE_KB * 1024.0f;
    
        private final static int DOWNLOAD_COMPLETE = 1;// 完成
        private final static int DOWNLOAD_NOMEMORY = -1;// 內存異常
        private final static int DOWNLOAD_FAIL = -2;// 失敗
    
        private String appName = null;// 應用名字
        private String appUrl = null;// 應用升級地址
        private File updateDir = null;// 文件目錄
        private File updateFile = null;// 升級文件
    
        // 通知欄
        private NotificationManager updateNotificationManager = null;
        private Notification updateNotification = null;
    
        private Intent updateIntent = null;// 下載完成
        private PendingIntent updatePendingIntent = null;// 在下載的時候
    
        @Override
        public IBinder onBind(Intent arg0) {
            return null;
        }
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            appName = intent.getStringExtra("appname");
            appUrl = intent.getStringExtra("appurl");
            updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            updateNotification = new Notification();
            //通知圖標
            updateNotification.icon = R.mipmap.head;
            //通知信息描述
            updateNotification.tickerText = "正在下載 " + appName;
            updateNotification.when = System.currentTimeMillis();
            updateIntent = new Intent(this, MyApplication.class);
            updatePendingIntent = PendingIntent.getActivity(this, 0, updateIntent,
                    0);
            updateNotification.contentIntent = updatePendingIntent;
            updateNotification.contentIntent.cancel();
            updateNotification.contentView = new RemoteViews(getPackageName(),
                    //這個佈局很簡單,就是一個圖片和兩個textview,分別是正在下載和下載進度
                    R.layout.download_notification);
            updateNotification.contentView.setTextViewText(
                    R.id.download_notice_name_tv, appName + " 正在下載");
            updateNotification.contentView.setTextViewText(
                    R.id.download_notice_speed_tv, "0MB (0%)");
            updateNotificationManager.notify(0, updateNotification);
            new UpdateThread().execute();
        }
    
        /**
         * 在這裏使用了asynctask異步任務來下載
         */
        class UpdateThread extends AsyncTask<Void, Void, Integer> {
            @Override
            protected Integer doInBackground(Void... params) {
                return downloadUpdateFile(appUrl);
            }
    
            @Override
            protected void onPostExecute(Integer result) {
                super.onPostExecute(result);
    
                if (result == DOWNLOAD_COMPLETE) {
                    Log.d("update", "下載成功");
                    String cmd = "chmod 777 " + updateFile.getPath();
                    try {
                        Runtime.getRuntime().exec(cmd);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Uri uri = Uri.fromFile(updateFile);
                    //安裝程序
                    Intent installIntent = new Intent(Intent.ACTION_VIEW);
                    installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    installIntent.setDataAndType(uri,
                            "application/vnd.android.package-archive");
                    updatePendingIntent = PendingIntent.getActivity(
                            UpdateService.this, 0, installIntent, 0);
                    updateNotification.contentIntent = updatePendingIntent;
                    updateNotification.contentView.setTextViewText(
                            R.id.download_notice_speed_tv,
                            getString(R.string.update_notice_finish));
                    updateNotification.tickerText = appName + "下載完成";
                    updateNotification.when = System.currentTimeMillis();
                    updateNotification.defaults = Notification.DEFAULT_SOUND;
                    updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                    updateNotificationManager.notify(0, updateNotification);
                    //啓動安裝程序
                    UpdateService.this.startActivity(installIntent);
                    stopSelf();
                } else if (result == DOWNLOAD_NOMEMORY) {
                    //如果內存有問題
                    updateNotification.tickerText = appName + "下載失敗";
                    updateNotification.when = System.currentTimeMillis();
                    updateNotification.contentView.setTextViewText(
                            R.id.download_notice_speed_tv,
                            getString(R.string.update_notice_nomemory));
                    updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                    updateNotification.defaults = Notification.DEFAULT_SOUND;
                    updateNotificationManager.notify(0, updateNotification);
                    stopSelf();
                } else if (result == DOWNLOAD_FAIL) {
                    //下載失敗
                    updateNotification.tickerText = appName + "下載失敗";
                    updateNotification.when = System.currentTimeMillis();
                    updateNotification.contentView.setTextViewText(
                            R.id.download_notice_speed_tv,
                            getString(R.string.update_notice_error));
                    updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                    updateNotification.defaults = Notification.DEFAULT_SOUND;
                    updateNotificationManager.notify(0, updateNotification);
                    stopSelf();
                }
            }
    
        }
    
        /**
         * 下載更新程序文件
         * @param downloadUrl   下載地址
         * @return
         */
        private int downloadUpdateFile(String downloadUrl) {
            int count = 0;
            long totalSize = 0;   //總大小
            long downloadSize = 0;   //下載的大小
            URI uri = null;
    
            //這個已經捨棄了,要用的話,就要加上org.apache.http.legacy.jar這個jar包
            HttpGet httpGet = null;
            try {
                uri = new URI(downloadUrl);
                httpGet = new HttpGet(uri);
            } catch (URISyntaxException e) {
                String encodedUrl = downloadUrl.replace(' ', '+');
                httpGet = new HttpGet(encodedUrl);
                e.printStackTrace();
            }
            HttpClient httpClient = new DefaultHttpClient();
            HttpResponse httpResponse = null;
            FileOutputStream fos = null;
            InputStream is = null;
            try {
                httpResponse = httpClient.execute(httpGet);
                if (httpResponse != null) {
                    int stateCode = httpResponse.getStatusLine().getStatusCode();
                    if (stateCode == HttpStatus.SC_OK) {
                        HttpEntity entity = httpResponse.getEntity();
                        if (entity != null) {
                            totalSize = entity.getContentLength();
                            //如果內存可用
                            if (MemoryAvailable(totalSize)) {
                                is = entity.getContent();
                                if (is != null) {
                                    fos = new FileOutputStream(updateFile, false);
                                    byte buffer[] = new byte[4096];
                                    int readsize = 0;
                                    while ((readsize = is.read(buffer)) > 0) {
                                        fos.write(buffer, 0, readsize);
                                        downloadSize += readsize;
                                        if ((count == 0)
                                                || (int) (downloadSize * 100 / totalSize) >= count) {
                                            count += 5;
                                            updateNotification.contentView
                                                    .setTextViewText(
                                                            R.id.download_notice_speed_tv,
                                                            getMsgSpeed(downloadSize,totalSize));
                                            updateNotificationManager.notify(0,
                                                    updateNotification);
                                        }
                                    }
                                    fos.flush();
                                    if (totalSize >= downloadSize) {
                                        return DOWNLOAD_COMPLETE;
                                    } else {
                                        return DOWNLOAD_FAIL;
                                    }
                                }
                            } else {
                                if (httpGet != null) {
                                    httpGet.abort();
                                }
                                return DOWNLOAD_NOMEMORY;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fos != null) {
                        fos.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (httpClient != null) {
                    httpClient.getConnectionManager().shutdown();
                }
            }
            return DOWNLOAD_FAIL;
        }
    
        /**
         * 可用內存大小
         * @param fileSize
         * @return
         */
        private boolean MemoryAvailable(long fileSize) {
            fileSize += (1024 << 10);
            if (MemoryStatus.externalMemoryAvailable()) {
                if ((MemoryStatus.getAvailableExternalMemorySize() <= fileSize)) {
                    if ((MemoryStatus.getAvailableInternalMemorySize() > fileSize)) {
                        createFile(false);
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    createFile(true);
                    return true;
                }
            } else {
                if (MemoryStatus.getAvailableInternalMemorySize() <= fileSize) {
                    return false;
                } else {
                    createFile(false);
                    return true;
                }
            }
        }
    
        /**
         * 獲取下載進度
         * @param downSize
         * @param allSize
         * @return
         */
        public static String getMsgSpeed(long downSize, long allSize) {
            StringBuffer sBuf = new StringBuffer();
            sBuf.append(getSize(downSize));
            sBuf.append("/");
            sBuf.append(getSize(allSize));
            sBuf.append(" ");
            sBuf.append(getPercentSize(downSize, allSize));
            return sBuf.toString();
        }
    
        /**
         * 獲取大小
         * @param size
         * @return
         */
        public static String getSize(long size) {
            if (size >= 0 && size < SIZE_BT) {
                return (double) (Math.round(size * 10) / 10.0) + "B";
            } else if (size >= SIZE_BT && size < SIZE_KB) {
                return (double) (Math.round((size / SIZE_BT) * 10) / 10.0) + "KB";
            } else if (size >= SIZE_KB && size < SIZE_MB) {
                return (double) (Math.round((size / SIZE_KB) * 10) / 10.0) + "MB";
            }
            return "";
        }
    
        /**
         * 獲取到當前的下載百分比
         * @param downSize   下載大小
         * @param allSize    總共大小
         * @return
         */
        public static String getPercentSize(long downSize, long allSize) {
            String percent = (allSize == 0 ? "0.0" : new DecimalFormat("0.0")
                    .format((double) downSize / (double) allSize * 100));
            return "(" + percent + "%)";
        }
    
    
        /**
         * 創建file文件
         * @param sd_available    sdcard是否可用
         */
        private void createFile(boolean sd_available) {
            if (sd_available) {
                updateDir = new File(Environment.getExternalStorageDirectory(),
                        UpdateInformation.downloadDir);
            } else {
                updateDir = getFilesDir();
            }
            updateFile = new File(updateDir.getPath(), appName + ".apk");
            if (!updateDir.exists()) {
                updateDir.mkdirs();
            }
            if (!updateFile.exists()) {
                try {
                    updateFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                updateFile.delete();
                try {
                    updateFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    這個時候,可能看到服務怎麼這麼多代碼啊,我頭都大了,不要着急,我們一步一步說明一下,這裏邏輯很簡單,就是在通知欄中,用到了通知,這個時候我們有三種情況,造成了我們好多代碼的重複,(你也可以不必考慮那麼多情況),還有,裏面有了幾個工具類,沒有提取出來,分別是獲取sdcard大小是否可用(創建文件夾),獲取當前下載進度,獲取應用大小,下載文件,這裏也可以使用第三方框架來下載。
    當然了哈,這裏我寫的還是有點問題的,每次進入都會提示,如果有必要,也可以實現是否要自動更新,用服務,也就是點擊是否自動更新,如果不是自動更新,就不會去觸發服務端接口信息,如果是自動更新,就去觸發,來獲取最新的app版本。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章