在應用中更新App版本

jopen 8年前發布 | 10K 次閱讀 Android開發 移動開發

歡迎Follow我的GitHub, 關注我的簡書.

在應用中, 為了提高用戶體驗, 會提供更新版本的功能. 那么如何實現呢? 我寫了一個簡單的Demo, 說明一下, 需要注意幾個細節. 使用了Retrofit和Rx處理網絡請求.

Github下載地址


更新
</div>

1. 邏輯

訪問服務器, 根據是否包含新版本, 判斷是否需要更新.
下載Apk, 下載完成后, 自動安裝, 高版本會覆蓋低版本.

邏輯:

public class MainActivity extends AppCompatActivity {

private static final String APP_NAME = "Ped_android";
private static final String VERSION = "1.0.0";
private static final String INFO_NAME = "計步器";
private static final String STORE_APK = "chunyu_apk";

@Bind(R.id.main_b_install_apk) Button mBInstallApk;

private UpdateAppUtils.UpdateCallback mUpdateCallback; // 更新回調

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    mUpdateCallback = new UpdateAppUtils.UpdateCallback() {
        @Override public void onSuccess(UpdateInfo updateInfo) {
            Toast.makeText(MainActivity.this, "有更新", Toast.LENGTH_SHORT).show();
            UpdateAppUtils.downloadApk(MainActivity.this, updateInfo, INFO_NAME, STORE_APK);
        }

        @Override public void onError() {
            Toast.makeText(MainActivity.this, "無更新", Toast.LENGTH_SHORT).show();
        }
    };

    mBInstallApk.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            UpdateAppUtils.checkUpdate(APP_NAME, VERSION, mUpdateCallback);
        }
    });
}

}</pre>

UpdateAppUtils是核心下載類. 輸入App的代號, 版本號, 異步回調, 發送到服務器, 判斷是否需要更新. 如果存在新版本, 則下載Apk, 并自動安裝更新.

2. 網絡請求

更新請求, 參數是App代號和當前版本號.

