Android應用的自動更新模塊

jopen 11年前發布 | 33K 次閱讀 Android Android開發 移動開發

軟件的自動更新一般都與Splash界面綁定在一起, 由于需要維護的軟件界面很復雜, 一個Activity中嵌入ViewPager, 并且邏輯比較復雜, 索性重新寫一個Activity, 現在的軟件都很流行使用Splash界面, 正好與自動更新配套在一起;


在這個自動更新Splash中, 使用到了 動畫設置 ,SharedPerference ,pull解析 ,dialog對話框 ,http網絡編程 ,handler 等.


注意一個錯誤已安裝具有該名稱和不同簽名的數據包 , 早上測試人員報告突然出現這個問題, 在開發的時候我直接將eclipse上編譯的版本放到了服務器上, 最后出現了這個問題, 開發的時候明明是好的啊, 怎么測試的時候出問題了呢.

編譯環境不同, 產生的簽名是不一樣的, 在eclipse上編譯生成 與 正式版本在linux下編譯 所產生的 數字簽名 是不一樣的.


一. 創建Activity

 

1. 創建Activity大概流程

a. 設置全屏顯示.

b. 設置布局, 并在布局中顯示當前版本號, 為Splash界面添加動畫.

c. 獲取當前時間.

d. 獲取SharedPerence配置文件.

e. 開啟檢查版本號線程, 后續的操作都在這個線程中執行.

 

2. 設置窗口樣式

 

(1) 設置全屏顯示

a. 代碼實現 : 由于是Splash界面, 這里需要設置成無標題, 并且全屏顯示, 注意下面的兩行代碼需要在setContentView()方法之前調用;

        //隱藏標題欄
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //隱藏狀態欄
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

b. 配置實現

<pre code_snippet_id="84549" snippet_file_name="blog_20131127_2_1903065" name="code" class="java">AndroidManifest.xml <activity android:name="myAcitivty"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /></pre>


(2) 關于窗口的其它設置

//①設置窗體始終點亮
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


<pre code_snippet_id="84549" snippet_file_name="blog_20131127_4_9427046" name="code" class="java">//②設置窗體始終點亮 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);</pre>


設置窗體始終點亮的配置文件實現

<pre code_snippet_id="84549" snippet_file_name="blog_20131127_5_3745445" name="code" class="java">//③AndroidManifest.xml添加權限 <uses-permission android:name="android.permission.WAKE_LOCK" /></pre>

<pre code_snippet_id="84549" snippet_file_name="blog_20131127_6_572099" name="code" class="java">//設置窗體背景模糊 getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);</pre>

(3) 屏幕方向設置


a. 配置文件實現

/設置橫屏
<activity android:name="myAcitivty"  android:screenOrientation="landscape" />

//設置豎屏 <activity android:name="myAcitivty" android:screenOrientation="portrait" /> </pre>


b. 代碼實現

//設置橫屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

//設置豎屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);</pre>

 

c. 獲取屏幕方向

//獲取橫屏方向
int orientation = this.getResources().getConfiguration().orientation;
其中的orientation方向可以使 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 或者 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE .

 

3. 設置動畫

 

為了更好的用戶體驗, 這里給Splash界面添加一個動畫, 這個動畫加給整個界面.

 

(1) 創建動畫


AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);    //創建動畫
animation.setDuration(2000); //設置漸變
splash_rl.setAnimation(animation);   //設置動畫載體
創建動畫吧: 創建的這個動畫是透明度漸變動畫, 傳入浮點型參數, 0代表完全透明, 1代表不透明, 傳入參數代表透明度從完全透明到不透明.

設置時間 : 設置的duration是動畫漸變過程所消耗的時間.

設置動畫 : 最后使用setAnimation()方法將穿件的動畫設置給Splash界面.


(2) 動畫常用方法

 

a. 普通設置 


<pre code_snippet_id="84549" snippet_file_name="blog_20131127_11_9066409" name="code" class="java"> alphaAnimation.setRepeatCount(5);//設置重復次數 alphaAnimation.setFillAfter(true);//動畫執行完是否停留在執行完的狀態 alphaAnimation.setStartOffset(1000);//動畫執行前等待的時間, 單位是毫秒 alphaAnimation.start();//開始動畫</pre>


b. 設置監聽器


