應(yīng)用自動(dòng)更新封裝-Android

前言

應(yīng)用更新應(yīng)該是現(xiàn)在每個(gè)應(yīng)用必備的一個(gè)功能逊躁。正是通過不斷的更新茅信,不斷的調(diào)優(yōu)盾舌,才使我們的應(yīng)用更完善。當(dāng)然在各大應(yīng)用市場中蘸鲸,它們已經(jīng)幫我們實(shí)現(xiàn)了這項(xiàng)功能妖谴,但是有一個(gè)問題,當(dāng)我們的應(yīng)用是在某度市場下載的應(yīng)用酌摇,如果那天我們不在使用某度市場膝舅,而是用別的市場,之前的發(fā)布的市場無法通知我們的應(yīng)用窑多,那么是不是我們就無法更新了仍稀。所以封裝一個(gè)自己的應(yīng)用自動(dòng)更新還是比較有必要的。那么今天我們就來學(xué)習(xí)一下埂息,如何封裝自己的應(yīng)用自動(dòng)更新功能技潘。


自動(dòng)更新的意義

  • 能及時(shí)告知所有用戶有新的版本
  • 對用戶來說,更新更加簡單千康,無須打開第三方應(yīng)用(避免應(yīng)用來回切換享幽,同時(shí)減少打開其他應(yīng)用后用戶不再回到本應(yīng)用)
  • 可以強(qiáng)制用戶更新(一切特定的場景下)
  • 更多的自動(dòng)控制權(quán)

分析原理

  • apk安裝包文件下載
  • 利用Notification通知用戶更新進(jìn)度
  • 文件下載后調(diào)用系統(tǒng)安裝應(yīng)用
    其實(shí)說白了就是下載更新的apk然后安裝。如果對斷電續(xù)傳和通知不了解的話先看先這個(gè)小項(xiàng)目后臺異步斷電續(xù)傳文件下載這個(gè)小項(xiàng)目是我學(xué)習(xí)第一行代碼時(shí)寫的吧秕,在寫這篇文章突然想起來琉闪,現(xiàn)在回頭看看,即使是入門砸彬,代碼寫的也是真心好颠毙。條例清晰,接口回調(diào)砂碉,方法封裝蛀蜜,雖然小但是邏輯很清晰。

實(shí)踐

我們先開下大體的思路流程:

流程圖

大致流程就是這樣增蹭。其實(shí)說白了就是下載任務(wù)然后安裝滴某。這里核心是下載部分那么我就可以用后臺異步斷電續(xù)傳文件下載這個(gè)例子下載(已經(jīng)合并2個(gè)例子放到一個(gè)工程中了)。在這里我在提供例外一種方法。

  • UpdateDownLoadListener這個(gè)類就是下載回調(diào)的監(jiān)聽
public interface UpdateDownLoadListener {

    /**
     * 下載請求開始回調(diào)
     */
    public void onStarted();

    /**
     * 進(jìn)度更新回調(diào)
     *
     * @param progress
     * @param downloadUrl
     */
    public void onProgressChanged(int progress, String downloadUrl);

    /**
     * 下載完成回調(diào)
     *
     * @param completeSize
     * @param downloadUrl
     */
    public void onFinished(int completeSize, String downloadUrl);

    /**
     * 下載失敗回調(diào)
     */
    public void onFailure();

}
  • UpdateDownLoadRequest 真正的處理文件下載和線程間的通信
public class UpdateDownLoadRequest implements Runnable {


    private String downloadUrl;
    private String downloadPath;
    private UpdateDownLoadListener mLoadListener;
    private long contentLength;

    private boolean isDownloading = false;
    private UpdateDownRequestHandle mHandle;

    public UpdateDownLoadRequest(String downloadUrl, String downloadPath, UpdateDownLoadListener loadListener) {
        this.downloadPath = downloadPath;
        this.downloadUrl = downloadUrl;
        this.mLoadListener = loadListener;
        this.isDownloading = true;
        this.mHandle = new UpdateDownRequestHandle();
    }