/**

  • 更新服務
  • <p>
  • Created by wangchenlong on 16/1/4. */ public interface UpdateService { String ENDPOINT = "http://www.chunyuyisheng.com&quot;;

    // 獲取個人信息 @GET("/cmsapi/app/update") Observable<UpdateInfo> getUpdateInfo(

         @Query("appName") String appName,
         @Query("version") String version);
    

    }</pre>

    創建服務的工廠類.

    /**

  • 創建Retrofit服務
  • <p>
  • Created by wangchenlong on 16/1/4. */ public class ServiceFactory { public static <T> T createServiceFrom(final Class<T> serviceClass, String endpoint) {
     Retrofit adapter = new Retrofit.Builder()
             .baseUrl(endpoint)
             .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx適配器
             .addConverterFactory(GsonConverterFactory.create()) // 添加Gson轉換器
             .build();
     return adapter.create(serviceClass);
    
    } }</pre>

    更新信息的Json類.

    /**
  • 更新信息(JSON)
  • <p>
  • Created by wangchenlong on 16/1/4. */ public class UpdateInfo { public Data data; // 信息 public Integer error_code; // 錯誤代碼 public String error_msg; // 錯誤信息

    public static class Data {

     public String curVersion; // 當前版本
     public String appURL; // 下載地址
     public String description; // 描述
     public String minVersion; // 最低版本
     public String appName; // 應用名稱
    

    }

    @Override public String toString() {

     return "當前版本: " + data.curVersion + ", 下載地址: " + data.appURL + ", 描述信息: " + data.description
             + ", 最低版本: " + data.minVersion + ", 應用代稱: " + data.appName
             + ", 錯誤代碼: " + error_code + ", 錯誤信息: " + error_msg;
    

    } }</pre>

    3. 請求和下載

    更新庫的主類, 包含檢查更新(checkUpdate)下載Apk(downloadApk)兩個重要方法.

    /**

  • 更新管理器
  • <p>
  • Created by wangchenlong on 16/1/6. */ @SuppressWarnings("unused") public class UpdateAppUtils {

    @SuppressWarnings("unused") private static final String TAG = "DEBUG-WCL: " + UpdateAppUtils.class.getSimpleName();

    /**

    • 檢查更新 */ @SuppressWarnings("unused") public static void checkUpdate(String appCode, String curVersion, UpdateCallback updateCallback) { UpdateService updateService =

           ServiceFactory.createServiceFrom(UpdateService.class, UpdateService.ENDPOINT);
      
      

      updateService.getUpdateInfo(appCode, curVersion)

           .subscribeOn(Schedulers.newThread())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(updateInfo -> onNext(updateInfo, updateCallback),
                   throwable -> onError(throwable, updateCallback));
      

      }

      // 顯示信息 private static void onNext(UpdateInfo updateInfo, UpdateCallback updateCallback) { Log.e(TAG, "返回數據: " + updateInfo.toString()); if (updateInfo.error_code != 0 || updateInfo.data == null ||

           updateInfo.data.appURL == null) {
       updateCallback.onError(); // 失敗
      

      } else {

       updateCallback.onSuccess(updateInfo);
      

      } }

      // 錯誤信息 private static void onError(Throwable throwable, UpdateCallback updateCallback) { updateCallback.onError(); }

      /**

    • 下載Apk, 并設置Apk地址,
    • 默認位置: /storage/sdcard0/Download *
    • @param context 上下文
    • @param updateInfo 更新信息
    • @param infoName 通知名稱
    • @param storeApk 存儲的Apk */ @SuppressWarnings("unused") public static void downloadApk(

       Context context, UpdateInfo updateInfo,
       String infoName, String storeApk
      

      ) { if (!isDownloadManagerAvailable()) {

       return;
      

      }

      String description = updateInfo.data.description; String appUrl = updateInfo.data.appURL;

      if (appUrl == null || appUrl.isEmpty()) {

       Log.e(TAG, "請填寫\"App下載地址\"");
       return;
      

      }

      appUrl = appUrl.trim(); // 去掉首尾空格

      if (!appUrl.startsWith("http")) {

       appUrl = "http://" + appUrl; // 添加Http信息
      

      }

      Log.e(TAG, "appUrl: " + appUrl);

      DownloadManager.Request request; try {

       request = new DownloadManager.Request(Uri.parse(appUrl));
      

      } catch (Exception e) {

       e.printStackTrace();
       return;
      

      } request.setTitle(infoName); request.setDescription(description); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

       request.allowScanningByMediaScanner();
       request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
      

      } request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, storeApk);

      Context appContext = context.getApplicationContext(); DownloadManager manager = (DownloadManager)

           appContext.getSystemService(Context.DOWNLOAD_SERVICE);
      
      

      // 存儲下載Key SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(appContext); sp.edit().putLong(PrefsConsts.DOWNLOAD_APK_ID_PREFS, manager.enqueue(request)).apply(); }

      // 最小版本號大于9 private static boolean isDownloadManagerAvailable() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; }

      // 錯誤回調 public interface UpdateCallback { void onSuccess(UpdateInfo updateInfo);

      void onError(); } }</pre>

      檢查更新: 創建服務, 在新線程中發送請求, 在主線程中接收數據, 判斷成功和失敗.

          /**

    • 檢查更新 */ @SuppressWarnings("unused") public static void checkUpdate(String appCode, String curVersion, UpdateCallback updateCallback) { UpdateService updateService =

           ServiceFactory.createServiceFrom(UpdateService.class, UpdateService.ENDPOINT);
      
      

      updateService.getUpdateInfo(appCode, curVersion)

           .subscribeOn(Schedulers.newThread())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(updateInfo -> onNext(updateInfo, updateCallback),
                   throwable -> onError(throwable, updateCallback));
      

      }</pre>

      下載Apk: 轉換和解析Url, 設置通知信息和存儲位置, 存儲下載Id, 自動安裝更新.

          /**

    • 下載Apk, 并設置Apk地址,
    • 默認位置: /storage/sdcard0/Download *
    • @param context 上下文
    • @param updateInfo 更新信息
    • @param infoName 通知名稱
    • @param storeApk 存儲的Apk */ @SuppressWarnings("unused") public static void downloadApk(

       Context context, UpdateInfo updateInfo,
       String infoName, String storeApk
      

      ) { if (!isDownloadManagerAvailable()) {

       return;
      

      }

      String description = updateInfo.data.description; String appUrl = updateInfo.data.appURL;

      if (appUrl == null || appUrl.isEmpty()) {

       Log.e(TAG, "請填寫\"App下載地址\"");
       return;
      

      }

      appUrl = appUrl.trim(); // 去掉首尾空格

      if (!appUrl.startsWith("http")) {

       appUrl = "http://" + appUrl; // 添加Http信息
      

      }

      Log.e(TAG, "appUrl: " + appUrl);

      DownloadManager.Request request; try {

       request = new DownloadManager.Request(Uri.parse(appUrl));
      

      } catch (Exception e) {

       e.printStackTrace();
       return;
      

      } request.setTitle(infoName); request.setDescription(description); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

       request.allowScanningByMediaScanner();
       request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
      

      } request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, storeApk);

      Context appContext = context.getApplicationContext(); DownloadManager manager = (DownloadManager)

           appContext.getSystemService(Context.DOWNLOAD_SERVICE);
      
      

      // 存儲下載Key SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(appContext); sp.edit().putLong(PrefsConsts.DOWNLOAD_APK_ID_PREFS, manager.enqueue(request)).apply(); }</pre>

      使用DownloadManager下載文件是Android的推薦方式.
      存儲下載Id(manager.enqueue(request))是為了在安裝應用時, 找到Apk.
      默認存儲地址/storage/sdcard0/Download.

      4.自動安裝

      注冊廣播接收器, 接收消息ACTION_DOWNLOAD_COMPLETE, 下載完成會發送廣播. 獲取下載文件的Uri, 進行匹配, 發送安裝消息, 自動安裝.

      /**

  • 安裝下載接收器
  • <p>
  • Created by wangchenlong on 16/1/5. */ public class InstallReceiver extends BroadcastReceiver {

    private static final String TAG =

         "DEBUG-WCL: " + InstallReceiver.class.getSimpleName();
    
    

    // 安裝下載接收器 @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 id = sp.getLong(PrefsConsts.DOWNLOAD_APK_ID_PREFS, -1L);
    
     if (downloadApkId == id) {
         DownloadManager dManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
         Intent install = new Intent(Intent.ACTION_VIEW);
         Uri downloadFileUri = dManager.getUriForDownloadedFile(downloadApkId);
         if (downloadFileUri != null) {
             install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
             install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             context.startActivity(install);
         } else {
             Log.e(TAG, "下載失敗");
         }
     }
    

    } }</pre>

    安裝本應用下載的Apk, 不安裝其他Apk, 存儲下載Id, 與廣播Id進行匹配.
    下載失敗, 也會發送下載完成(ACTION_DOWNLOAD_COMPLETE)廣播, Uri可能為空, 需要判斷, 否則發生崩潰.

    OK, that's all! Enjoy It!

    來自: http://www.jianshu.com/p/5a6501a934cb

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!