Android App更新模块

作者: 小追兵 | 来源:发表于2016-11-24 21:55 被阅读4381次
    • 应用更新有两种:
      1、 检测到更新,App实现下载功能,下载安装。
      2、检查到更新,跳转应用市场下载。

    • 两种方法优缺点:
      第一种:

    • 优点:是,下载统一,便于版本统一升级,只要用户点击更新,可以保证其实官方最新的包。

    • 缺点:实现起来稍微优点麻烦,如果写不好,下载的包可能无法解析,有些机型导致下载了无法安装的问题。

      第二种:

      • 优点:实现简单,几乎不用写几行代码。
      • 缺点:每个手机安装的应用市场不同,不同应用市场的审核速度不一样,导致用户用的版本参差不齐,提示更新后,用户去应用市场下载安装,发现还提示更新,是因为应用市场上不是最新包。

    下面先给出第二种实习方法,再给第一种方法(实践后结论下载稳定)

    //检查到需要更新,调用
     ToolUtil.gotoMarket(mActivity, mActivity.getPackageName());
    

     /**
         * 打开市场
         */
        public static void gotoMarket(Context context, String pck) {
            if (!isHaveMarket(context)) {
                Toast.makeText(context, "您手机中没有安装应用市场!", Toast.LENGTH_SHORT).show();
                return;
            }
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("market://details?id=" + pck));
            if (intent.resolveActivity(context.getPackageManager()) != null) {
                context.startActivity(intent);
            }
        }
    

     /**
         * 是否安装市场
         */
        private static boolean isHaveMarket(Context context) {
            Intent intent = new Intent();
            intent.setAction("android.intent.action.MAIN");
            intent.addCategory("android.intent.category.APP_MARKET");
            PackageManager pm = context.getPackageManager();
            List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
            return infos.size() > 0;
        }
    

    第二种到此就完了。可以验证下,前题是,你的app要在应用商场上线。没有上线的话,是找不到对应的APP的。



    那么下来重点说下第一种:

    先看效果图:

    下载效果图

    这里先说,图中的dialog,用的是github上 material-dialogs 。(这了忽略对话框的代码)

    从左到右,检查更新-->是否需要下载-->点击下载-->状态栏和对话框同时走下载进度条-->最后下载成功。

    • 提出下载的主要代码。

    // 启动Service 开始下载
    AppUpdateService.startUpdate(mContext, url, fileName, new AppUpdateService.OnProgressListener() {
                @Override
                public void onProgress(int progress) {
                    //更新对话框进度条
                    mDialog.setProgress(progress);
                }
    
                @Override
                public void onSuccess(boolean isSuccess) {
                    mDialog.dismiss();
                    //失败提示
                    if (!isSuccess) {
                        ToastTools.toastLong("更新不成功");
                    }
                }
            });
    

    这里用到的InternetService类,IntentService是Service的子类,
    IntentService实现了work非阻塞线程,适合做耗时操作,可以自行Google or 百度 他们的区别和用法。

    package com.hundun.yanxishe.service;
    
    import android.app.IntentService;
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.net.Uri;
    import android.widget.RemoteViews;
    
    import com.hundun.yanxishe.R;
    import com.hundun.yanxishe.tools.LogTool;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    /**
     * An {@link IntentService} subclass for handling asynchronous task requests in
     * a service on a separate handler thread.
     * <p>
     * helper methods.
     */
    public class AppUpdateService extends IntentService {
        private static final String ACTION_UPDATE = "com.hundun.yanxishe.service.action.update";
        private static final String EXTRA_URL = "com.hundun.yanxishe.service.extra.url";
        private static final String EXTRA_FILE_NAME = "com.hundun.yanxishe.service.extra.file.name";
        private boolean isRunning = false;
        private NotificationManager updateNotificationManager;
        private Notification updateNotification;
        private PendingIntent updatePendingIntent;
        private static OnProgressListener mProgressListener;
    
        public interface OnProgressListener {
            void onProgress(int progress);
    
            void onSuccess(boolean isSuccess);
        }
    
        public AppUpdateService() {
            super("AppUpdateService");
        }
    
    
        @Override
        protected void onHandleIntent(Intent intent) {
            if (intent != null) {
                final String action = intent.getAction();
                if (ACTION_UPDATE.equals(action)) {
                    final String param1 = intent.getStringExtra(EXTRA_URL);
                    final String param2 = intent.getStringExtra(EXTRA_FILE_NAME);
                    startDownloade(param1, param2);
                }
            }
        }
    
        @Override
        public void onDestroy() {
            mProgressListener = null;
            super.onDestroy();
        }
    
        /**
         * Starts this service to perform action Baz with the given parameters. If
         * the service is already performing a task this action will be queued.
         *
         * @see IntentService
         */
        public static void startUpdate(Context context, String param1, String param2, OnProgressListener pregressListener) {
            mProgressListener = pregressListener;
            Intent intent = new Intent(context, AppUpdateService.class);
            intent.setAction(ACTION_UPDATE);
            intent.putExtra(EXTRA_URL, param1);
            intent.putExtra(EXTRA_FILE_NAME, param2);
            context.startService(intent);
        }
    
        private void startDownloade(String url, String fileName) {
            LogTool.debug("开始升级----" + url + "---" + fileName);
            if (isRunning) {
                return;
            }
            isRunning = true;
            initRemoteView();
    
            try {
                boolean isSuccess = downloadUpdateFile(url, fileName);
                if (mProgressListener != null) {
                    mProgressListener.onSuccess(isSuccess);
                }
                if (isSuccess) {
                    Uri uri = Uri.fromFile(new File(fileName));
                    Intent installIntent = new Intent(Intent.ACTION_VIEW);
                    installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    installIntent.setDataAndType(uri, "application/vnd.android.package-archive");
                    startActivity(installIntent);
                    try {
                        updateNotificationManager.cancel(0);
                    } catch (Exception ex) {
                        LogTool.error(ex.getMessage(), ex);
                    }
                } else {
                    Notification notification = new Notification.Builder(AppUpdateService.this)
                            .setContentTitle(getString(R.string.app_name))
                            .setContentText("下载失败")
                            .setSmallIcon(R.drawable.download)
                            .build();
                    updateNotificationManager.notify(0, notification);
                }
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 初始化状态栏进度条
         */
        private void initRemoteView() {
            try {
                updateNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                //状态栏提醒内容
                updateNotification = new Notification.Builder(this)
                        .setTicker("版本更新下载")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.drawable.download).build();
                updatePendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, RemoteViews.class), 0);
                updateNotification.contentIntent = updatePendingIntent;
                //状态栏提醒内容
                updateNotification.contentView = new RemoteViews(getApplication().getPackageName(), R.layout.progress);
                updateNotification.contentView.setProgressBar(R.id.progressBar1, 100, 0, false);
                updateNotification.contentView.setTextViewText(R.id.textView1, "0%");
                // 发出通知
                updateNotificationManager.notify(0, updateNotification);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 下载文件
         *
         * @param downloadUrl
         * @param filepath
         * @return
         * @throws Exception
         */
        private boolean downloadUpdateFile(String downloadUrl, String filepath) {
            try {
                int downloadCount = 0;
                int currentSize = 0;
                long totalSize = 0;
                int updateTotalSize = 0;
                boolean result = false;
                HttpURLConnection httpConnection = null;
                InputStream is = null;
                FileOutputStream fos = null;
                File temp = new File(filepath + ".tmp");
                if (temp.getParentFile().isDirectory()) {
                    temp.getParentFile().mkdirs();
                }
                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(20000);
                    httpConnection.setReadTimeout(120000);
                    updateTotalSize = httpConnection.getContentLength();
                    if (httpConnection.getResponseCode() == 404) {
                        throw new Exception("fail!");
                    }
                    is = httpConnection.getInputStream();
                    fos = new FileOutputStream(temp, false);
                    byte buffer[] = new byte[4096];
                    int readsize = 0;
                    while ((readsize = is.read(buffer)) > 0) {
                        fos.write(buffer, 0, readsize);
                        totalSize += readsize; // 为了防止频繁的通知导致应用吃紧,百分比增加10才通知一次
                        if ((downloadCount == 0) || (int) (totalSize * 100 / updateTotalSize) - 1 > downloadCount) {
                            downloadCount += 1;
                            try {
                                updateNotification.contentView.setProgressBar(R.id.progressBar1, 100, (int) totalSize * 100 / updateTotalSize,
                                        false);
                                updateNotification.contentView.setTextViewText(R.id.textView1, (int) totalSize * 100 / updateTotalSize + "%");
                                updateNotification.contentIntent = updatePendingIntent;
                                updateNotificationManager.notify(0, updateNotification);
                                if (mProgressListener != null) {
                                    mProgressListener.onProgress((int) totalSize * 100 / updateTotalSize);
                                    System.out.println("AppUpdateService.downloadUpdateFile"+(int) totalSize * 100 / updateTotalSize);
                                }
                            } catch (Exception ex) {
                                LogTool.error(ex.getMessage(), ex);
                            }
                        }
                    }
                    temp.renameTo(new File(filepath));
                    temp.delete();
                } finally {
                    if (httpConnection != null) {
                        httpConnection.disconnect();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (fos != null) {
                        fos.close();
                    }
                    result = updateTotalSize > 0 && updateTotalSize == totalSize;
                    if (!result) { //下载失败或者为下载完成
                        new File(filepath).delete();
                    }
                }
                return result;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    }
    
    

    贴出状态栏的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
                  android:gravity="center_vertical"
                  android:orientation="horizontal"
        >
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:paddingLeft="5dp"
            android:scaleType="centerInside"
            android:src="@drawable/download"
            />
    
    
        <ProgressBar
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="4dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            android:progressDrawable="@drawable/progressbar_style"
            />
    
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="40dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:text="100%"
            android:textColor="#888888"
            android:textSize="14sp"/>
    
    </LinearLayout>
    

    这里贴代码比较多,没什么讲解,但是如果你对照着每行代码写下来,应该不需要我讲解就会明白了。

    核心代码已经全部给出,如有疑问,可以“简书”找 “小追兵”留言。

    注意

    状态栏的图片setSmallIcon(R.drawable.download),这里的图片不要太大(我的6464 px),还有注意放在drawable文件夹下,否则可能会异报常。改了,如果还报异常,clean项目应该就好了。*

    文件从网络下载,写流的部分,不是我写,搬砖搬来的。

    最后:有需要Shadowsock翻墙账号可以私聊。

    相关文章

      网友评论

      • d0dcced150d4:mProgressListener.onProgress((int) totalSize * 100 / updateTotalSize);这个地方应该改成mProgressListener.onProgress((int) (totalSize * 100 / updateTotalSize));
        如果文件太大上面一种写法会导致进度显示为负数
      • bimromatic:是什么意思
      • bimromatic:你好我想问下上面 ACTION_UPDATE;EXTRA_URL";EXTRA_FILE_NAME
        5c5a225672e6:作者應該是拿來當key值使用,你可以想像成Bundle要代值前方的 bundle key。希望會比較好理解
      • VH2016:还有一种方法:消息机制
        小追兵:@VH2016 有相关推荐吗?这个是项目中用到的一个。我提取出来,方便大家参考
      • qige1991:你好,能给一下源码吗1312386674@qq.com,多谢
        小追兵:@qige1991 代码基本都在了,源码在项目中,没有单独的模块
        小追兵:@qige1991 代码基本上都贴出了,都是核心代码,完整的demo没有呢,都在项目里了
      • 谁的春春不迷茫:你好,这个下载代码有断点续传功能吗
        小追兵: @技术的拾荒者 没做断点续传

      本文标题:Android App更新模块

      本文链接:https://www.haomeiwen.com/subject/snkipttx.html