獲得Android系統所有已安裝的應用並連網自動檢測升級更新

來源:互聯網
上載者:User

一、  說明:

本樣本是在上一個樣本(Android應用自身升級)的基礎上完成的。環境配置也同上一個demo一樣。只是增加了一些功能用來檢測Android系統中所有需要升級的應用程式,並從伺服器上下載更新。

二、 功能需求說明:

         a)    檢測出Android系統中所有已安裝的應用(區別與Android系統內建的應用),並獲得每個應用的資訊。

         b)    根據上一步獲得的系統中已安裝的應用資訊,通過http串連tomcat7伺服器,檢測每個應用的代碼版本並與當前應用的代碼版本進行比較,然後將需要更新的應用顯示                    在ListView中。

         c)     在ListView的onItemClick事件中提示是否下載並更新當前的應用。

         d)    監聽系統的程式安裝和替換廣播,當收到系統中有程式安裝或替換時,重新整理當前的ListView視圖以及Activity的標題(Activity的標題用於提示當前需要更新的應用程式數量)。

三、  應用需求的前提假設:

          a)    伺服器端的配置

                 在tomcat7伺服器的根目錄下建立AppUpdate目錄,作為更新程式訪問的根目錄。

          b)    Apk檔案的下載路徑的標準假設

               在搜尋到系統所有使用者安裝的應用後,需要訪問伺服器中對應的每一個應用的version.json設定檔,進行代碼版本的比較。該搜尋路徑的標準規則我們假定為:

               系統當前安裝的每一個應用對應的新版本所在的路徑為:

               http://10.0.2.2:8080/AppUpdate/應用程式名稱/version.json,其中的應用程式名稱與當前安裝的應用程式名稱相對應,鑒於應用程式名稱中可能包含‘.’和空格(目前只發現這兩                 種情況),我們將應用程式名稱中所有的‘.’和空格都以底線代替。例如:

              我們搜到系統中當前已安裝的應用程式名稱為Sample Soft Keyboard的應用(包含空格),伺服器中對應的version.json檔案的遠程路徑為:

             

http://10.0.2.2:8080/AppUpdate/Sample_Soft_Keyboard/version.json,(Sample,Soft,Keyboard之間有底線),對應的新的Apk檔案的遠程路徑為:

              http://10.0.2.2:8080/AppUpdate/Sample_Soft_Keyboard/Sample_Soft_Keyboard.apk

四、  流程以及關鍵技術說明:

        1) 首先要掃描出系統所有的應用資訊,並過濾掉系統內建的應用,只儲存使用者安裝的應用。程式碼範例:

public void scanNeedUpdateApp(){List<PackageInfo> appPackage = getPackageManager().getInstalledPackages(0);//獲得系統所有應用的安裝包資訊        for(int i=0; i<appPackage.size(); i++){        PackageInfo packageInfo = appPackage.get(i);        ApplicationInfo tmpAppInfo = new ApplicationInfo();        tmpAppInfo.appName = packageInfo.applicationInfo.loadLabel(getPackageManager()).toString();        tmpAppInfo.packageName = packageInfo.packageName;        tmpAppInfo.versionName = packageInfo.versionName;        tmpAppInfo.versionCode = packageInfo.versionCode;        tmpAppInfo.appIcon = packageInfo.applicationInfo.loadIcon(getPackageManager());        //只添加非系統應用        if((packageInfo.applicationInfo.flags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0){            String appName = tmpAppInfo.appName.toString().replace('.', ' ');        String VerJSONPath =webServicePath + appName +"/" +"version.json";        VerJSONPath = VerJSONPath.replaceAll(" ", "_");//拼接對應的version.json的訪問路徑        System.out.println(VerJSONPath);        try {        JSONObject jsonObj = GetNewVersionCode.getVersionJSON(VerJSONPath);if(jsonObj != null){Log.i("JSONnotNull","json 不為空白!");int newVersionCode = Integer.parseInt(jsonObj.getString("versionCode"));System.out.print("舊代碼版本");System.out.println(tmpAppInfo.versionCode);System.out.print("新代碼版本");System.out.println(newVersionCode);if(tmpAppInfo.versionCode < newVersionCode){tmpAppInfo.newVersionCode = newVersionCode;tmpAppInfo.newVersionName = jsonObj.getString("versionName");needUpdateList.add(tmpAppInfo);//將第三方的應用添加到列表中}}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}        }        }}

       說明: 獲得已安裝的應用程式資訊 可以通過getPackageManager()方法獲得

       Public abstract PackageManager  getPackageManager(), 然後將所有已安裝的包資訊放入List<PackageInfo>泛型中。方法如下

       Public abstract List<PackageInfo>  getInstalledPackages(int flags)

       過濾第三方應用,所有的系統應用的flag標誌為FALG_SYSTEM(值為1),第三方的應用為flag標誌的值為0

  2)   填充ListView的adapter資料集,並重新整理ListView,程式碼範例如下:

