App自动更新基础实现方式

         本篇文章主要实现在应用内发现新版本,用户点击下载apk,同时在通知栏下实现下载进度更新,下载完成后自动弹出安装窗口等等功能,来源于慕课网的视频整理,适合新手,做了详细的注释说明

          不提供源码,但源码已全部奉上:

回调接口

/**
 * Created by Administrator on 2017/10/1 0001.
 * 回调接口:实现各种事件的监听回调
 */
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();
}

Request

/**
 * 真正负责我们文件的下载和线程间通信
 */
public class UpdateDownloadRequest implements Runnable {
    //下载路径
    private String downloadUrl;
    //文件保存路径
    private String localFilePath;
    //事件回调
    private UpdateDownloadListener downloadListener;
    //下载的标志
    private boolean isDownloading = false;
    //文件长度
    private long currentLength;

    //Handler
    private DownloadResponseHandler downloadHandler;

    //构造方法对我们需要的参数进行初始化
    public UpdateDownloadRequest(String downloadUrl, String localFilePath, UpdateDownloadListener downloadListener) {
        this.downloadUrl = downloadUrl;
        this.localFilePath = localFilePath;
        this.downloadListener = downloadListener;
        this.isDownloading = true;//构造方法完成以后表明已经开始下载

        this.downloadHandler = new DownloadResponseHandler();
    }

    //真正的去建立连接的方法
    private void makeRequest() throws InterruptedIOException, IOException {

        if (Thread.currentThread().isInterrupted()) {//如果我们的当前线程没有被打断(即在正在后台运行)
            try {
                //创建URL对象
                URL url = new URL(downloadUrl);
                //通过url对象获取Connection对象:HttpURLConnection不需要导入第三方的框架就可以加载
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                //设置一些属性
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(5000);
                connection.setRequestProperty("Connection", "Keep-Alive");//保持连接
                connection.connect();//阻塞我们的当前线程(所以会放在子线程中进行)

                //连接建立以后获取文件的长度
                currentLength = connection.getContentLength();
                //开始下载
                if (Thread.currentThread().isInterrupted()) {//健壮性判断
                    //真正完成文件的下载
                    downloadHandler.sendResponseMessage(connection.getErrorStream());
                }
            } catch (IOException e) {
                //有异常:抛出
                throw e;
            }
        }
    }

    //完成连接的建立
    @Override
    public void run() {
        try {
            makeRequest();
        } catch (InterruptedIOException e) {
        } catch (IOException e) {
        }
    }

    /**
     * 格式化数字:保证数字精确到小数点后两位
     */
    private String getTwoPointFloatStr(float value) {
        DecimalFormat fnum = new DecimalFormat("0.00");
        return fnum.format(value);
    }

    /**
     * 异常类型:包含下载过程中所有可能出现的异常情况
     */
    public enum FailureCode {
        UnKnowHost,
        Socket,
        ScoketTimeout,
        ConnectTimeout,
        IO,
        HttpResponse,
        JSON,
        Interrupted
    }

    /**
     * 用来真正的去下载文件,并发送消息和回调接口
     * 完成我们消息的发送方法封装、消息处理方法的封装、文件下载方法封装
     */
    public class DownloadResponseHandler {
        //静态常量表明每种事件
        protected static final int SUCCESS_MESSAGE = 0;
        protected static final int FAILURE_MESSAGE = 1;
        protected static final int START_MESSAGE = 2;
        protected static final int FINISH_MESSAGE = 3;
        protected static final int NETWORK_OFF = 4;
        private static final int PROGRESS_CHANGE = 5;

        private int mCompleteSize = 0;
        private int progress = 0;

        private Handler handler;//真正的完成线程间的通信

