应用自动更新封装-Android

前言

应用更新应该是现在每个应用必备的一个功能。正是通过不断的更新,不断的调优,才使我们的应用更完善。当然在各大应用市场中,它们已经帮我们实现了这项功能,但是有一个问题,当我们的应用是在某度市场下载的应用,如果那天我们不在使用某度市场,而是用别的市场,之前的发布的市场无法通知我们的应用,那么是不是我们就无法更新了。所以封装一个自己的应用自动更新还是比较有必要的。那么今天我们就来学习一下,如何封装自己的应用自动更新功能。


自动更新的意义

  • 能及时告知所有用户有新的版本
  • 对用户来说,更新更加简单,无须打开第三方应用(避免应用来回切换,同时减少打开其他应用后用户不再回到本应用)
  • 可以强制用户更新(一切特定的场景下)
  • 更多的自动控制权

分析原理

  • apk安装包文件下载
  • 利用Notification通知用户更新进度
  • 文件下载后调用系统安装应用
    其实说白了就是下载更新的apk然后安装。如果对断电续传和通知不了解的话先看先这个小项目后台异步断电续传文件下载这个小项目是我学习第一行代码时写的,在写这篇文章突然想起来,现在回头看看,即使是入门,代码写的也是真心好。条例清晰,接口回调,方法封装,虽然小但是逻辑很清晰。

实践

我们先开下大体的思路流程:

流程图

大致流程就是这样。其实说白了就是下载任务然后安装。这里核心是下载部分那么我就可以用后台异步断电续传文件下载这个例子下载(已经合并2个例子放到一个工程中了)。在这里我在提供例外一种方法。

  • UpdateDownLoadListener这个类就是下载回调的监听
public interface UpdateDownLoadListener {

    /**
     * 下载请求开始回调
     */
    public void onStarted();

    /**
     * 进度更新回调
     *
     * @param progress
     * @param downloadUrl
     */
    public void onProgressChanged(int progress, String downloadUrl);

    /**
     * 下载完成回调
     *
     * @param completeSize
     * @param downloadUrl
     */
    public void onFinished(int completeSize, String downloadUrl);

    /**
     * 下载失败回调
     */
    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();//阻塞我们当前的线程
                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();
        }
    }

    /**
     * 包含了下载过程中所有可能出现的异常情况
     */
    public enum FailureCode {
        UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, Json, Interrupted
    }

    /**
     * 文件下载 将消息传递个主线程
     */
    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);
            }
        }

        /**
         * 获取一个消息对象
         *
         * @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);
                }
            }

        }
    }
}

这段代码有点长,简单来看就开启线程下载任务,根据现在的状态利用handle发送各种状态的消息,然后利用接口回调,调用接口,再让启动下载的类也就是我们后台下载的服务类去实现接口并处理相应的逻辑。

  • UpdateDownManager 下载调度管理器,调用我们的UpdateDownLoadRequest,也是下载任务的入口,在这里我们为了为了健壮性加入一切判断。并将下载任务设置单例模式,并用线程池,方便管理闲扯避免僵尸线程。
  • UpdateDownService这里就是我们启动下载任务的地方
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来告知用户下载进度
     *
     * @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()方法中启动下载,下载完成结束当前服务。然后用Notification通知用户,在用系统自带的api安装。最后就是在Activity启动服务下载任务就能进行了。篇幅较长Activity的代码我就不粘贴出来了。


结束

相比在第一行代码中的,这段代码多了做了一些逻辑上的处理,是代码更健壮性。原理都是相同的,如果你是在小范围应用或是自己做的练手应用想加入自动更新功能,就可以将这些代码封装到自己的工具类中,当然距离成熟框架还是有很大的距离,比如我们更新要和服务器版本对比。服务器推送新版本功能等等,但是思路都是这样的。在这里我只是抛砖引玉。身为小白的我,还需努力。 后续会更新在线更新等热修复的文章敬请期待。

写的不好大家多多谅解。如有错误真心希望大家提出来。最后希望大家一起进步。加油!!!

源码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,081评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,095评论 4 62
  • 便秘小秘方:一片生姜、一个创可贴、两个透明液体,最快的三天以内大便通畅,最慢的也不会超过一个礼拜一般人都知道,生姜...
    菱520阅读 288评论 0 0
  • 某月的某一天,我换了新的工作环境,工作到第四天,有个男孩子跑过来对我说:我觉得你很漂亮,我们交个朋友吧,因为我的直...
    易朱阅读 201评论 0 0
  • 如果她不回我 如果她不理我 如果她真的放弃 如果她觉得我很浅薄 如果她不再相信我 我该怎么办? 我不知道 静待时间...
    生呼吸阅读 179评论 0 0