alphaAnimation.setAnimationListener(new AnimationListener() {
            //動畫開始時回調
            @Override
            public void onAnimationStart(Animation animation) {
            }
            //動畫重復執行時回調
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
            //動畫執行結束時回調
            @Override
            public void onAnimationEnd(Animation animation) {
            }
        });

 

4. SharedPerference使用

 

       //獲取SharedPerference
        SharedPreferences sharedPreferences = getSharedPreferences("sp", Context.MODE_PRIVATE);

    Editor editor = sharedPreferences.edit();   //獲取Editor對象
    editor.putBoolean("isUpdate", true);        //向sp中寫入數據
    editor.commit();                            //提交

    sharedPreferences.getBoolean("isUpdate", true);//獲取sp中的變量</pre><br />


5. onCreate()方法代碼 


/**

 * 創建Activity時調用
 * 
 * ① 設置全屏顯示, 由于是Splash界面, 因此不能有標題
 * ② 設置布局, 版本號, 執行動畫 
 * ③ 設置當前時間
 * ④ 獲取SharedPerference配置文件
 * ⑤ 開啟檢查版本號線程, 后續操作都在改線程中操作
 * 
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //隱藏標題欄
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    //隱藏狀態欄
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    //設置布局
    setContentView(R.layout.splash);

    /*
     *  顯示當前軟件的版本號
     *  獲取布局中的TextView控件, 將版本號設置到這個TextView控件中
     */
    tv_version = (TextView) findViewById(R.id.tv_version);
    version =getString(R.string.current_version) + " " + getVersion();
    tv_version.setText(version);

    /*
     *  在界面設置一個動畫, 用來表明正在運行
     *  a. 獲取布局
     *  b. 創建一個動畫對象
     *  c. 將動畫設置到布局中
     */
    splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
    AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
    animation.setDuration(2000);
    splash_rl.setAnimation(animation);

    /*
     * 這個時間值是用來控制Splash界面顯示時間的
     * 記錄下這個值, 然后執行到下面, 如果時間差在3秒以內, 
     * 就執行下面的操作, 如果時間差不足3秒, 就Thread.sleep時間差
     * 等夠3秒在執行下面的操作
     */
    time = System.currentTimeMillis();

    //從SharedPreference中獲取一些配置
    sp = getSharedPreferences("config", Context.MODE_PRIVATE);

    //開啟檢查版本號線程
    new Thread(new CheckVersionTask()).start();
}</pre><br />



二. 檢查版本號

 

1. 檢查版本號線程

流程 : 

a. 保持Splash持續時間 : 獲取當前時間與time進行比較, 如果不足3秒, 人為使Splash保持3秒時間;

b. 查看更新設置 : 從sp中獲取更新設置, 如果sp中自動更新為true, 那么就執行下面的更新流程, 如果sp中自動更新為false, 那么直接進入主界面.

c. 獲取信息 : 從網絡中獲取更新信息, 根據是否成功獲取信息執行不同的操作.