        private DownloadResponseHandler() {
            //创建依附于主线程的Handler:处理我们所有的事件
            handler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    handlerSelfMessage(msg);
                }
            };
        }

        /**
         * 用来发送不同的消息类型对象对象
         */
        protected void sendFinishMessage() {
            sendMessage(obtainMessage(FINISH_MESSAGE, null));
        }

        private void sendProgressChangeMessage(int progress) {
            sendMessage(obtainMessage(PROGRESS_CHANGE, new Object[]{progress}));
        }

        protected void sendFailureMessage(FailureCode failureCode) {
            sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode}));
        }

        protected void sendMessage(Message msg) {
            if (null != null) {
                //调用handler的方法
                handler.sendMessage(msg);
            } else {
                //为空直接处理我们的message
                handlerSelfMessage(msg);
            }
        }

        /**
         * 获取一个消息对象
         */
        protected Message obtainMessage(int responseMessage, Object response) {
            Message msg = null;
            if (null != handler) {
                //不为空直接调用handler的方法
                msg = handler.obtainMessage(responseMessage, response);
            } else {
                //为空,我们就调取Message.obtain()获取Message,上面的最终也是调用这个方法获取Message
                msg = Message.obtain();
                msg.what = responseMessage;
                msg.obj = response;
            }
            return msg;
        }

        //处理我们的各种消息:根据不同类型发送不同消息
        protected void handlerSelfMessage(Message msg) {
            Object[] response;
            switch (msg.what) {
                case FAILURE_MESSAGE:
                    response = (Object[]) msg.obj;
                    handlerFailureMessage(((FailureCode) response[0]));
                    break;

                case PROGRESS_CHANGE:
                    response = (Object[]) msg.obj;
                    handlerProgressChangeMessage(((Integer) response[0]).intValue());
                    break;

                case FINISH_MESSAGE:
                    onFinish();
                    break;
            }
        }

        /**
         * 各种消息的处理逻辑
         */
        protected void handlerProgressChangeMessage(int progress) {
            downloadListener.onProgressChanged(progress, "");
        }

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

        //外部接口的回调
        public void onFinish() {
            downloadListener.onFinished(mCompleteSize, "");
        }

        public void onFailure(FailureCode failureCode) {
            downloadListener.onFailure();
        }

        //文件下载方法,会发送各种类型的事件:下载开始、下载进度更新、下载成功。。。
        void sendResponseMessage(InputStream is) {
            //建立文件读写流
            RandomAccessFile randomAccessFile = null;
            mCompleteSize = 0;//开始的完成进度0

            //
            try {
                //定义自己一次读取多少到缓存,防止内存撑爆
                byte[] buffer = new byte[1024];
                //读写长度
                int length = -1;
                int limit = 0;

                randomAccessFile = new RandomAccessFile(localFilePath, "rwd");//文件路径,可读可写模式
                //循环从流中读取字节数组
                while ((length = is.read(buffer)) != -1) {//不等于-1表明还没有读完

                    //当前是下载状态,把我们读到的字节写到本地
                    if (isDownloading) {
                        randomAccessFile.write(buffer, 0, length);//读写的长度
                        //累加长度
                        mCompleteSize += length;
                        //计算当前的下载进度
                        if (mCompleteSize < currentLength) {//完成的大小小于总大小,说明正在下载
                            progress = (int) Float.parseFloat(getTwoPointFloatStr(mCompleteSize / currentLength));

                            //发送进度:limit / 30 == 0表示隔30次更新一次进度,不用读一个字节就去更新下载进度
                            if (limit / 30 == 0 && progress <= 100) {//限制我们notification的更新频率
                                sendProgressChangeMessage(progress);
                            }
                            limit++;
                        }
                    }
                }

                //文件读取完成,发送完成通知
                sendFinishMessage();

            } catch (IOException e) {
                //出现异常,发送失败信息
                sendFailureMessage(FailureCode.IO);
            } finally {
                try {
                    //记得关闭流
                    if (null != is) {
                        is.close();
                    }

                    //关闭读写
                    if (null != randomAccessFile) {
                        randomAccessFile.close();
                    }
                } catch (IOException e) {
                    //发送Io异常
                    sendFailureMessage(FailureCode.IO);
                }
            }
        }
    }
}

/**
 * 下载调度管理器,调用我们的UpdateDownloadRequest,完成真正的下载,有两种实现方式:
 * 1.直接起一个线程,线程中传入我们的UpdateDownloadRequest进行下载
 * 2.初始化一个线程池,将我们的任务UpdateDownloadRequest添加到线程池当中,使用线程池实现UpdateManager
 */
public class UpdateManager {
    //单例模式
    private static UpdateManager manager;
    //线程池
    private ThreadPoolExecutor threadPoolExecutor;
    //添加的任务
    private UpdateDownloadRequest request;

    private UpdateManager() {
        threadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    }

    //加载单例模式
    static {
        manager = new UpdateManager();
    }

