1. 程式人生 > >Android實現在線更新的過程案例

Android實現在線更新的過程案例

客戶端 cati 重新 2.0 對話框 environ lis exc root

一、更新軟件的準備
在線更新軟件的話需要我們有簽名的應用,我們需要把簽過名之後的軟件放入到服務器中,我的如下:
技術分享圖片
其中apk是有簽名的更新版本!
updateinfo.html代碼如下:

{"version":"2.0","description":"有全新版本,請下載!","apkurl":"hhtp://172.23.252.89:8080/MobileSafe2.0.apk"}
  • 1

二、具體客戶端軟件的實現

項目結構如下:

技術分享圖片

主要的業務邏輯在這裏SplashActivity.java

package com.xuliugen.mobilesafe;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import net.tsz.afinal.FinalHttp;
import net.tsz.afinal.http.AjaxCallBack;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.TextView;
import android.widget.Toast;

/**
 * splash界面的作用 
 * 1、用來展現產品的Logo; 
 * 2、應用程序初始化的操作; 
 * 3、檢查應用程序的版本; 
 * 4、檢查當前應用程序是否合法註冊;
 * 
 * 
 * 更新安裝的話要使用簽名
 * @author xuliugen
 * 
 */
public class SplashActivity extends Activity {


    protected static final int SHOW_UPDATE_DIALOG = 0;
    protected static final int ENTER_HOME = 1;
    protected static final int URL_ERROR = 2;
    protected static final int NETWORK_ERROR = 3;
    protected static final int JSON_ERROR = 40;

    private TextView tv_splash_version;
    private TextView tv_update_info;//升級進度

    private String description;// 版本信息的描述
    private String apkurl;// 版本更新的地址

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        tv_splash_version = (TextView) this.findViewById(R.id.tv_splash_version);
        tv_update_info = (TextView) findViewById(R.id.tv_update_info);
        tv_splash_version.setText("版本號:" + getVersionName());

        // 檢查更新
        checkUpdate();

        // 設置動畫
        AlphaAnimation alphaAnimation = new AlphaAnimation(0.2f, 1.0f);
        alphaAnimation.setDuration(3000);// 設置動畫的時長

