http://blog.csdn.net/harvic880925/article/details/25191159
前言:在軟件開發的尾聲應該都會遇到這個問題,還好網上資料很多,所以基本不費什麼力氣就搞定了,現記錄於下。這裏用的PHP服務器。
效果圖:(PHP服務器)
初始界面 檢測後,如果已是最新版
如果不是最新版,提示更新 正在下載 安裝新程序
一、準備知識
在AndroidManifest.xml裏定義了每個Android apk的版本標識:
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.try_downloadfile_progress"
- android:versionCode="1"
- android:versionName="1.0" >
其中,android:versionCode和android:versionName兩個字段分別表示版本代碼,版本名稱。versionCode是整型數字,versionName是字符串。由於version是給用戶看的,不太容易比較大小,升級檢查時,可以以檢查versionCode爲主,方便比較出版本的前後大小。
那麼,在應用中如何讀取AndroidManifest.xml中的versionCode和versionName呢?可以使用PackageManager的API,參考以下代碼:
- /**
- * 獲取軟件版本號
- * @param context
- * @return
- */
- public static int getVerCode(Context context) {
- int verCode = -1;
- try {
- //注意:"com.example.try_downloadfile_progress"對應AndroidManifest.xml裏的package="……"部分
- verCode = context.getPackageManager().getPackageInfo(
- "com.example.try_downloadfile_progress", 0).versionCode;
- } catch (NameNotFoundException e) {
- Log.e("msg",e.getMessage());
- }
- return verCode;
- }
- /**
- * 獲取版本名稱
- * @param context
- * @return
- */
- public static String getVerName(Context context) {
- String verName = "";
- try {
- verName = context.getPackageManager().getPackageInfo(
- "com.example.try_downloadfile_progress", 0).versionName;
- } catch (NameNotFoundException e) {
- Log.e("msg",e.getMessage());
- }
- return verName;
- }
這裏要注意一個地方:代碼裏的“com.example.try_downloadfile_progress”對應AndroidManifest.xml裏的package="……"部分
二、XML代碼
XML代碼非常簡單,就是如初始化界面那樣,在裏面加一個BUTTON而已。代碼如下:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- tools:context=".MainActivity" >
- <Button
- android:id="@+id/chek_newest_version"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_margin="5dip"
- android:text="檢測最新版本"/>
- </RelativeLayout>
三、輔助類構建(Common)
這裏爲了開發方便,將一些公共的函數,單獨放在Common類中實現,代碼如下:
- package com.example.try_downloadfile_progress;
- /**
- * @author harvic
- * @date 2014-5-7
- * @address http://blog.csdn.net/harvic880925
- */
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.util.List;
- import org.apache.http.HttpResponse;
- import org.apache.http.NameValuePair;
- import org.apache.http.client.entity.UrlEncodedFormEntity;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.apache.http.protocol.HTTP;
- import android.content.Context;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.util.Log;
- public class Common {
- public static final String SERVER_IP="http://192.168.1.105/";
- public static final String SERVER_ADDRESS=SERVER_IP+"try_downloadFile_progress_server/index.php";//軟件更新包地址
- public static final String UPDATESOFTADDRESS=SERVER_IP+"try_downloadFile_progress_server/update_pakage/baidu.apk";//軟件更新包地址
- /**
- * 向服務器發送查詢請求,返回查到的StringBuilder類型數據
- *
- * @param ArrayList
- * <NameValuePair> vps POST進來的參值對
- * @return StringBuilder builder 返回查到的結果
- * @throws Exception
- */
- public static StringBuilder post_to_server(List<NameValuePair> vps) {
- DefaultHttpClient httpclient = new DefaultHttpClient();
- try {
- HttpResponse response = null;
- // 創建httpost.訪問本地服務器網址
- HttpPost httpost = new HttpPost(SERVER_ADDRESS);
- StringBuilder builder = new StringBuilder();
- httpost.setEntity(new UrlEncodedFormEntity(vps, HTTP.UTF_8));
- response = httpclient.execute(httpost); // 執行
- if (response.getEntity() != null) {
- // 如果服務器端JSON沒寫對,這句是會出異常,是執行不過去的
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(response.getEntity().getContent()));
- String s = reader.readLine();
- for (; s != null; s = reader.readLine()) {
- builder.append(s);
- }
- }
- return builder;
- } catch (Exception e) {
- // TODO: handle exception
- Log.e("msg",e.getMessage());
- return null;
- } finally {
- try {
- httpclient.getConnectionManager().shutdown();// 關閉連接
- // 這兩種釋放連接的方法都可以
- } catch (Exception e) {
- // TODO Auto-generated catch block
- Log.e("msg",e.getMessage());
- }
- }
- }
- /**
- * 獲取軟件版本號
- * @param context
- * @return
- */
- public static int getVerCode(Context context) {
- int verCode = -1;
- try {
- //注意:"com.example.try_downloadfile_progress"對應AndroidManifest.xml裏的package="……"部分
- verCode = context.getPackageManager().getPackageInfo(
- "com.example.try_downloadfile_progress", 0).versionCode;
- } catch (NameNotFoundException e) {
- Log.e("msg",e.getMessage());
- }
- return verCode;
- }
- /**
- * 獲取版本名稱
- * @param context
- * @return
- */
- public static String getVerName(Context context) {
- String verName = "";
- try {
- verName = context.getPackageManager().getPackageInfo(
- "com.example.try_downloadfile_progress", 0).versionName;
- } catch (NameNotFoundException e) {
- Log.e("msg",e.getMessage());
- }
- return verName;
- }
- }
這裏除了上面我們提到的兩個函數getVerCode和getVerName外,還有幾個常量和一個函數定義,含義分別如下:
SERVER_IP:服務器IP地址(大家在拿到試驗的時候,要改成自己服務器IP地址)
SERVER_ADDRESS:android程序要將請求發送到的頁面地址,無須更改。
UPDATESOFTADDRESS:更新安裝包存放的位置,無須更改。
函數StringBuilder post_to_server(List<NameValuePair> vps)是訪問服務器並返回結果的功能封裝。傳進去名值對,返回StringBuilder對象
四、主頁面代碼構建
1、首先設置AndroidManifest.xml,使其能訪問網絡和SD卡
在</manifest>標籤上面,加入:
- <uses-permission android:name="android.permission.INTERNET" >
- </uses-permission>
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" >
- </uses-permission>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
- </uses-permission>
2、主頁代碼:
先貼出全部代碼,然後逐步講解,全部代碼如下:
- package com.example.try_downloadfile_progress;
- /**
- * @author harvic
- * @date 2014-5-7
- * @address http://blog.csdn.net/harvic880925
- */
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.List;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpResponse;
- import org.apache.http.NameValuePair;
- import org.apache.http.client.ClientProtocolException;
- import org.apache.http.client.HttpClient;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.apache.http.message.BasicNameValuePair;
- import org.json.JSONArray;
- import android.net.Uri;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.os.Environment;
- import android.os.Handler;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.app.Dialog;
- import android.app.ProgressDialog;
- import android.content.DialogInterface;
- import android.content.Intent;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- public class MainActivity extends Activity {
- Button m_btnCheckNewestVersion;
- long m_newVerCode; //最新版的版本號
- String m_newVerName; //最新版的版本名
- String m_appNameStr; //下載到本地要給這個APP命的名字
- Handler m_mainHandler;
- ProgressDialog m_progressDlg;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //初始化相關變量
- initVariable();
- m_btnCheckNewestVersion.setOnClickListener(btnClickListener);
- }
- private void initVariable()
- {
- m_btnCheckNewestVersion = (Button)findViewById(R.id.chek_newest_version);
- m_mainHandler = new Handler();
- m_progressDlg = new ProgressDialog(this);
- m_progressDlg.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- // 設置ProgressDialog 的進度條是否不明確 false 就是不設置爲不明確
- m_progressDlg.setIndeterminate(false);
- m_appNameStr = "haha.apk";
- }
- OnClickListener btnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- new checkNewestVersionAsyncTask().execute();
- }
- };
- class checkNewestVersionAsyncTask extends AsyncTask<Void, Void, Boolean>
- {
- @Override
- protected Boolean doInBackground(Void... params) {
- // TODO Auto-generated method stub
- if(postCheckNewestVersionCommand2Server())
- {
- int vercode = Common.getVerCode(getApplicationContext()); // 用到前面第一節寫的方法
- if (m_newVerCode > vercode) {
- return true;
- } else {
- return false;
- }
- }
- return false;
- }
- @Override
- protected void onPostExecute(Boolean result) {
- // TODO Auto-generated method stub
- if (result) {//如果有最新版本
- doNewVersionUpdate(); // 更新新版本
- }else {
- notNewVersionDlgShow(); // 提示當前爲最新版本
- }
- super.onPostExecute(result);
- }
- @Override
- protected void onPreExecute() {
- // TODO Auto-generated method stub
- super.onPreExecute();
- }
- }
- /**
- * 從服務器獲取當前最新版本號,如果成功返回TURE,如果失敗,返回FALSE
- * @return
- */
- private Boolean postCheckNewestVersionCommand2Server()
- {
- StringBuilder builder = new StringBuilder();
- JSONArray jsonArray = null;
- try {
- // 構造POST方法的{name:value} 參數對
- List<NameValuePair> vps = new ArrayList<NameValuePair>();
- // 將參數傳入post方法中
- vps.add(new BasicNameValuePair("action", "checkNewestVersion"));
- builder = Common.post_to_server(vps);
- jsonArray = new JSONArray(builder.toString());
- if (jsonArray.length()>0) {
- if (jsonArray.getJSONObject(0).getInt("id") == 1) {
- m_newVerName = jsonArray.getJSONObject(0).getString("verName");
- m_newVerCode = jsonArray.getJSONObject(0).getLong("verCode");
- return true;
- }
- }
- return false;
- } catch (Exception e) {
- Log.e("msg",e.getMessage());
- m_newVerName="";
- m_newVerCode=-1;
- return false;
- }
- }
- /**
- * 提示更新新版本
- */
- private void doNewVersionUpdate() {
- int verCode = Common.getVerCode(getApplicationContext());
- String verName = Common.getVerName(getApplicationContext());
- String str= "當前版本:"+verName+" Code:"+verCode+" ,發現新版本:"+m_newVerName+
- " Code:"+m_newVerCode+" ,是否更新?";
- Dialog dialog = new AlertDialog.Builder(this).setTitle("軟件更新").setMessage(str)
- // 設置內容
- .setPositiveButton("更新",// 設置確定按鈕
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- m_progressDlg.setTitle("正在下載");
- m_progressDlg.setMessage("請稍候...");
- downFile(Common.UPDATESOFTADDRESS); //開始下載
- }
- })
- .setNegativeButton("暫不更新",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- // 點擊"取消"按鈕之後退出程序
- finish();
- }
- }).create();// 創建
- // 顯示對話框
- dialog.show();
- }
- /**
- * 提示當前爲最新版本
- */
- private void notNewVersionDlgShow()
- {
- int verCode = Common.getVerCode(this);
- String verName = Common.getVerName(this);
- String str="當前版本:"+verName+" Code:"+verCode+",/n已是最新版,無需更新!";
- Dialog dialog = new AlertDialog.Builder(this).setTitle("軟件更新")
- .setMessage(str)// 設置內容
- .setPositiveButton("確定",// 設置確定按鈕
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- finish();
- }
- }).create();// 創建
- // 顯示對話框
- dialog.show();
- }
- private void downFile(final String url)
- {
- m_progressDlg.show();
- new Thread() {
- public void run() {
- HttpClient client = new DefaultHttpClient();
- HttpGet get = new HttpGet(url);
- HttpResponse response;
- try {
- response = client.execute(get);
- HttpEntity entity = response.getEntity();
- long length = entity.getContentLength();
- m_progressDlg.setMax((int)length);//設置進度條的最大值
- InputStream is = entity.getContent();
- FileOutputStream fileOutputStream = null;
- if (is != null) {
- File file = new File(
- Environment.getExternalStorageDirectory(),
- m_appNameStr);
- fileOutputStream = new FileOutputStream(file);
- byte[] buf = new byte[1024];
- int ch = -1;
- int count = 0;
- while ((ch = is.read(buf)) != -1) {
- fileOutputStream.write(buf, 0, ch);
- count += ch;
- if (length > 0) {
- m_progressDlg.setProgress(count);
- }
- }
- }
- fileOutputStream.flush();
- if (fileOutputStream != null) {
- fileOutputStream.close();
- }
- down();
- } catch (ClientProtocolException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }
- private void down() {
- m_mainHandler.post(new Runnable() {
- public void run() {
- m_progressDlg.cancel();
- update();
- }
- });
- }
- void update() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(new File(Environment
- .getExternalStorageDirectory(), m_appNameStr)),
- "application/vnd.android.package-archive");
- startActivity(intent);
- }
- }
1、OnCreate函數:
先從主函數開始講,OnCreate函數中只有兩部分,一個是initVariable()初始化變量,這個不多說,難度不大;第二個是爲版本檢測按鈕設置監聽函數——btnClickListener,而在btnClickListener函數中可以明顯的看到,其中也只有一個類checkNewestVersionAsyncTask,這裏採用異步方式處理更新問題。下面看checkNewestVersionAsyncTask函數
2、checkNewestVersionAsyncTask函數
- class checkNewestVersionAsyncTask extends AsyncTask<Void, Void, Boolean>
- {
- @Override
- protected Boolean doInBackground(Void... params) {
- // TODO Auto-generated method stub
- if(postCheckNewestVersionCommand2Server())
- {
- int vercode = Common.getVerCode(getApplicationContext()); // 用到前面第一節寫的方法
- if (m_newVerCode > vercode) {
- return true;
- } else {
- return false;
- }
- }
- return false;
- }
- @Override
- protected void onPostExecute(Boolean result) {
- // TODO Auto-generated method stub
- if (result) {//如果有最新版本
- doNewVersionUpdate(); // 更新新版本
- }else {
- notNewVersionDlgShow(); // 提示當前爲最新版本
- }
- super.onPostExecute(result);
- }
- @Override
- protected void onPreExecute() {
- // TODO Auto-generated method stub
- super.onPreExecute();
- }
- }
(1)首先看後臺操作doInBackground
首先利用postCheckNewestVersionCommand2Server()函數將請求發送到服務器,該函數根據是否請求成功返回TRUE或FALSE,然後將從服務器上獲取的版本代碼與本地的版本代碼進行比較,如果要更新返回TRUE,如果不要更新返回FASLE。
下面看看postCheckNewestVersionCommand2Server()的代碼:
- private Boolean postCheckNewestVersionCommand2Server()
- {
- StringBuilder builder = new StringBuilder();
- JSONArray jsonArray = null;
- try {
- // 構造POST方法的{name:value} 參數對
- List<NameValuePair> vps = new ArrayList<NameValuePair>();
- // 將參數傳入post方法中
- vps.add(new BasicNameValuePair("action", "checkNewestVersion"));
- builder = Common.post_to_server(vps);
- jsonArray = new JSONArray(builder.toString());
- if (jsonArray.length()>0) {
- if (jsonArray.getJSONObject(0).getInt("id") == 1) {
- m_newVerName = jsonArray.getJSONObject(0).getString("verName");
- m_newVerCode = jsonArray.getJSONObject(0).getLong("verCode");
- return true;
- }
- }
- return false;
- } catch (Exception e) {
- Log.e("msg",e.getMessage());
- m_newVerName="";
- m_newVerCode=-1;
- return false;
- }
- }
這裏就是構建名值對,然後發向服務器,如果獲取到了值就返回TURE,如果沒獲取到值,就返回FALSE。服務器返回的JSON值爲:
- [{"id":"1","verName":"1.0.1","verCode":"2"}]
對於服務器代碼,由於是用PHP寫的,這裏就不再列出了,在源碼裏有。
(2)onPostExecute()
繼續第一部分,在doInBackground操作完成後,如果需要更新doInBackground返回TRUE,否則返回FASLE,所以在onPostExecute中根據result不同調用不同的函數,利用doNewVersionUpdate(); 提示用戶更新最新版本。利用notNewVersionDlgShow();
/提示用戶當前即爲最新版本,無需更新。
對於notNewVersionDlgShow()函數僅僅是創建了個對話框,沒什麼實體內容,就不再具體講解。下面具體講述doNewVersionUpdate()下載,更新與安裝程序的過程。
3、doNewVersionUpdate()提示版本更新
具體代碼如下:
- private void doNewVersionUpdate() {
- int verCode = Common.getVerCode(getApplicationContext());
- String verName = Common.getVerName(getApplicationContext());
- String str= "當前版本:"+verName+" Code:"+verCode+" ,發現新版本:"+m_newVerName+
- " Code:"+m_newVerCode+" ,是否更新?";
- Dialog dialog = new AlertDialog.Builder(this).setTitle("軟件更新").setMessage(str)
- // 設置內容
- .setPositiveButton("更新",// 設置確定按鈕
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- m_progressDlg.setTitle("正在下載");
- m_progressDlg.setMessage("請稍候...");
- downFile(Common.UPDATESOFTADDRESS); //開始下載
- }
- })
- .setNegativeButton("暫不更新",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int whichButton) {
- // 點擊"取消"按鈕之後退出程序
- finish();
- }
- }).create();// 創建
- // 顯示對話框
- dialog.show();
- }
這裏創建一個具有確定按鈕和取消按鈕功能的對話框,如果用戶點擊取消按鈕,會利用finish()結束掉程序運行;如果點擊確定按鈕,則利用 downFile(Common.UPDATESOFTADDRESS); 函數開始程序下載,其中downFile()函數傳進去的參數是APP所在的服務器地址。注意這裏的地址要具體到下載文件,比如這裏是http://192.168.1.105/server/XXX.apk
4、downFile(final String url)下載並顯示進度
具體代碼如下:
- private void downFile(final String url)
- {
- m_progressDlg.show();
- new Thread() {
- public void run() {
- HttpClient client = new DefaultHttpClient();
- HttpGet get = new HttpGet(url);
- HttpResponse response;
- try {
- response = client.execute(get);
- HttpEntity entity = response.getEntity();
- long length = entity.getContentLength();
- m_progressDlg.setMax((int)length);//設置進度條的最大值
- InputStream is = entity.getContent();
- FileOutputStream fileOutputStream = null;
- if (is != null) {
- File file = new File(
- Environment.getExternalStorageDirectory(),
- m_appNameStr);
- fileOutputStream = new FileOutputStream(file);
- byte[] buf = new byte[1024];
- int ch = -1;
- int count = 0;
- while ((ch = is.read(buf)) != -1) {
- fileOutputStream.write(buf, 0, ch);
- count += ch;
- if (length > 0) {
- m_progressDlg.setProgress(count);//設置當前進度
- }
- }
- }
- fileOutputStream.flush();
- if (fileOutputStream != null) {
- fileOutputStream.close();
- }
- down(); //告訴HANDER已經下載完成了,可以安裝了
- } catch (ClientProtocolException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }
通過利用httpClient的get方法,獲取指定URL的內容,然後寫到本地SD卡中,對於進度條,首先利用m_progressDlg.setMax((int)length);設置進度條的最大值,然後在讀取返回結果並寫到本地時,利用 m_progressDlg.setProgress(count);設置當前進度。在全部寫完以後,調用down()函數,通知HANDER安裝程序。
5、安裝程序
安裝程序主要利用下面兩個函數:
- /**
- * 告訴HANDER已經下載完成了,可以安裝了
- */
- private void down() {
- m_mainHandler.post(new Runnable() {
- public void run() {
- m_progressDlg.cancel();
- update();
- }
- });
- }
- /**
- * 安裝程序
- */
- void update() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(new File(Environment
- .getExternalStorageDirectory(), m_appNameStr)),
- "application/vnd.android.package-archive");
- startActivity(intent);
- }
由於在android程序中必須依循單線程操作UI,所以在非主線程中不能操作UI,否則程序會崩掉,具體參見:《AsnyncTask與handler(一)——AsyncTask異步處理》與《AsnyncTask與handler(二)——handler消息機制》。所以這裏作用handler的方式更新UI。
好了,到這就全部講完了,下面給出客戶端與服務器端源碼,懂PHP的童鞋賺到了有木有……
源碼地址:http://download.csdn.net/detail/harvic880925/7309013 不要分,僅供分享。
請大家尊重作者原創版權,轉載請標明出處:http://blog.csdn.net/harvic880925/article/details/25191159 不勝感激。