    //获取单例模式
    public static UpdateManager getInstance() {
        return manager;
    }

    //开始下载
    public void startDownloads(String downloadUrl, String localPath, UpdateDownloadListener listener) {
        if (null != request) {
            return;
        }

        //检查路径是否合法
        checkLocalFilePath(localPath);

        //request为null时,将request添加到我们的线程池当中
        request = new UpdateDownloadRequest(downloadUrl, localPath, listener);
        //开始真正的去下载任务
        Future<?> future = threadPoolExecutor.submit(request);

    }

    //用来检查文件路径是否已经存在
    private void checkLocalFilePath(String localPath) {
        //获取问价夹的位置
        File dir = new File(localPath.substring(0, localPath.lastIndexOf("/") + 1));
        //检查文件夹是否存在
        if(!dir.exists()){
            //不存在创建文件夹
            dir.mkdir();
        }

        File file = new File(localPath);
        if(!file.exists()){
            //文件不存在则创建
            try {
                file.createNewFile();
            } catch (Exception e) {
                e.printStackTrace();
            }
 

/**
 * 真正调用我们的UpdateManager完成下载功能
 * 使用Serice:因为直接在我们的Activity启动线程,我们的Activity随时会被回收,所以就会产生僵尸
 * 线程,所以用Service在后天默默下载
 */
public class UpdateService extends Service {
    private String apkUrl;
    private String filePath;
    private NotificationManager notificationManager;
    private Notification mNotification;
    private PendingIntent contentIntent;

    @Override
    public void onCreate() {

        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //文件路径
        filePath = Environment.getExternalStorageDirectory() + "/okamiy/QJFund.apk";
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (null == intent) {
            //表明此次参数不对,下载失败,报异常
            notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed_msg), 0);
            //失败要停掉我们的Service
            stopSelf();
        }

        //取出我们的Url
        apkUrl = intent.getStringExtra("apkUrl");
        //通知用户下载开始了
        notifyUser(getString(R.string.update_download_start), getString(R.string.update_download_start_msg), 0);//刚开始进度为0
        startDownload();
        return super.onStartCommand(intent, flags, startId);
    }

    //下载:在回调中写你的逻辑
    private void startDownload() {
        UpdateManager.getInstance().startDownloads(apkUrl, filePath, new UpdateDownloadListener() {
            @Override
            public void onStarted() {

            }

            //进度改变通知用户
            @Override
            public void onProgressChanged(int progress, String downloadUrl) {
                notifyUser(getString(R.string.update_download_processing), getString(R.string.update_download_processing), progress);
            }

            //下载完成通知用户
            @Override
            public void onFinished(int completeSize, String downloadUrl) {
                notifyUser(getString(R.string.update_download_finish), getString(R.string.update_download_finish), 100);
                //下载完成停止服务
                stopSelf();
            }

            //下载失败通知用户
            @Override
            public void onFailure() {
                notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed_msg), 0);//失败进度为0
                //停止服务
                stopSelf();
            }
        });
    }

    /**
     * 更新我们的Notification来告知用户当前的下载进度
     *
     * @param result
     * @param reason
     * @param progress
     */
    private void notifyUser(String result, String reason, int progress) {
        //创建Notification:采用NotificationCompat兼容者构造模式创建,Android会帮我们做各个系统的Notification兼容,
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        //设置参数
        builder.setSmallIcon(R.drawable.sample_footer_loading)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.sample_footer_loading_progress))
                .setContentTitle(getString(R.string.app_name));

        //判断是否下载
        if (progress > 0 && progress < 100) {//正在下载,显示我们的progress
            builder.setProgress(100, progress, false);//最大值,当前值,false表明我们需要具体的进度而不是转圈
        } else {
            //下载完成或者失败,隐藏progress
            builder.setProgress(0, 0, false);
        }

        //可以被自动清除掉
        builder.setAutoCancel(true);
        //系统当前时间
        builder.setWhen(System.currentTimeMillis());
        builder.setTicker(result);
        //进度完成就去安装,否则就是一个空的Intent
        builder.setContentIntent(progress >= 100 ? getContentIntent()
                : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));

        mNotification = builder.build();
        //发送Notification
        notificationManager.notify(0, mNotification);//id :0

    }

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

    /**
     * 调用系统的安装程序进行安装
     * @return
     */
    public PendingIntent getContentIntent() {
        //创建我们下载好的文件
        File apkFile =new File(filePath);
        //创建Intent
        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");//data:文件路径 ,type:应用安装程序

        //包装intent
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);//参数4:标志位
        return pendingIntent;
    }
}