源碼 : 

 private final class CheckVersionTask implements Runnable{
        public void run() {
            try {
                /*

             * 獲取當前時間, 與onCreate方法中獲取的時間進行比較
             * 如果不足3秒, 在等待夠3秒之后在執行下面的操作
             */
            long temp = System.currentTimeMillis();
            if(temp - time < 3000){
                SystemClock.sleep(temp - time);
            }

            /*
             * 檢查配置文件中的設置, 是否設置了自動更新; 
             * 如果設置了自動更新, 就執行下面的操作,
             * 如果沒有設置自動更新, 就直接進入主界面
             */
            boolean is_auto_update = sp.getBoolean("is_auto_update", true);
            if(!is_auto_update){
                loadMainUI();
                return;
            }

            /*
             * 獲取更新信息
             * 如果信息不為null, 向handler發信息SUCESS_GET_UPDATEINOF, 執行后續操作
             * 如果信息為null, 向handler發信息ERROR_GET_UPDATEINOF, 執行后續操作
             * 如果出現異常, 向handler發信息ERROR_GET_UPDATEINOF, 執行后續操作
             */
            updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
            if(updateInfo != null){
                Message msg = new Message();
                msg.what = SUCESS_GET_UPDATEINOF;
                mHandler.sendMessage(msg);
            }else{
                Message msg = new Message();
                msg.what = ERROR_GET_UPDATEINOF;
                mHandler.sendMessage(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Message msg = new Message();
            msg.what = ERROR_GET_UPDATEINOF;
            mHandler.sendMessage(msg);
        }
    }
}</pre><br />


2. 獲取版本號方法

 

流程 : 

a. 創URL建對象;

b. 創建HttpURLConnection對象;

c. 設置超時時間;

d. 設置獲取方式;

e. 查看鏈接是否成功;

f. 解析輸入流信息;


源碼 : 

 /**

 * 獲取更新信息
 *      ① 根據字符串地址創建URL對象
 *      ② 根據URL對象創建HttpURLConnection鏈接對象
 *      ③ 設置鏈接對象5秒超時
 *      ④ 設置鏈接對象獲取的方式為get方式
 *      ⑤ 如果成功連接, conn.getRequestCode值就是200, 此時就可以獲取輸入流
 *      ⑥ 解析輸入流獲取更新信息
 *      
 */
private UpdateInfo getUpdateInfo(String path){
    try {
        URL url = new URL(path);    //創建URL對象
        //創建連接對象
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //設置鏈接超時
        conn.setConnectTimeout(5000);
        //設置獲取方式
        conn.setRequestMethod("GET");
        //如果連接成功, 獲取輸入流
        if(conn.getResponseCode() == 200){
            InputStream is = conn.getInputStream();
            //解析輸入流中的數據, 返回更新信息
            return parserUpdateInfo(is);
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}</pre><br />

3. 更新信息對象


將從網上獲取的更新信息 包括 版本號, apk文件地址, 軟件描述等信息封裝在一個類中.


 public class UpdateInfo {
        private String version; //當前軟件版本號
        private String url;     //獲取到的軟件地址
        private String description; //軟件描述

    public String getVersion() {
        return version;
    }
    public void setVersion(String version) {
        this.version = version;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    @Override
    public String toString() {
        return "UpdateInfo [version=" + version + ", url=" + url
                + ", description=" + description + "]";
    }
}</pre><br />

4. pull解析輸入流

(1) pull解析流程


a. 獲取pull解析器 : XmlPullParser parser = Xml.newPullParser();

b. 為pull解析器設置編碼 : parser.setInput(inputStream, "UTF-8");

c. 獲取pull解析器事件 : int eventType = parser.getEventType(), 之后的解析都要根據這個解析事件進行, 例如開始解析標簽的事件時 XmlPullParser.START_TAG, 文檔結束的事件時 XmlPullParser.END_DOCUMENT.

d. 解析流程控制 : 解析的時候, 如果沒有解析到文檔最后就一直解析, 這里使用while循環, eventType != XmlPullParser.END_DOCUMENT 就一直循環, 循環玩一個元素之后, 調用parser.next()遍歷下一個元素.

e. 獲取標簽名 : 在事件解析標簽的時候 ( eventType == XmlPullParser.START_TAG ) , 調用parser.getName()可以獲取這個標簽的標簽名, 如果我們想要獲取這個標簽下的文本元素, 可以使用parser.nextText()來獲取. 


(2) 更新xml文件


<?xml version="1.0" encoding="UTF-8"?>
<updateInfo>
  <version>3.2</version>
  <url>http://127.0.0.1:8080/web/mobilesafe.apk</url>
  <description>客戶端更新</description>
</updateInfo>

(3) 源碼


 /**

 * 獲取更新信息
 *      ① 創建pull解析器
 *      ② 為解析器設置編碼格式
 *      ③ 獲取解析事件
 *      ④ 遍歷整個xml文件節點, 獲取標簽元素內容
 */
private UpdateInfo parserUpdateInfo(InputStream is){
    try {
        UpdateInfo updateInfo = null;
        //1. 創建pull解析解析器
        XmlPullParser parser = Xml.newPullParser();
        //2. 設置解析編碼
        parser.setInput(is, "UTF-8");
        //3. 獲取解析器解事件, 如解析到文檔開始 , 結尾, 標簽等
        int eventType = parser.getEventType();
        //4. 在文檔結束前一直解析
        while (eventType != XmlPullParser.END_DOCUMENT) {
            switch (eventType) {
            //只解析標簽
            case XmlPullParser.START_TAG:
                if ("updateInfo".equals(parser.getName())) {
                    //當解析到updateInfo標簽的時候, 跟標簽開始, 創建一個UpdateInfo對象
                    updateInfo = new UpdateInfo();
                } else if ("version".equals(parser.getName())) {
                    //解析版本號標簽
                    updateInfo.setVersion(parser.nextText());
                } else if ("url".equals(parser.getName())) {
                    //解析url標簽
                    updateInfo.setUrl(parser.nextText());
                } else if ("description".equals(parser.getName())) {
                    //解析描述標簽
                    updateInfo.setDescription(parser.nextText());
                }
                break;
            default:
                break;
            }
            //每解析完一個元素, 就將解析標志位下移
            eventType = parser.next();
        }
        is.close();
        return updateInfo;
    } catch (XmlPullParserException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}</pre> <p> </p>

三. Handler對象


Handler對象用來控制整個更新過程的進行;

 private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            /*

         * 獲取更新信息錯誤 , 在斷網或者獲取信息出現異常執行
         * 提示一下, 之后進入主界面
         */
        case ERROR_GET_UPDATEINOF:
            ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
            loadMainUI();
            break;
        /*
         * 成功獲取更新信息, 一般在成功從網上獲取xml文件并解析出來
         * 如果版本號相同, 說明不用更新, 直接進入主界面
         * 如果版本號不同, 需要彈出更新對話框
         */
        case SUCESS_GET_UPDATEINOF:
            if(updateInfo.getVersion().equals(version)){
                loadMainUI();
            }else{
                showUpdateDialog();
            }
            break;
        /*
         * 下載apk文件出現錯誤, 中途斷網 出現異常等情況
         * 提示后進入主界面
         */
        case ERROR_DOWNLOAD_APK:
            mPb.dismiss();
            ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
            loadMainUI();
            break;
        /*
         * 成功下載apk文件之后執行的操作
         * 取消進度條對話框, 之后安裝apk文件
         */
        case SUCCESS_DOWNLOAD_APK:
            mPb.dismiss();
            installApk();
            break;
        default:
            break;
        }
    };
};</pre><br />

四. 下載安裝apk文件

1. 更新對話框

 

(1) 更新流程


先彈出更新對話框提示, 點擊確定就彈出進度條對話框, 下載apk文件 . 如果點擊取消, 直接進入主界面


更新對話框 : 這是一個AlertDialog , 先創建builder, 然后設置標題, 顯示內容, 設置積極消極按鈕, 創建對話框 之后顯示對話框;

進度條對話框 : 這是一個ProgressDialog, 直接使用new創建, 設置信息與顯示樣式, 最后顯示對話框.


(2) 創建對話框流程


創建一個對話框的流程 : 

a. 創建builder對象 : Builder builder = new Builder(context);

b. 設置標題 : builder.setTittle("");

c. 設置顯示信息 : builder.setMessage("");

d. 設置按鈕 : builder.setPositiveButton("", onClickListener);

e. 創建對話框 : Dialog dialog = builder.create();

f. 顯示對話框 : dialog.show();

 

創建進度條對話框流程 : 

a. 創建進度條對話框 : ProgressDialog progressDialog = new ProgressDialog(context);

b. 設置進度條對話框樣式 : progressDialog.setProgressStyle();

c. 設置顯示信息 : progressDialog.setMessage();

d. 顯示對話框 : progressDialog.show();


(3) 源碼 


 /**

 * 彈出更新對話框
 * 
 * a. 創建builder對象
 * b. 設置標題
 * c. 設置對話框顯示信息
 * d. 設置該對話框不可回退, 如果回退的話就會卡在本界面
 * e. 設置確定按鈕
 * f. 設置取消按鈕
 * g. 創建對話框
 * h. 顯示對話框
 * 
 * 確定按鈕按下顯示進度條對話框
 * a. 創建一個進度條對話框
 * b. 設置該對話框不能回退
 * c. 設置進度條樣式
 * d. 設置進度條的信息
 * e. 顯示進度條對話框
 * f. 開啟一個線程, 下載apk文件
 */
protected void showUpdateDialog() {
    //創建builder對象
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    //設置標題
    builder.setTitle(getString(R.string.update_dialog_tittle));
    //設置對話框信息
    builder.setMessage(updateInfo.getDescription());
    //設置不可回退
    builder.setCancelable(false);
    //設置確定按鈕
    builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            //創建進度條對話框
            mPb = new ProgressDialog(SplashActivity.this);
            //設置進度條對話框不可回退
            mPb.setCancelable(false);
            //設置進度條對話框樣式
            mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            //設置進度條對話框的信息
            mPb.setMessage(getString(R.string.update_dialog_messsage));
            //顯示進度條對話框
            mPb.show();
            //開啟顯示進度條對話框線程
            new Thread(new DownloadApkTask()).start();
        }
    });
    builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            loadMainUI();
        }
    });
    //創建更新信息提示對話框
    mUpdateInfoDialog = builder.create();
    //顯示更新信息提示對話框
    mUpdateInfoDialog.show();
}</pre><br />

