adnroid 使用service。 更新apk

項目中要使用到版本更新的功能,參考了開源中國源碼,也遇到一些問題,再次記錄一下:

遇到的問題:
1. 1.notifacation.bulider怎麼使用?
2. notifacation和notifacation.bulider有什麼區別?
3. 文件創建遇到的坑。
4. bind和service傳值。
5. 退出時要注意的事項。

step1:
開始需要比對服務器的apk版本。
json結構是這樣的

{"versionCode":"1","vsesionContent":"更新apk"}

查看本身的apk版本:

 versionCode = Application.getContext().getPackageManager().getPackageInfo(packageName, 0).versionCode;

step2:
如果服務器版本號大於當前版本號開始下載。

在這裏先說明service有兩種啓動方式:
第一種:startService(Intent);
第二種:bindService(Intent,ServiceConnection,flags);
兩種方式關閉不一樣,需要單獨關閉纔可以。
第一種:
Intent intent=new Intent(content,DownloadService.class);
content.startService();

第二種如果需要和activity交互數據的話可以使用。
以下是我寫的啓動方式
 public static void openDownLoadService(Context context, String downurl,
                                           String tilte) {
        final ICallbackResult callback = new ICallbackResult() {

            @Override
            public void OnBackResult(Object s) {
            }
        };

        ServiceConnection conn = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
                binder.addCallback(callback);
                binder.start();

            }
        };
        Intent intent = new Intent(context, DownloadService.class);
        intent.putExtra(DownloadService.BUNDLE_DOWNLOAD_URL, downurl);
        intent.putExtra(DownloadService.BUNDLE_KEY_TITLE, tilte);
        context.startService(intent);
        context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

我自己寫了一個接口,從onServiceConnected獲取service返回的數據。
service 執行的順序是:
oncreate->onStartCommand->Binder->onServiceConnected

在OnCreate進行一些初始化的數據。
比如:實例化 DownloadBinder,以及NotificationManager

在onServiceConnected判斷是否已經可以對activity進行通訊了
如果已經連接上了就開啓線程來下載apk文件

   DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
                binder.addCallback(callback);
                binder.start();
                Log.d("aa", "onServiceConnected");

接下來放出 DownloadBinderBinder類

public class DownloadBinder extends Binder {



        public void start() {
            Log.d("aa","Binder");
            if (downLoadThread == null || !downLoadThread.isAlive()) {
                progress = 0;
                **1**.setUpNotification();
                new Thread() {
                    public void run() {
                        // 下載
                **2**  startDownload();
                    }
                }.start();
            }
        }

        public void cancel() {
            canceled = true;
        }

        public int getProgress() {
            return progress;
        }

        public boolean isCanceled() {
            return canceled;
        }

        public boolean serviceIsDestroy() {
            return serviceIsDestroy;
        }

        public void cancelNotification() {
            mHandler.sendEmptyMessage(2);
        }

        public void addCallback(ICallbackResult callback) {
            DownloadService.this.callback = callback;
        }


    }

先看到 1:

因爲我們要在通知欄自定義下載的進度,所有我們要自己寫入佈局.

那麼我們會使用到該類 NotificationCompat.Builder
爲什麼不直接使用 Notification 是因爲有些方法已經hide了,比如 這個setLatestEventInfo();
setLatestEventInfo()方法來爲通知初始化佈局和數據。

接下來是我寫的:

 /**
     * 設置notification
     */
    private void setUpNotification() {
        CharSequence tickerText = "準備下載";
        long when = System.currentTimeMillis();
        NotificationBuilder = new NotificationCompat.Builder(mContext);
        /**
         * 這個屬性一定要加.不然顯示不了
         */
        NotificationBuilder.setSmallIcon(R.mipmap.logoicon);
        NotificationBuilder.setWhen(when);
        NotificationBuilder.setTicker(tickerText);
        NotificationBuilder.setOngoing(true);
        /**
         * 自定義試圖
         */
        contentView = new RemoteViews(getPackageName(), R.layout.notification_download_show);
        contentView.setTextViewText(R.id.tv_download_state, mTitle);
        NotificationBuilder.setContent(contentView);


        /**
         * 設置隱身跳轉----
         */
//        Intent intent = new Intent(this, MainActivity.class);
//        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
//                intent, PendingIntent.FLAG_UPDATE_CURRENT);
//        NotificationBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
    }

詳細的資料可以參考

http://blog.leanote.com/post/554ca3792168cb3a61000001

然後看到 2

