应用自动更新封装-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的代码我就不粘贴出来了。


结束

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

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

源码地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

多线程下载

楼主三年磨剑(当然不是磨着一把剑),倾血奉献Android多线程下载Demo。有的人就问了“怎么写来写去还是Demo?”,因为老哥我实在太忙了, 每天写一点...

1918
来自专栏向治洪

android优化之省电

Android程序中耗电最多的地方在以下几个方面 : 1、 大数据量的传输。 2、 不停的在网络间切换。 3、 解析大量的文本数据。 那么我们怎么样来改...

19510
来自专栏移动端周边技术扩展

iOS打开系统功能对应的URL

1813
来自专栏Android源码框架分析

十分钟了解Android触摸事件原理(InputManagerService)

从手指接触屏幕到MotionEvent被传送到Activity或者View,中间究竟经历了什么?Android中触摸事件到底是怎么来的呢?源头是哪呢?本文就直观...

3824
来自专栏wOw的Android小站

[Android][Framework] 无障碍快捷方式相关代码

问题:无障碍快捷方式(Accessibility Shortcut)打开不生效。

2471
来自专栏xingoo, 一个梦想做发明家的程序员

网络嗅探器

网络嗅探器:把网卡设置成混杂模式,并可实现对网络上传输的数据包的捕获与分析。 原理:   通常的套接字程序只能响应与自己MAC地址相匹配的 或者是 广播形式发出...

43610
来自专栏程序员互动联盟

【开发指南】如何为nexus 5编译固件

nexus 5是谷歌的亲儿子,而android的源码是开源的,那如果我有一个nexus 5手机,为何不自己为nexus 5编译软件呢? 开搞,本文假定已经有an...

40012
来自专栏FreeBuf

SniffAir:无线渗透测试框架

SniffAir是一个开源的无线安全框架,可帮助你轻松解析被动收集的无线数据并发起复杂的无线渗透测试。此外,它还可以处理大型的或多个pcap文件,执行交叉检查和...

922
来自专栏CDN及云技术分享

Openssl状态机的实现

Openssl是通过“握手“建立加密信道,在该信道双方的身份都是合法的,并且传输数据都是密文传输。Openssl握手通过客户端和服务端互相交换信息计算出secr...

3793
来自专栏Java与Android技术栈

用kotlin来实现dsl风格的编程

Anko 是一个 DSL (Domain-Specific Language), 它是JetBrains出品的,用 Kotlin 开发的安卓框架。它主要的目的是...

912

扫码关注云+社区

领取腾讯云代金券