    //真正的建立連接
    private void makeRequest() throws IOException {

        if (!Thread.currentThread().isInterrupted()) {
            try {
                URL url = new URL(downloadUrl);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(5000);
                connection.setRequestProperty("Connection", "Keep-Alive");
                connection.connect();//阻塞我們當(dāng)前的線程
                contentLength = connection.getContentLength();
                if (!Thread.currentThread().isInterrupted()) {
                    //完成文件的下載
                    mHandle.sendResponseMessage(connection.getInputStream());
                }
            } catch (IOException e) {
                throw e;
            }

        }
    }

    @Override
    public void run() {
        try {
            makeRequest();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 包含了下載過程中所有可能出現(xiàn)的異常情況
     */
    public enum FailureCode {
        UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, Json, Interrupted
    }

    /**
     * 文件下載 將消息傳遞個(gè)主線程
     */
    public class UpdateDownRequestHandle {

        private static final int SUCCESS_MESSAGE = 0;
        private static final int FAILURE_MESSAGE = 1;
        private static final int START_MESSAGE = 2;
        private static final int FINISH_MESSAGE = 3;
        private static final int NETWORK_MESSAGE = 4;
        private static final int PROGRESS_CHANGED = 5;

        private Handler handler;//完成線程見的通信

        private int currentSize = 0;
        private int progress = 0;

        public UpdateDownRequestHandle() {
            handler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    handleSelfMessage(msg);
                }
            };
        }

        protected void handleSelfMessage(Message msg) {
            Object[] response;
            switch (msg.what) {
                case FAILURE_MESSAGE:
                    response = (Object[]) msg.obj;
                    handlerFailureMessage((FailureCode) response[0]);
                    break;
                case PROGRESS_CHANGED:
                    response = (Object[]) msg.obj;
                    int p = ((Integer) response[0]).intValue();
                    handlerProgressChangedMessage(p);
                    break;
                case FINISH_MESSAGE:
                    onFinish();
                    break;
            }
        }

        //各種消息的處理邏輯
        protected void handlerProgressChangedMessage(int progress) {
            mLoadListener.onProgressChanged(progress, "");
        }

        protected void handlerFailureMessage(FailureCode failureCode) {
            onFailure(failureCode);
        }


        public void onFinish() {
            mLoadListener.onFinished(currentSize, "");
        }

        public void onFailure(FailureCode failureCode) {
            Log.d("TAG", "onFailure: " + failureCode);
            mLoadListener.onFailure();
        }

        protected void sendFailureMsg(FailureCode code) {
            sendMsg(obtainMessage(FAILURE_MESSAGE, new Object[]{code}));
        }

        protected void sendFinishMsg() {
            sendMsg(obtainMessage(FINISH_MESSAGE, null));
        }

        protected void sendProgressChangedMsg(int progress) {
            sendMsg(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
        }

        protected void sendMsg(Message msg) {
            if (handler != null) {
                handler.sendMessage(msg);
            } else {
                handleSelfMessage(msg);
            }
        }

        /**
         * 獲取一個(gè)消息對象
         *
         * @param responseMessage
         * @param response
         * @return
         */
        protected Message obtainMessage(int responseMessage, Object response) {
            Message msg;
            if (handler != null) {
                msg = handler.obtainMessage(responseMessage, response);
            } else {
                msg = Message.obtain();
                msg.what = responseMessage;
                msg.obj = response;
            }
            return msg;

        }

        public void sendResponseMessage(InputStream inputStream) {
            RandomAccessFile acesFile = null;
            currentSize = 0;

            try {
                acesFile = new RandomAccessFile(downloadPath, "rwd");
                int limit = 0;
                int length = -1;
                byte[] bs = new byte[1024];
                while ((length = inputStream.read(bs)) != -1) {
                    if (isDownloading) {
                        acesFile.write(bs, 0, length);
                        currentSize += length;
                        if (currentSize < contentLength) {
                            progress = (int) (currentSize * 100 / contentLength);
                            if (limit % 30 == 0 && progress <= 100) {
                                //為了限制一下notification的更新頻率
                                sendProgressChangedMsg(progress);
                            }
                            if (progress >= 100) {
                                //下載完成
                                sendProgressChangedMsg(progress);
                            }
                            limit++;
                        }
                    }
                }
                sendFinishMsg();
            } catch (IOException e) {
                sendFailureMsg(FailureCode.IO);
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }

                    if (acesFile != null) {
                        acesFile.close();
                    }
                } catch (IOException e) {
                    sendFailureMsg(FailureCode.IO);
                }
            }

        }
    }
}