/**
 * 自定义CommonDialog
 */
public class CommonDialog extends Dialog {

    private TextView titleTV;
    private TextView contentTV;
    private Button sureBTN;
    private Button cancelBTN;

    private String title;//标题
    private String content;//内容
    private String leftBtnText;//左边按钮text
    private String rightBtnText;//右边按钮text

    private OnYesClickListener onYesClickListener;
    private OnNoClickListener onNoClickListener;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setLeftBtnText(String leftBtnText) {
        this.leftBtnText = leftBtnText;
    }


    public void setRightBtnText(String rightBtnText) {
        this.rightBtnText = rightBtnText;
    }


    public void setOnYesClickListener(OnYesClickListener onYesClickListener) {
        this.onYesClickListener = onYesClickListener;
    }

    public void setOnNoClickListener(OnNoClickListener onNoClickListener) {
        this.onNoClickListener = onNoClickListener;
    }

    public CommonDialog(Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.dialog_layout);
        setCanceledOnTouchOutside(false);//点击空白不能取消dialog
        initView();
        initData();
        initEvent();
    }

    /**
     * 初始化事件
     */
    private void initEvent() {
        sureBTN.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onYesClickListener!=null){
                    onYesClickListener.yesClick();
                }
            }
        });
        cancelBTN.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onNoClickListener!=null){
                    onNoClickListener.noClick();
                }
            }
        });
    }

    /**
     * 初始化数据
     */
    private void initData() {
        titleTV.setText(title);
        contentTV.setText(content);
        sureBTN.setText(leftBtnText);
        cancelBTN.setText(rightBtnText);
    }

    /**
     * 初始化控件
     */
    private void initView() {
        titleTV= (TextView) findViewById(R.id.titleTV);
        contentTV= (TextView) findViewById(R.id.contentTV);
        sureBTN= (Button) findViewById(R.id.sureBTN);
        cancelBTN= (Button) findViewById(R.id.cancelBTN);
    }

    /**
     * 设置确定按钮和取消按钮的接口回调
     */
    public interface OnYesClickListener {
        void yesClick();
    }
    public interface OnNoClickListener{
        void noClick();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/titleTV"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="Title"
        android:textSize="18sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#000000" />

    <TextView
        android:id="@+id/contentTV"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="80dp"
        android:padding="10dp"
        android:text="content"
        android:textSize="16sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#000000" />

    <LinearLayout
        android:layout_marginTop="2dp"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/sureBTN"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_weight="1"
            android:text="立即更新"
            android:textSize="16sp" />

        <Button
            android:id="@+id/cancelBTN"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            android:text="下次再说"
            android:textSize="16sp" />
    </LinearLayout>
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
    <Button
        android:id="@+id/login"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="检测更新"
        />
</RelativeLayout>


MainActivity

public class MainActivity extends AppCompatActivity {

    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.login);

        //1.点击按钮更新
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkVersion();
            }
        });

        //2.app启动的时候检查更新
        checkVersion();
    }

    /**
     * 检查更新:发送http请求到服务器,服务器返回最新的版本号,如果当期应用的版本号小于返回的版本号,则进行应用
     * 更新,反之则不需要更新
     */
    private void checkVersion() {
        final CommonDialog dialog = new CommonDialog(this);
        dialog.setTitle("ApkUpdate");
        dialog.setContent("发现新版本,请及时更新");
        dialog.setLeftBtnText("立即更新");
        dialog.setRightBtnText("稍后再说");
        dialog.setOnYesClickListener(new CommonDialog.OnYesClickListener() {
            @Override
            public void yesClick() {
                //启动Service
                Intent intent = new Intent(MainActivity.this, UpdateService.class);
                //传入版本号:取服务器apk地址
                intent.putExtra("apkUrl", "换成你的apk地址即可");
                //启动Service
                startService(intent);
            }
        });

        dialog.setOnNoClickListener(new CommonDialog.OnNoClickListener() {
            @Override
            public void noClick() {
                dialog.dismiss();
            }
        });
        dialog.show();
    }
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值