//填充ListViewpublic void fillListView(){ListView newUpdateListView = (ListView)findViewById(R.id.listview);        appAdapter =new NeedUpdateListAdapter(this,needUpdateList);//填充adapter資料集        newUpdateListView.setDividerHeight(5);        if(!appAdapter.isEmpty()){        int needUpdateCount = appAdapter.getCount();        setTitle("當前發現"+needUpdateCount + "款應用需要升級!");        newUpdateListView.setAdapter(appAdapter);//填充ListView        newUpdateListView.setOnItemClickListener(this);//設定ListView中Item的單擊事件        }else{        setTitle("未發現需要升級的應用!");        }}

   3)   註冊一個Handler用於在ListView重新整理之後,重新整理ListView所在的Activity的標題(用於顯示當前剩餘的需要更新的應用數量)程式碼範例:

//註冊refreshTitleHandler,用於在廣播接收中更新Activity的標題public void registerRefreshTitleHandler(){refreshTitleHandler = new Handler(){        public void handleMessage(Message msg){        switch(msg.what){        case 0:setTitle("當前發現"+appAdapter.getCount()+"款應用需要升級");break;        }        super.handleMessage(msg);        }        };}

   4)   監聽系統中應用程式的安裝和替換廣播,並在廣播接收中重新整理ListView

         說明:因為在新版本的應用下載更新時會進入系統內建的安裝程式,所以我們要監聽系統發送的有應用程式安裝或者替換的廣播

        (”android.intent.action.PACKAGE_REPLACED”),並在廣播接收函數onReceiver()中處理重新整理ListView視圖,這就涉及到不同的類中更新UI介面的問題。在這裡我們的           解決方案是在接收到廣播重新整理ListView後向Activity的Handle發送一個訊息,用於更新Activity的標題。(Handle我們設定成全域靜態方便引用)。程式碼範例如下:

public class RefreshListViewBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// TODO Auto-generated method stubLog.i("Broadcast", "我是廣播");if(intent.getAction().equals("android.intent.action.PACKAGE_REPLACED")){//擷取被替換的包名,因為getDataString()返回的值包含了“package:“,所以要從第八的位置開始截取String replacedPackageName =intent.getDataString().substring(8);if(!context.getPackageName().equals(replacedPackageName)){Log.i("curPackName",context.getPackageName());System.out.print(replacedPackageName);Log.i("replacedPack",replacedPackageName);if(CheckUpdateAllActivity.appAdapter.remove(replacedPackageName)){//重新整理主Activity的TitltMessage message = new Message();message.what = 0;CheckUpdateAllActivity.refreshTitleHandler.sendMessage(message);}}}}}

         進一步說明:廣播接收的註冊方式有兩種,一種是靜態註冊(在xml檔案中),另一種是動態註冊(在代碼中),二者的區別在於:生命週期不一樣。若以靜態方式註冊               的廣播,在第一次註冊之後就與其所在的應用程式無關了,即在應用程式退出後,系統仍然能接受到該廣播(若在應用程式退出後有該廣播發出)。以動態方式註冊的廣            播與程式有關,即程式退出後,就無法處理對應的廣播了。樣本中接受系統程式是否被替換的廣播會監聽自身的替換(重新RunAs),所以我們要屏蔽掉自身的替換,如
           果不屏蔽的話在開始RunAs自己後(監聽了自己)會找不到程式中的appAdapter(因為程式還沒開始運行)而報出null 指標異常的現象。所以我們根據上下文(context)來          獲得當前應用的包名,並與此時被替換的包名(通過intent來獲得)作比較來過濾掉自身的監聽。

   5)   ListView的Item單擊事件。ListView中顯示的Item代表可升級的應用程式,若使用者單擊Item項後,彈出是否更新的對話方塊。