2. 下載apk線程


 /**

 * 在這個線程中主要執行downloadApk方法, 這個方法傳入apk路徑和進度條對話框
 * 注意 : 下載的前提是sd卡的狀態是掛載的
 */
private final class DownloadApkTask implements Runnable{
    public void run() {
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            try {
                SystemClock.sleep(2000);
                apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);
                Message msg = new Message();
                msg.what = SUCCESS_DOWNLOAD_APK;
                mHandler.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
                Message msg = new Message();
                msg.what = ERROR_DOWNLOAD_APK;
                mHandler.sendMessage(msg);
            }
        }
    }
}</pre><br />

3. 下載apk核心方法

 

從網絡下載文件流程

a. 創建URL對象 : 這個對象一般根據字符串地址創建, URL url = new URL(path);

b. 創建HttpURLConnection對象 : 這個對象根據URL對象創建, HttpURLConnection conn = (HttpURLConnection)url.openConnection();

c. 設置超時時間 : 單位是毫秒, conn.setConnectionTimeout(5000);

d. 設置請求方式 : conn.setRequestMethod("GET");

e. 成功連接 : 如果成功連接, 那么conn.getResponseCode()的值為200;


進度條對話框設置

a. 設置進度條最大值 : mProgressDialog.setMax(int max);