前提:因爲service是運行在主線程中所以我們在service開啓線程進行下載apk操作,然後在通過安裝apk 進行更新

這裏就需要設計到一個文件創建的問題了, 因爲我們再次安裝下載好的apk時需要一個制定下載路徑。

看到這個方法

 public static void installAPK(Context context, File file) {
        if (file == null || !file.exists())
            return;
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file),
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }

這裏需要File 這個file就是我們下載apk的地方。也是因爲文件這個問題,花費了我幾個小時。
原來的代碼:

  File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
            if (!file.exists()) {
                file.mkdir();
            }
            String apkFile = saveFileName;
            File saveFile = new File(apkFile);

現在代碼

  File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
            if (!file.exists()) {
                file.mkdirs();
            }
            String apkFile = saveFileName;
            File saveFile = new File(apkFile);

有沒有看出來有什麼不同。

file.mkdirs(); file.mkdir(); 這個地方少些一個s。我心都涼了
第一個函數 如果是 xxx/load/xxx.apk 是可以創建的。支持多文件創建
第二個函數,只支持一個文件一個文件創建。

如果文件創建對了,就進行網絡下載apk的操作了
代碼如下:

  /**
     * 下載文件
     *
     * @param downloadUrl
     * @param saveFile
     * @return
     * @throws IOException
     */
    private long downloadUpdateFile(String downloadUrl, File saveFile) throws IOException {
        int downloadCount = 0;
        int currentSize = 0;
        long totalSize = 0;
        int updateTotalSize = 0;

        HttpURLConnection httpConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;

        try {
            URL url = new URL(downloadUrl);
            httpConnection = (HttpURLConnection) url.openConnection();
            httpConnection
                    .setRequestProperty("User-Agent", "PacificHttpClient");
            if (currentSize > 0) {
                httpConnection.setRequestProperty("RANGE", "bytes="
                        + currentSize + "-");
            }
            httpConnection.setConnectTimeout(10000);
            httpConnection.setReadTimeout(20000);
            httpConnection.setRequestMethod("GET");
            updateTotalSize = httpConnection.getContentLength();
            if (httpConnection.getResponseCode() == 404) {
                throw new Exception("fail!");
            }
            is = httpConnection.getInputStream();
            fos = new FileOutputStream(saveFile, false);
            byte buffer[] = new byte[1024];
            int readsize = 0;
            while ((readsize = is.read(buffer)) > 0) {
                fos.write(buffer, 0, readsize);
                totalSize += readsize;
                // 爲了防止頻繁的通知導致應用吃緊,百分比增加10才通知一次
                if ((downloadCount == 0)
                        || (int) (totalSize * 100 / updateTotalSize) - 10 >= downloadCount) {
                    downloadCount += 10;
                    // 更新進度
                    Message msg = mHandler.obtainMessage();
                    msg.what = 1;
                    msg.arg1 = downloadCount;
                    mHandler.sendMessage(msg);
                    if (callback != null)
                        callback.OnBackResult(progress);
                }
            }

            // 下載完成通知安裝
            mHandler.sendEmptyMessage(0);
            // 下載完了,cancelled也要設置
            canceled = true;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
            if (is != null) {
                is.close();
            }
            if (fos != null) {
                fos.close();
            }
        }
        return totalSize;
    }

我這裏使用到的是原始的網絡請求 配合handler進行通知欄的更新

   private Handler mHandler = new Handler() {

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    // 下載完畢
                    mNotificationManager.cancel(NOTIFY_ID);
                    installApk();
                    break;
                case 2:
                    // 取消通知
                    mNotificationManager.cancel(NOTIFY_ID);
                    break;
                case 1:
                    int rate = msg.arg1;
                    if (rate < 100) {
                        contentView.setTextViewText(R.id.tv_download_state, mTitle + "(" + rate
                                + "%" + ")");
                        contentView.setProgressBar(R.id.pb_download, 100, rate,
                                false);
                    } else {
                        // 下載完畢後變換通知形式
                        NotificationBuilder.setAutoCancel(true);
                        serviceIsDestroy = true;
                        stopSelf();// 停掉服務自身
                    }
                    mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
                    break;
            }
        }

    };

對了 最後還需要注意一個問題:

就是程序如果是 System.exit(0); 這樣退出的話,那麼通知欄就會在下載了 service也會終止掉
效果就是這樣
這裏寫圖片描述

參考

Android Service完全解析,關於服務你所需知道的一切(下)
Android 狀態欄通知Notification詳解
開源中國app源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章