//ListView中Item的單擊事件public void onItemClick(AdapterView<?> parent, View view, int position, long id) {// TODO Auto-generated method stubApplicationInfo clickedItemInfo = (ApplicationInfo) appAdapter.getItem(position);curClickItemAppName = clickedItemInfo.appName;StringBuffer sb = new StringBuffer();Log.i("adapterVerName",clickedItemInfo.versionName);sb.append("目前的版本:" + "\n");sb.append("版本名稱:" + clickedItemInfo.versionName);sb.append("版本代碼:" + clickedItemInfo.versionCode + "\n");sb.append("發現新版本:" + "\n");sb.append("版本名稱:" + clickedItemInfo.newVersionName);sb.append("版本代碼:" + clickedItemInfo.newVersionCode + "\n");sb.append("是否更新?");Dialog dialog = new AlertDialog.Builder(CheckUpdateAllActivity.this).setTitle("軟體更新").setMessage(sb.toString()).setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubif(curClickItemAppName != ""){String appName = curClickItemAppName.replace('.', ' ');String loadUrl = webServicePath + appName +"/" +appName +".apk";loadUrl = loadUrl.replaceAll(" ", "_");Log.i("LoadUrl", loadUrl);downLoadApkFile(loadUrl, appName);}else{curClickItemAppName = "";}}}).setNegativeButton("暫不更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubcurClickItemAppName = "";}}).create();dialog.show();}

        說明:被單擊的Item對應的應用程式資訊的擷取通過getItem(position)來完成,該函數返回的物件類型即Adapter中儲存的資料類型,強轉一下就可以了。之後就是按照           我們事先約定的新版本Apk的遠端存取路徑規則來拼接Apk的下載路徑了。

     6)    下載新版本的apk檔案。這一步沒什麼好說的了,上一篇已經很詳細了(Android單個應用自身的升級)樣本如下:

//下載新的apk應用檔案protected void downLoadApkFile(final String url, final String appName) {// TODO Auto-generated method stubpBar = new ProgressDialog(CheckUpdateAllActivity.this);pBar.setTitle("正在下載");pBar.setMessage("請稍候...");pBar.setProgressStyle(ProgressDialog.STYLE_SPINNER);pBar.show();new Thread(){public void run(){HttpClient httpClient = new DefaultHttpClient();HttpGet httpGet = new HttpGet(url);HttpResponse httpResponse;try {httpResponse = httpClient.execute(httpGet);if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){  HttpEntity httpEntity = httpResponse.getEntity();InputStream is = httpEntity.getContent();FileOutputStream fos = null;if(is !=null){File file = new File(Environment.getExternalStorageDirectory(),appName+".apk");fos = new FileOutputStream(file);byte[] buf = new byte[1024];int ch = -1;do{ch = is.read(buf);if(ch <= 0)break;fos.write(buf, 0, ch);}while(true);is.close();fos.close();haveDownLoad(appName + ".apk");}else{throw new RuntimeException("isStream is null");}}else if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND){//404未找到相應的檔案Looper.prepare();Toast toast = Toast.makeText(CheckUpdateAllActivity.this, "未找到對應的Apk檔案!", 1);pBar.cancel();toast.show();Looper.loop();}} catch (ClientProtocolException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();}

         說明:簡單的http通訊的步驟:1.建立HttpClient用戶端。2.建立訪問路徑HttpGet3.請求串連HttpResponse。通過http請求下載遠端的檔案時,有可能在所給的訪問路徑下         沒有需要的檔案,在這裡請求狀態通過HttpResponse.getStatusLine().getStatusCode()來獲得。常見的請求回應碼為200(請求成功),404(未找到對應的檔案)。另           外我們在下載子線程中去取消了下載進度條(下載完成後),涉及了在子線程中去更新UI,這樣違反了Android系統中UI單執行緒模式的原則,是不被允許的。所以要用
                  Looper或者Handle來處理。此處用了Looper。

    7)   下載完成後提示是否安裝,當選擇取消後刪除sdcard中下載的Apk檔案。

//下載完成 關閉進度條,並提示是否安裝protected void haveDownLoad(final String fileName) {// TODO Auto-generated method stubpBar.cancel();//下載完成取消進度條haveDownHandler.post(new Runnable(){@Overridepublic void run() {// TODO Auto-generated method stubDialog installDialog = new AlertDialog.Builder(CheckUpdateAllActivity.this).setTitle("下載完成").setMessage("是否安裝新的應用").setPositiveButton("確定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubinstallNewApk(fileName);}}).setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubFile downLoadApk = new File(Environment.getExternalStorageDirectory(),fileName);if(downLoadApk.exists()){downLoadApk.delete();}}}).create();installDialog.show();}});}

    8)    調用系統內建的安裝程式進行安裝。如果想採用靜默安裝,網上大俠們說要修改源碼才可以。