b. 設置進度條當前值 : mProgressDialog.setProgress(int curr);

 

 /**

 * 下載apk更新文件
 *  
 * a. 根據SD卡路徑創建文件對象, 這個文件用來保存下載的文件
 * b. 創建URL對象
 * c. 創建HttpUrlConnection對象
 * d. 設置鏈接對象超時時間
 * e. 設置請求方式 get
 * f. 如果請求成功執行下面的操作
 * 
 * g. 通過鏈接對象獲取網絡資源的大小
 * h. 將文件大小設置給進度條對話框
 * i. 獲取輸入流, 并且讀取輸入流信息
 * j. 根據讀取到的字節數, 將已經讀取的數據設置給進度條對話框
 */
public File downloadApk(String path,ProgressDialog pb) throws Exception{
    //創建本地文件對象
    File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
    //創建HttpURL連接
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");
    if(conn.getResponseCode() == 200){
        int max = conn.getContentLength();
        //設置進度條對話框的最大值
        pb.setMax(max);
        int count = 0;
        InputStream is = conn.getInputStream();
        FileOutputStream fos = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer, 0, len);
            //設置進度條對話框進度
            count = count + len;
            pb.setProgress(count);
        }
        is.close();
        fos.close();
    }
    return file;
}</pre><br />

4. 安裝apk文件


 /**

 * 安裝apk文件流程
 * 
 * a. 設置Action : Intent.ACTION_VIEW.
 * b. 設置數據和類型 : 設置apk文件的uri 和 MIME類型
 * c. 開啟安裝文件的Activity.
 */
protected void installApk() {
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
    startActivity(intent);
}</pre><br />

五. 相關的源碼 


(1) 布局文件

splash.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="

<ProgressBar android:id="@+id/pb"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="30dip"/>

<TextView android:id="@+id/tv_version"
   android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_above="@id/pb"
    android:layout_marginBottom="60dip"
    android:textSize="30sp"
    android:textColor="#17A6E8"
    android:text="version"
    />

</RelativeLayout></pre>


(2) Activity頁面切換動畫


