【Android 应用开发】Android应用的自动更新模块
.
作者?:萬境絕塵?
轉載請注明出處?:?http://blog.csdn.net/shulianghan/article/details/18964835
.
軟件的自動更新一般都與Splash界面綁定在一起, 由于需要維護的軟件界面很復雜, 一個Activity中嵌入ViewPager, 并且邏輯比較復雜, 索性重新寫一個Activity, 現在的軟件都很流行使用Splash界面, 正好與自動更新配套在一起;
在這個自動更新Splash中, 使用到了 動畫設置 ,SharedPerference ,pull解析 ,dialog對話框 ,http網絡編程 ,handler 等.
注意一個錯誤 :?已安裝具有該名稱和不同簽名的數據包 , 早上測試人員報告突然出現這個問題, 在開發的時候我直接將eclipse上編譯的版本放到了服務器上, 最后出現了這個問題, 開發的時候明明是好的啊, 怎么測試的時候出問題了呢.
編譯環境不同, 產生的簽名是不一樣的, 在eclipse上編譯生成 與 正式版本在linux下編譯 所產生的 數字簽名 是不一樣的.
又發現一個BUG : 在彈出更新對話框, 點擊確定下載完畢之后會彈出系統自帶的替換應用程序對話框, 在這里點取消的話就會一直卡在Splash界面. 設置一個跳轉機制解決這個問題.
解決方案 :利用觸摸劃屏事件, 向左側劃屏100px, 就自動跳轉到主界面 , 最后的最終代碼已經加上去了
/*** 設置觸摸事件* 在手指按下時記錄x坐標值 , 在手指抬起的時候記錄x坐標值 , 如果兩個值相差超過100* 那么跳轉到主界面 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)*/@Overridepublic 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" />
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() {//動畫開始時回調@Overridepublic void onAnimationStart(Animation animation) {}//動畫重復執行時回調@Overridepublic void onAnimationRepeat(Animation animation) {}//動畫執行結束時回調@Overridepublic void onAnimationEnd(Animation animation) {}});
4. SharedPerference使用
5. onCreate()方法代碼?
/*** 創建Activity時調用* * ① 設置全屏顯示, 由于是Splash界面, 因此不能有標題* ② 設置布局, 版本號, 執行動畫 * ③ 設置當前時間* ④ 獲取SharedPerference配置文件* ⑤ 開啟檢查版本號線程, 后續操作都在改線程中操作* */@Overridepublic 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;}@Overridepublic 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"><translateandroid:fromXDelta="100%p"android:toXDelta="0"android:fromYDelta="0"android:toYDelta="0" android:duration="200"/> </set>
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid: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配置文件* ⑤ 開啟檢查版本號線程, 后續操作都在改線程中操作* */@Overridepublic 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;}@Overridepublic String toString() {return "UpdateInfo [version=" + version + ", url=" + url+ ", description=" + description + "]";}}/*** 設置觸摸事件* 在手指按下時記錄x坐標值 , 在手指抬起的時候記錄x坐標值 , 如果兩個值相差超過100* 那么跳轉到主界面 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)*/@Overridepublic 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
.
總結
以上是生活随笔為你收集整理的【Android 应用开发】Android应用的自动更新模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 应用开发】Androi
- 下一篇: 【UML 建模】UML建模语言入门-视图