1. 程式人生 > >Android提示版本更新

Android提示版本更新

前言:在軟體開發的尾聲應該都會遇到這個問題,還好網上資料很多,所以基本不費什麼力氣就搞定了,現記錄於下。這裡用的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 不勝感激。