main_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     >
    <translate
        android:fromXDelta="100%p"
        android:toXDelta="0"
        android:fromYDelta="0"
        android:toYDelta="0" 
        android:duration="200"
        />
</set>


splash_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     >
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%p"
        android:fromYDelta="0"
        android:toYDelta="0" 
        android:duration="200"
        />
</set>


(3) SplashActivity源碼


SplashActivity.java

public class SplashActivity extends Activity {

private static final String TAG = "SplashActivity";

public static final int ERROR_GET_UPDATEINOF = 0;
public static final int SUCESS_GET_UPDATEINOF = 1;
public static final int ERROR_DOWNLOAD_APK = 2;
public static final int SUCCESS_DOWNLOAD_APK = 3;

private static final String XML_FILE_DIRECTORY = "updateinfo.xml";
private static final String UPDATE_FOLDER_DIRECTORY = "/webupdate/";

private TextView tv_version;
private PackageManager pm;
private String version;
private UpdateInfo updateInfo;

private Dialog mUpdateInfoDialog;
private ProgressDialog mPb;
private File apkFile;

private RelativeLayout splash_rl;
private long time;
private SharedPreferences sp;

private Handler mHandler = new Handler(){
    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        /*
         * 獲取更新信息錯誤 , 在斷網或者獲取信息出現異常執行
         * 提示一下, 之后進入主界面
         */
        case ERROR_GET_UPDATEINOF:
            ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
            loadMainUI();
            break;
        /*
         * 成功獲取更新信息, 一般在成功從網上獲取xml文件并解析出來
         * 如果版本號相同, 說明不用更新, 直接進入主界面
         * 如果版本號不同, 需要彈出更新對話框
         */
        case SUCESS_GET_UPDATEINOF:
            if(updateInfo.getVersion().equals(version)){
                loadMainUI();
            }else{
                showUpdateDialog();
            }
            break;
        /*
         * 下載apk文件出現錯誤, 中途斷網 出現異常等情況
         * 提示后進入主界面
         */
        case ERROR_DOWNLOAD_APK:
            mPb.dismiss();
            ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
            loadMainUI();
            break;
        /*
         * 成功下載apk文件之后執行的操作
         * 取消進度條對話框, 之后安裝apk文件
         */
        case SUCCESS_DOWNLOAD_APK:
            mPb.dismiss();
            installApk();
            break;
        default:
            break;
        }
    };
};

/**
 * 創建Activity時調用
 * 
 * ① 設置全屏顯示, 由于是Splash界面, 因此不能有標題
 * ② 設置布局, 版本號, 執行動畫 
 * ③ 設置當前時間
 * ④ 獲取SharedPerference配置文件
 * ⑤ 開啟檢查版本號線程, 后續操作都在改線程中操作
 * 
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //隱藏標題欄
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    //隱藏狀態欄
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    //設置布局
    setContentView(R.layout.splash);

    /*
     *  顯示當前軟件的版本號
     *  獲取布局中的TextView控件, 將版本號設置到這個TextView控件中
     */
    tv_version = (TextView) findViewById(R.id.tv_version);
    version =getString(R.string.current_version) + " " + getVersion();
    tv_version.setText(version);

    /*
     *  在界面設置一個動畫, 用來表明正在運行
     *  a. 獲取布局
     *  b. 創建一個動畫對象
     *  c. 將動畫設置到布局中
     */
    splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
    AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
    alphaAnimation.setDuration(2000);
    splash_rl.setAnimation(alphaAnimation);

    /*
     * 這個時間值是用來控制Splash界面顯示時間的
     * 記錄下這個值, 然后執行到下面, 如果時間差在3秒以內, 
     * 就執行下面的操作, 如果時間差不足3秒, 就Thread.sleep時間差
     * 等夠3秒在執行下面的操作
     */
    time = System.currentTimeMillis();

    //從SharedPreference中獲取一些配置
    sp = getSharedPreferences("config", Context.MODE_PRIVATE);

    //開啟檢查版本號線程
    new Thread(new CheckVersionTask()).start();
}