這段代碼有點(diǎn)長霎奢,簡單來看就開啟線程下載任務(wù)户誓,根據(jù)現(xiàn)在的狀態(tài)利用handle發(fā)送各種狀態(tài)的消息,然后利用接口回調(diào)幕侠,調(diào)用接口帝美,再讓啟動(dòng)下載的類也就是我們后臺下載的服務(wù)類去實(shí)現(xiàn)接口并處理相應(yīng)的邏輯。

  • UpdateDownManager 下載調(diào)度管理器晤硕,調(diào)用我們的UpdateDownLoadRequest悼潭,也是下載任務(wù)的入口,在這里我們?yōu)榱藶榱私研约尤胍磺信袛辔韫俊2⑾螺d任務(wù)設(shè)置單例模式舰褪,并用線程池,方便管理閑扯避免僵尸線程疏橄。
  • UpdateDownService這里就是我們啟動(dòng)下載任務(wù)的地方
public class UpdateDownService extends Service {
    private static final String TAG = "UpdateDownService";
    private String apkUrl;
    private String filePath;
    private NotificationManager mNotificationManager;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        filePath = Environment.getExternalStorageDirectory() + "/testDownload/test.apk";
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null) {
            notifyUser("下載失敗", "下載失敗原因", 0);
            stopSelf();
        }
        apkUrl = intent.getStringExtra("apkUrl");
        Log.i("TAG", "下載地址: " + apkUrl);
        notifyUser(getString(R.string.update_download_start), getString(R.string.update_download_start), 0);
        startDownload();
        return super.onStartCommand(intent, flags, startId);
    }


    private void startDownload() {
        UpdateDownManager.getInstance().startDownloads(apkUrl, filePath, new UpdateDownLoadListener() {

            @Override
            public void onStarted() {
            }

            @Override
            public void onProgressChanged(int progress, String downloadUrl) {
                Log.d(TAG, "onProgressChanged: "+progress);
                notifyUser(getString(R.string.update_download_processing), getString(R.string.update_download_processing), progress);
            }

            @Override
            public void onFinished(int completeSize, String downloadUrl) {
                Log.d(TAG, "onProgressChanged: "+completeSize);
                notifyUser(getString(R.string.update_download_finish), getString(R.string.update_download_finish), 100);
                stopSelf();
            }

            @Override
            public void onFailure() {
                Log.d(TAG, "onProgressChanged: ");
                notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed_msg), 0);
                stopSelf();
            }
        });
    }

    /**
     * 更新notification來告知用戶下載進(jìn)度
     *
     * @param result
     * @param reason
     * @param progress
     */
    private void notifyUser(String result, String reason, int progress) {
        Notification mNotification;
        NotificationCompat.Builder build = new NotificationCompat.Builder(this);
        build.setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentTitle(getString(R.string.app_name));
        if (progress > 0 && progress < 100) {
            build.setProgress(100, progress, false);
        } else {
            build.setProgress(0, 0, false);
        }

        build.setAutoCancel(false);
        build.setWhen(System.currentTimeMillis());
        build.setTicker(result);
        build.setContentIntent(progress >= 100 ? getContentIntent() : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
        mNotification = build.build();
        mNotificationManager.notify(0, mNotification);
    }

    public PendingIntent getContentIntent() {
        File apkFile = new File(filePath);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.parse("file://" + apkFile.getAbsolutePath()), "application/vnd.android.package-archive");
        return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

我們在onStartCommand()方法中啟動(dòng)下載占拍,下載完成結(jié)束當(dāng)前服務(wù)。然后用Notification通知用戶软族,在用系統(tǒng)自帶的api安裝刷喜。最后就是在Activity啟動(dòng)服務(wù)下載任務(wù)就能進(jìn)行了。篇幅較長Activity的代碼我就不粘貼出來了立砸。


結(jié)束

相比在第一行代碼中的掖疮,這段代碼多了做了一些邏輯上的處理,是代碼更健壯性颗祝。原理都是相同的浊闪,如果你是在小范圍應(yīng)用或是自己做的練手應(yīng)用想加入自動(dòng)更新功能,就可以將這些代碼封裝到自己的工具類中螺戳,當(dāng)然距離成熟框架還是有很大的距離搁宾,比如我們更新要和服務(wù)器版本對比。服務(wù)器推送新版本功能等等倔幼,但是思路都是這樣的盖腿。在這里我只是拋磚引玉。身為小白的我损同,還需努力翩腐。 后續(xù)會更新在線更新等熱修復(fù)的文章敬請期待。

寫的不好大家多多諒解膏燃。如有錯(cuò)誤真心希望大家提出來茂卦。最后希望大家一起進(jìn)步。加油W榱ā5攘处渣!

源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛛砰,一起剝皮案震驚了整個(gè)濱河市罐栈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泥畅,老刑警劉巖悠瞬,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涯捻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)望迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門障癌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辩尊,你說我怎么就攤上這事涛浙。” “怎么了摄欲?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵轿亮,是天一觀的道長。 經(jīng)常有香客問我胸墙,道長我注,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任迟隅,我火速辦了婚禮但骨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘智袭。我一直安慰自己奔缠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布吼野。 她就那樣靜靜地躺著校哎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞳步。 梳的紋絲不亂的頭發(fā)上闷哆,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音谚攒,去河邊找鬼阳准。 笑死,一個(gè)胖子當(dāng)著我的面吹牛馏臭,可吹牛的內(nèi)容都是我干的野蝇。 我是一名探鬼主播讼稚,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绕沈!你這毒婦竟也來了锐想?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤乍狐,失蹤者是張志新(化名)和其女友劉穎赠摇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浅蚪,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藕帜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惜傲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽故。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盗誊,靈堂內(nèi)的尸體忽然破棺而出时甚,到底是詐尸還是另有隱情,我是刑警寧澤哈踱,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布荒适,位于F島的核電站,受9級特大地震影響开镣,放射性物質(zhì)發(fā)生泄漏刀诬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一哑子、第九天 我趴在偏房一處隱蔽的房頂上張望舅列。 院中可真熱鬧,春花似錦卧蜓、人聲如沸帐要。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榨惠。三九已至,卻和暖如春盛霎,著一層夾襖步出監(jiān)牢的瞬間赠橙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工愤炸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留期揪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓规个,卻偏偏與公主長得像凤薛,于是被迫代替她去往敵國和親姓建。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫缤苫、插件速兔、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評論 4 62
  • 便秘小秘方:一片生姜、一個(gè)創(chuàng)可貼活玲、兩個(gè)透明液體涣狗,最快的三天以內(nèi)大便通暢,最慢的也不會超過一個(gè)禮拜一般人都知道舒憾,生姜...
    菱520閱讀 285評論 0 0
  • 某月的某一天镀钓,我換了新的工作環(huán)境,工作到第四天镀迂,有個(gè)男孩子跑過來對我說:我覺得你很漂亮掸宛,我們交個(gè)朋友吧,因?yàn)槲业闹?..
    易朱閱讀 198評論 0 0
  • 如果她不回我 如果她不理我 如果她真的放棄 如果她覺得我很淺薄 如果她不再相信我 我該怎么辦措译? 我不知道 靜待時(shí)間...
    生呼吸閱讀 178評論 0 0