Android應用的自動更新模塊

.java

做者 :萬境絕塵 linux

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835android

.web


軟件的自動更新通常都與Splash界面綁定在一塊兒, 因爲須要維護的軟件界面很複雜, 一個Activity中嵌入ViewPager, 而且邏輯比較複雜, 索性從新寫一個Activity, 如今的軟件都很流行使用Splash界面, 正好與自動更新配套在一塊兒;編程


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


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

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


又發現一個BUG : 在彈出更新對話框, 點擊肯定下載完畢以後會彈出系統自帶的替換應用程序對話框, 在這裏點取消的話就會一直卡在Splash界面. 設置一個跳起色制解決這個問題.eclipse

解決方案 :利用觸摸劃屏事件, 向左側劃屏100px, 就自動跳轉到主界面 , 最後的最終代碼已經加上去了ide

	/**
	 *	設置觸摸事件
	 *	在手指按下時記錄x座標值 , 在手指擡起的時候記錄x座標值 , 若是兩個值相差超過100
	 *	那麼跳轉到主界面 
	 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN :
				touchPositionX0 = (int) event.getX();
				break;
			case MotionEvent.ACTION_UP :
				touchPositionX1 = (int) event.getX();
				if((touchPositionX0 - touchPositionX1) > 100)
					loadMainUI();
				touchPositionX0 = 0;
				touchPositionX1 = 0;
				break;
		}
		return true;
	}




一. 建立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. 配置實現

AndroidManifest.xml
<activity 
	android:name="myAcitivty"  
	android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />


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

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


//②設置窗體始終點亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


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

//③AndroidManifest.xml添加權限
<uses-permission android:name="android.permission.WAKE_LOCK" />


//設置窗體背景模糊
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);

(3) 屏幕方向設置


a. 配置文件實現

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

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


b. 代碼實現

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

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


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. 普通設置 


        alphaAnimation.setRepeatCount(5);//設置重複次數
        alphaAnimation.setFillAfter(true);//動畫執行完是否停留在執行完的狀態
        alphaAnimation.setStartOffset(1000);//動畫執行前等待的時間, 單位是毫秒
        alphaAnimation.start();//開始動畫


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中的變量


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();
    }



二. 檢查版本號


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);
			}
		}
    }


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;
	}

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 + "]";
		}
	}

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;
	}


三. 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;
			}
		};
	};

.

做者 :萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.


四. 下載安裝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();
	}

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);
				}
			}
		}
    }

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;
	}

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);
	}

五. 相關的源碼 


(1) 佈局文件

splash.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/ivt_splash" 
    android:id="@+id/splash_rl">

    <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>



(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 int touchPositionX0;
	private int touchPositionX1;
	
	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 + "]";
		}
	}
	
	/**
	 *	設置觸摸事件
	 *	在手指按下時記錄x座標值 , 在手指擡起的時候記錄x座標值 , 若是兩個值相差超過100
	 *	那麼跳轉到主界面 
	 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN :
				touchPositionX0 = (int) event.getX();
				break;
			case MotionEvent.ACTION_UP :
				touchPositionX1 = (int) event.getX();
				if((touchPositionX0 - touchPositionX1) > 100)
					loadMainUI();
				touchPositionX0 = 0;
				touchPositionX1 = 0;
				break;
		}
		return true;
	}
}

.

做者 :萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.

相關文章
相關標籤/搜索