private final class CheckVersionTask implements Runnable{
    public void run() {
        try {
            /*
             * 獲取當前時間, 與onCreate方法中獲取的時間進行比較
             * 如果不足3秒, 在等待夠3秒之后在執行下面的操作
             */
            long temp = System.currentTimeMillis();
            if(temp - time < 3000){
                SystemClock.sleep(temp - time);
            }

            /*
             * 檢查配置文件中的設置, 是否設置了自動更新; 
             * 如果設置了自動更新, 就執行下面的操作,
             * 如果沒有設置自動更新, 就直接進入主界面
             */
            boolean is_auto_update = sp.getBoolean("is_auto_update", true);
            if(!is_auto_update){
                loadMainUI();
                return;
            }

            /*
             * 獲取更新信息
             * 如果信息不為null, 向handler發信息SUCESS_GET_UPDATEINOF, 執行后續操作
             * 如果信息為null, 向handler發信息ERROR_GET_UPDATEINOF, 執行后續操作
             * 如果出現異常, 向handler發信息ERROR_GET_UPDATEINOF, 執行后續操作
             */
            updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
            if(updateInfo != null){
                Message msg = new Message();
                msg.what = SUCESS_GET_UPDATEINOF;
                mHandler.sendMessage(msg);
            }else{
                Message msg = new Message();
                msg.what = ERROR_GET_UPDATEINOF;
                mHandler.sendMessage(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Message msg = new Message();
            msg.what = ERROR_GET_UPDATEINOF;
            mHandler.sendMessage(msg);
        }
    }
}

/**
 * 安裝apk文件流程
 * 
 * a. 設置Action : Intent.ACTION_VIEW.
 * b. 設置數據和類型 : 設置apk文件的uri 和 MIME類型
 * c. 開啟安裝文件的Activity.
 */
protected void installApk() {
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
    startActivity(intent);
}

/**
 * 彈出更新對話框
 * 
 * a. 創建builder對象
 * b. 設置標題
 * c. 設置對話框顯示信息
 * d. 設置該對話框不可回退, 如果回退的話就會卡在本界面
 * e. 設置確定按鈕
 * f. 設置取消按鈕
 * g. 創建對話框
 * h. 顯示對話框
 * 
 * 確定按鈕按下顯示進度條對話框
 * a. 創建一個進度條對話框
 * b. 設置該對話框不能回退
 * c. 設置進度條樣式
 * d. 設置進度條的信息
 * e. 顯示進度條對話框
 * f. 開啟一個線程, 下載apk文件
 */
protected void showUpdateDialog() {
    //創建builder對象
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    //設置標題
    builder.setTitle(getString(R.string.update_dialog_tittle));
    //設置對話框信息
    builder.setMessage(updateInfo.getDescription());
    //設置不可回退
    builder.setCancelable(false);
    //設置確定按鈕
    builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            //創建進度條對話框
            mPb = new ProgressDialog(SplashActivity.this);
            //設置進度條對話框不可回退
            mPb.setCancelable(false);
            //設置進度條對話框樣式
            mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            //設置進度條對話框的信息
            mPb.setMessage(getString(R.string.update_dialog_messsage));
            //顯示進度條對話框
            mPb.show();
            //開啟顯示進度條對話框線程
            new Thread(new DownloadApkTask()).start();
        }
    });
    builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            loadMainUI();
        }
    });
    //創建更新信息提示對話框
    mUpdateInfoDialog = builder.create();
    //顯示更新信息提示對話框
    mUpdateInfoDialog.show();
}

/**
 * 在這個線程中主要執行downloadApk方法, 這個方法傳入apk路徑和進度條對話框
 * 注意 : 下載的前提是sd卡的狀態是掛載的
 */
private final class DownloadApkTask implements Runnable{
    public void run() {
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            try {
                SystemClock.sleep(2000);
                apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);
                Message msg = new Message();
                msg.what = SUCCESS_DOWNLOAD_APK;
                mHandler.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
                Message msg = new Message();
                msg.what = ERROR_DOWNLOAD_APK;
                mHandler.sendMessage(msg);
            }
        }
    }
}

/**
 * 下載apk更新文件
 *  
 * a. 根據SD卡路徑創建文件對象, 這個文件用來保存下載的文件
 * b. 創建URL對象
 * c. 創建HttpUrlConnection對象
 * d. 設置鏈接對象超時時間
 * e. 設置請求方式 get
 * f. 如果請求成功執行下面的操作
 * 
 * g. 通過鏈接對象獲取網絡資源的大小
 * h. 將文件大小設置給進度條對話框
 * i. 獲取輸入流, 并且讀取輸入流信息
 * j. 根據讀取到的字節數, 將已經讀取的數據設置給進度條對話框
 */