        //執行動畫
        findViewById(R.id.rl_root_splash).startAnimation(alphaAnimation);
    }

    /**
     * 由於更新界面是在主線程中操作
     * 
     * 所以可以使用handler,當子線程中運行結束的時候們可以通知主線程進行相關的操作
     */
    private Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //得到handler發送的message消息進行處理
            switch (msg.what) {
            case SHOW_UPDATE_DIALOG:// 顯示升級的對話框
                showUpdateDialog();
                break;
            case ENTER_HOME:// 進入主頁面
                enterHome();
                break;

            case URL_ERROR:// URL錯誤
                enterHome();
                Toast.makeText(getApplicationContext(), "URL錯誤", 0).show();

                break;

            case NETWORK_ERROR:// 網絡異常
                enterHome();
                Toast.makeText(SplashActivity.this, "網絡異常", 0).show();
                break;

            case JSON_ERROR:// JSON解析出錯
                enterHome();
                Toast.makeText(SplashActivity.this, "JSON解析出錯", 0).show();
                break;

            default:
                break;
            }
        }


    };
    /**
     * 檢查是否有新版本
     * 
     * 需要請求網絡,一般在子線程中使用
     */
    private void checkUpdate() {
        new Thread() {
            public void run() {
                // URL:http://172.23.252.89:8080/updateinfo.json

                Message message = Message.obtain();// 得到一個存在的信息,用於存放更新的信息

                long startTime = System.currentTimeMillis();
                try {
                    URL url = new URL(getString(R.string.serverurl));
                    //URL url = new URL("http://172.23.252.89:8080/updateinfo.json");
                    // 聯網操作
                    HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                    httpURLConnection.setRequestMethod("GET");//設置請求方式
                    httpURLConnection.setConnectTimeout(4000);//設置超時時間

                    int code = httpURLConnection.getResponseCode();// 獲得響應碼
                    if (code == 200) {// 成功
                        InputStream inputStream = httpURLConnection.getInputStream();

                        //把流轉換為一個String類型   
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        byte[] buffer = new byte[1024];
                        int len = 0;
                        while((len = inputStream.read(buffer))!=-1){
                            baos.write(buffer, 0, len);
                        }
                        inputStream.close();
                        String result = baos.toString();//得到string類型的數據
                        baos.close();

                        //因為得到的數據是一個json的string所以要json解析
                        JSONObject jsonObj = new JSONObject(result);
                        //得到服務器的版本信息
                        String version = (String) jsonObj.get("version");
                        description = (String) jsonObj.get("description");
                        apkurl = (String) jsonObj.get("apkurl");

                        //校驗是否有新版本
                        if (getVersionName().equals(version)) {// 版本一致,進入主界面
                            message.what = ENTER_HOME;
                        } else {// 有新版本,彈出一個升級對話框
                            message.what = SHOW_UPDATE_DIALOG;
                        }

                    }
                } catch (MalformedURLException e) {
                    message.what = URL_ERROR;
                    e.printStackTrace();
                } catch (IOException e) {
                    message.what = NETWORK_ERROR;
                    e.printStackTrace();
                } catch (JSONException e) {
                    message.what = JSON_ERROR;
                    e.printStackTrace();
                } finally {

                    long endTime = System.currentTimeMillis();

                    long dTime = endTime-startTime;//花費的時間
                    //在界面中停留3秒
                    if(dTime < 3000){
                        try {
                            Thread.sleep(3000-dTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    handler.sendMessage(message);// 將消息發送出去
                }
            }
        }.start();
    }

    /**
     * 得到應用層序的版本名稱
     * 
     * @return
     */
    private String getVersionName() {

        // 用於管理安裝的apk和未安裝的apk
        PackageManager packageManager = getPackageManager();

        try {
            // 得到apk的功能清單文件:為了防止出錯直接使用getPackageName()方法獲得包名
            // packageManager.getPackageInfo("com.xuliugen.mobilesafe", 0);
            PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);

            //返回版本名稱
            return packageInfo.versionName;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 彈出升級對話框
     */
    protected void showUpdateDialog() {
        AlertDialog.Builder builder = new Builder(this);
        builder.setTitle("提示升級");
//      builder.setCancelable(false);//強制升級:就是不讓用戶取消
        builder.setMessage(description);//為dialog設置信息

        builder.setOnCancelListener(new OnCancelListener() { //

            @Override
            public void onCancel(DialogInterface dialog) {
                // 進入主頁面
                enterHome();
                dialog.dismiss(); // 取消顯示對話框

            }
        });

        builder.setNegativeButton("下次再說", new OnClickListener() {// 取消

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 進入主頁面
                        enterHome();
                        dialog.dismiss(); // 取消顯示對話框
                    }
                });

        builder.setPositiveButton("立刻升級", new OnClickListener() {//確認

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // 下載APK,並且替換安裝
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//得到sd卡的狀態
                            // sdcard存在
                            // 使用afnal下載apk文件
                            FinalHttp finalhttp = new FinalHttp();
                            // 得到sdcard路徑
                            String SDCARD_PATH = Environment
                                    .getExternalStorageDirectory()
                                    .getAbsolutePath()
                                    + "/mobilesafe2.0.apk";
                            finalhttp.download(apkurl, SDCARD_PATH,
                            new AjaxCallBack<File>() {

                                @Override
                                public void onFailure(Throwable t, int errorNo,
                                        String strMsg) {
                                    t.printStackTrace();
                                    Toast.makeText(getApplicationContext(), "下載失敗", 1).show();
                                    super.onFailure(t, errorNo, strMsg);
                                }
                                /**
                                 * count:為總的大小
                                 * current:為當前下載的大小
                                 */
                                @Override
                                public void onLoading(long count, long current) {//正在下載
                                    super.onLoading(count, current);
                                    tv_update_info.setVisibility(View.VISIBLE);
                                    //當前下載百分比
                                    int progress = (int) (current * 100 / count);
                                    tv_update_info.setText("下載進度:"+progress+"%");
                                }

                                /**
                                 * file t:表示文件的路徑
                                 */
                                @Override
                                public void onSuccess(File t) {
                                    super.onSuccess(t);

                                    //成功的時候要安裝apk
                                    installAPK(t);
                                }

                                /**
                                 * 安裝APK
                                 * @param t :文件下載的路徑
                                 */
                                private void installAPK(File t) {
                                  Intent intent = new Intent();
                                  intent.setAction("android.intent.action.VIEW");
                                  intent.addCategory("android.intent.category.DEFAULT");
                                  intent.setDataAndType(Uri.fromFile(t), "application/vnd.android.package-archive");

                                  startActivity(intent);
                                }
                            });
                    } else {// sdcard不存在
                    Toast.makeText(getApplicationContext(), "沒有sdcard,請安裝上在試",0).show();
                    return;
                }

            }
        });

        builder.show();

    }

    /**
     * 進入主界面
     */
    protected void enterHome() {
        Intent intent = new Intent(this, HomeActivity.class);
        startActivity(intent);
        // 關閉當前頁面
        finish();
    }
}

其中使用的把流轉化為一個String類型的方法可以封裝為一個工具類:

StreamTools.java

package com.xuliugen.mobilesafe.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 把流轉換為一個String類型
 * 
 * @author xuliugen
 * 
 */
public class StreamTools {
    /**
     * @param is  輸入流
     * @return String 返回的字符串
     * @throws IOException
     */
    public static String readFromStream(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        is.close();
        String result = baos.toString();
        baos.close();
        return result;
    }
}

註意:我們的簽名一定要牢記,但是如果忘記也有方法解決:
(1)改簽名:這將無法覆蓋安裝,這是包名不變的情況,這就要求用戶先卸載以前的應用,體驗不佳!
(2)改包名:重新簽名,這就相當於手機安裝兩個應用,但是可以用技術卸載第一個應用。

Android實現在線更新的過程案例