//安裝下載後的應用程式private void installNewApk(final String fileName) {// TODO Auto-generated method stubIntent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory(),fileName)),"application/vnd.android.package-archive");startActivity(intent);} 

    9)  JSON檔案解析類。該類中封裝了一個靜態方法getVersionJSON()用於獲得遠端version.json檔案資訊,返回物件類型為JSONObject,便於解析出每一個應用對應的           versionCode資訊。樣本如下:

public class GetNewVersionCode {public static JSONObject getVersionJSON(String VerJSONPath) throws ClientProtocolException, IOException, JSONException{StringBuilder VerJSON = new StringBuilder();HttpClient client = new DefaultHttpClient();HttpParams httpParams = client.getParams();HttpConnectionParams.setConnectionTimeout(httpParams, 3000);HttpConnectionParams.setSoTimeout(httpParams, 5000);HttpResponse response;response = client.execute(new HttpGet(VerJSONPath));//請求成功System.out.print("連結請求碼:");System.out.println(response.getStatusLine().getStatusCode());if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){Log.i("ConOK","連結成功");HttpEntity entity = response.getEntity();if(entity != null){BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"), 8192);String line = null;while((line = reader.readLine()) != null){VerJSON.append(line+"\n");}reader.close();JSONArray verJSONArray = new JSONArray(VerJSON.toString());if(verJSONArray.length() > 0){JSONObject obj = verJSONArray.getJSONObject(0);return obj;}}Log.i("ContFail","擷取JSONObject失敗!");return null;}Log.i("ConFail","連結失敗!");return null;}}

   10)   與ListView綁定的Adapter類,該類中最主要的方法是getCount()和getView()用於繪製ListView。在這裡重寫該類的構造方法,以便於在Adapter中儲存我們需要的類型            (ApplicationInfo)。樣本如下:

public class NeedUpdateListAdapter extends BaseAdapter {Context context;ArrayList<ApplicationInfo> needUpdateList = new ArrayList<ApplicationInfo>();public NeedUpdateListAdapter(Context context, ArrayList<ApplicationInfo> newNeedUpdateList){this.context = context;needUpdateList.clear();for(int i = 0; i<newNeedUpdateList.size(); i++){needUpdateList.add(newNeedUpdateList.get(i));}}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn needUpdateList.size();}@Overridepublic Object getItem(int position) {// TODO Auto-generated method stubreturn needUpdateList.get(position);}@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubView newView = convertView;final ApplicationInfo appItem = needUpdateList.get(position);if(newView == null){LayoutInflater vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);newView = vi.inflate(R.layout.check_update_list_item, null);//newView.setClickable(true);加上此句ListViewi點擊無響應,不知道是為什麼}TextView appName = (TextView)newView.findViewById(R.id.appName);ImageView appIcon=(ImageView)newView.findViewById(R.id.icon);if(appName != null)appName.setText(appItem.appName);if(appIcon != null)appIcon.setImageDrawable(appItem.appIcon);return newView;}public  boolean remove(String packageName){boolean flag = false;for(int i = 0; i < needUpdateList.size(); i++){if(needUpdateList.get(i).packageName.equals(packageName)){needUpdateList.remove(i);flag = true;Log.i("RemovePack", packageName);notifyDataSetChanged();}}if(flag){flag = false;return true;}return false;}public void removeAll(){needUpdateList.clear();notifyDataSetChanged();}}

     說明:當每繪製一項時就會調用一次getView()方法,就會裝載一次我們建立的布局檔案一次(check_update_list_item.xml),在getView方法中設定布局檔案中的組件       (TextView和ImageView)就能得到我們想要的視圖。另外添加兩個方法remove()和removeAll()用於ListView的重新整理(當我們下載安裝新版本後)。其中                  NotifyDataSetChanged()用於自動重新整理ListView。

    11)  應用程式資訊類,主要用於儲存應用程式的一些資訊,如當前應用的程式名,包名,版本名稱,版本代碼,表徵圖,所搜尋到的新版本的版本名稱,新的版本代碼。

public class ApplicationInfo {public String appName = "";public String packageName = "";public String versionName = "";public String newVersionName = "";public int versionCode = 0;public int newVersionCode = 0;public Drawable appIcon = null;}

源碼下載串連:本篇源碼下載

下面是一些:

    將系統所有已安裝的應用添加到ListView中

點擊更新某一個應用

下載新應用

下載完成提示安裝

進入系統安裝


更新完成後重新整理ListView和Activity標題

當在遠程伺服器中沒有找到對應的apk檔案則提示錯誤。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.