public File downloadApk(String path,ProgressDialog pb) throws Exception{
    //創建本地文件對象
    File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
    //創建HttpURL連接
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");
    if(conn.getResponseCode() == 200){
        int max = conn.getContentLength();
        //設置進度條對話框的最大值
        pb.setMax(max);
        int count = 0;
        InputStream is = conn.getInputStream();
        FileOutputStream fos = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer, 0, len);
            //設置進度條對話框進度
            count = count + len;
            pb.setProgress(count);
        }
        is.close();
        fos.close();
    }
    return file;
}

private String getFileName(String path){
    return path.substring(path.lastIndexOf("/") + 1);
}

private String getVersion() {
    try {
        pm = this.getPackageManager();
        PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);
        return packageInfo.versionName;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 獲取更新信息
 *      ① 根據字符串地址創建URL對象
 *      ② 根據URL對象創建HttpURLConnection鏈接對象
 *      ③ 設置鏈接對象5秒超時
 *      ④ 設置鏈接對象獲取的方式為get方式
 *      ⑤ 如果成功連接, conn.getRequestCode值就是200, 此時就可以獲取輸入流
 *      ⑥ 解析輸入流獲取更新信息
 *      
 */
private UpdateInfo getUpdateInfo(String path){
    try {
        URL url = new URL(path);    //創建URL對象
        //創建連接對象
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //設置鏈接超時
        conn.setConnectTimeout(5000);
        //設置獲取方式
        conn.setRequestMethod("GET");
        //如果連接成功, 獲取輸入流
        if(conn.getResponseCode() == 200){
            InputStream is = conn.getInputStream();
            //解析輸入流中的數據, 返回更新信息
            return parserUpdateInfo(is);
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 獲取更新信息
 *      ① 創建pull解析器
 *      ② 為解析器設置編碼格式
 *      ③ 獲取解析事件
 *      ④ 遍歷整個xml文件節點, 獲取標簽元素內容
 */
private UpdateInfo parserUpdateInfo(InputStream is){
    try {
        UpdateInfo updateInfo = null;
        //1. 創建pull解析解析器
        XmlPullParser parser = Xml.newPullParser();
        //2. 設置解析編碼
        parser.setInput(is, "UTF-8");
        //3. 獲取解析器解事件, 如解析到文檔開始 , 結尾, 標簽等
        int eventType = parser.getEventType();
        //4. 在文檔結束前一直解析
        while (eventType != XmlPullParser.END_DOCUMENT) {
            switch (eventType) {
            //只解析標簽
            case XmlPullParser.START_TAG:
                if ("updateInfo".equals(parser.getName())) {
                    //當解析到updateInfo標簽的時候, 跟標簽開始, 創建一個UpdateInfo對象
                    updateInfo = new UpdateInfo();
                } else if ("version".equals(parser.getName())) {
                    //解析版本號標簽
                    updateInfo.setVersion(parser.nextText());
                } else if ("url".equals(parser.getName())) {
                    //解析url標簽
                    updateInfo.setUrl(parser.nextText());
                } else if ("description".equals(parser.getName())) {
                    //解析描述標簽
                    updateInfo.setDescription(parser.nextText());
                }
                break;
            default:
                break;
            }
            //每解析完一個元素, 就將解析標志位下移
            eventType = parser.next();
        }
        is.close();
        return updateInfo;
    } catch (XmlPullParserException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

private void loadMainUI(){
    Intent intent = new Intent(this,HomeActivity.class);
    startActivity(intent);
    finish();
    overridePendingTransition(R.anim.main_in, R.anim.splash_out);
}


public class UpdateInfo {
    private String version; //當前軟件版本號
    private String url;     //獲取到的軟件地址
    private String description; //軟件描述

    public String getVersion() {
        return version;
    }
    public void setVersion(String version) {
        this.version = version;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    @Override
    public String toString() {
        return "UpdateInfo [version=" + version + ", url=" + url
                + ", description=" + description + "]";
    }
}

}</pre>

來自:
http://blog.csdn.net/shulianghan/article/details/16879203

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