adnroid 使用service。 更新apk

项目中要使用到版本更新的功能,参考了开源中国源码,也遇到一些问题,再次记录一下:

遇到的问题:
1. 1.notifacation.bulider怎么使用?
2. notifacation和notifacation.bulider有什么区别?
3. 文件创建遇到的坑。
4. bind和service传值。
5. 退出时要注意的事项。

step1:
开始需要比对服务器的apk版本。
json结构是这样的

{"versionCode":"1","vsesionContent":"更新apk"}

查看本身的apk版本:

 versionCode = Application.getContext().getPackageManager().getPackageInfo(packageName, 0).versionCode;

step2:
如果服务器版本号大于当前版本号开始下载。

在这里先说明service有两种启动方式:
第一种:startService(Intent);
第二种:bindService(Intent,ServiceConnection,flags);
两种方式关闭不一样,需要单独关闭才可以。
第一种:
Intent intent=new Intent(content,DownloadService.class);
content.startService();

第二种如果需要和activity交互数据的话可以使用。
以下是我写的启动方式
 public static void openDownLoadService(Context context, String downurl,
                                           String tilte) {
        final ICallbackResult callback = new ICallbackResult() {

            @Override
            public void OnBackResult(Object s) {
            }
        };

        ServiceConnection conn = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
                binder.addCallback(callback);
                binder.start();

            }
        };
        Intent intent = new Intent(context, DownloadService.class);
        intent.putExtra(DownloadService.BUNDLE_DOWNLOAD_URL, downurl);
        intent.putExtra(DownloadService.BUNDLE_KEY_TITLE, tilte);
        context.startService(intent);
        context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

我自己写了一个接口,从onServiceConnected获取service返回的数据。
service 执行的顺序是:
oncreate->onStartCommand->Binder->onServiceConnected

在OnCreate进行一些初始化的数据。
比如:实例化 DownloadBinder,以及NotificationManager

在onServiceConnected判断是否已经可以对activity进行通讯了
如果已经连接上了就开启线程来下载apk文件

   DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
                binder.addCallback(callback);
                binder.start();
                Log.d("aa", "onServiceConnected");

接下来放出 DownloadBinderBinder类

public class DownloadBinder extends Binder {



        public void start() {
            Log.d("aa","Binder");
            if (downLoadThread == null || !downLoadThread.isAlive()) {
                progress = 0;
                **1**.setUpNotification();
                new Thread() {
                    public void run() {
                        // 下载
                **2**  startDownload();
                    }
                }.start();
            }
        }

        public void cancel() {
            canceled = true;
        }

        public int getProgress() {
            return progress;
        }

        public boolean isCanceled() {
            return canceled;
        }

        public boolean serviceIsDestroy() {
            return serviceIsDestroy;
        }

        public void cancelNotification() {
            mHandler.sendEmptyMessage(2);
        }

        public void addCallback(ICallbackResult callback) {
            DownloadService.this.callback = callback;
        }


    }

先看到 1:

因为我们要在通知栏自定义下载的进度,所有我们要自己写入布局.

那么我们会使用到该类 NotificationCompat.Builder
为什么不直接使用 Notification 是因为有些方法已经hide了,比如 这个setLatestEventInfo();
setLatestEventInfo()方法来为通知初始化布局和数据。

接下来是我写的:

 /** * 设置notification */
    private void setUpNotification() {
        CharSequence tickerText = "准备下载";
        long when = System.currentTimeMillis();
        NotificationBuilder = new NotificationCompat.Builder(mContext);
        /** * 这个属性一定要加.不然显示不了 */
        NotificationBuilder.setSmallIcon(R.mipmap.logoicon);
        NotificationBuilder.setWhen(when);
        NotificationBuilder.setTicker(tickerText);
        NotificationBuilder.setOngoing(true);
        /** * 自定义试图 */
        contentView = new RemoteViews(getPackageName(), R.layout.notification_download_show);
        contentView.setTextViewText(R.id.tv_download_state, mTitle);
        NotificationBuilder.setContent(contentView);


        /** * 设置隐身跳转---- */
// Intent intent = new Intent(this, MainActivity.class);
// PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
// NotificationBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
    }

详细的资料可以参考

http://blog.leanote.com/post/554ca3792168cb3a61000001

然后看到 2

前提:因为service是运行在主线程中所以我们在service开启线程进行下载apk操作,然后在通过安装apk 进行更新

这里就需要设计到一个文件创建的问题了, 因为我们再次安装下载好的apk时需要一个制定下载路径。

看到这个方法

 public static void installAPK(Context context, File file) {
        if (file == null || !file.exists())
            return;
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file),
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }

这里需要File 这个file就是我们下载apk的地方。也是因为文件这个问题,花费了我几个小时。
原来的代码:

  File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
            if (!file.exists()) { file.mkdir(); }
            String apkFile = saveFileName;
            File saveFile = new File(apkFile);

现在代码

  File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
            if (!file.exists()) { file.mkdirs(); }
            String apkFile = saveFileName;
            File saveFile = new File(apkFile);

有没有看出来有什么不同。

file.mkdirs(); file.mkdir(); 这个地方少些一个s。我心都凉了
第一个函数 如果是 xxx/load/xxx.apk 是可以创建的。支持多文件创建
第二个函数,只支持一个文件一个文件创建。

如果文件创建对了,就进行网络下载apk的操作了
代码如下:

  /** * 下载文件 * * @param downloadUrl * @param saveFile * @return * @throws IOException */
    private long downloadUpdateFile(String downloadUrl, File saveFile) throws IOException {
        int downloadCount = 0;
        int currentSize = 0;
        long totalSize = 0;
        int updateTotalSize = 0;

        HttpURLConnection httpConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;

        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(10000);
            httpConnection.setReadTimeout(20000);
            httpConnection.setRequestMethod("GET");
            updateTotalSize = httpConnection.getContentLength();
            if (httpConnection.getResponseCode() == 404) {
                throw new Exception("fail!");
            }
            is = httpConnection.getInputStream();
            fos = new FileOutputStream(saveFile, false);
            byte buffer[] = new byte[1024];
            int readsize = 0;
            while ((readsize = is.read(buffer)) > 0) {
                fos.write(buffer, 0, readsize);
                totalSize += readsize;
                // 为了防止频繁的通知导致应用吃紧,百分比增加10才通知一次
                if ((downloadCount == 0)
                        || (int) (totalSize * 100 / updateTotalSize) - 10 >= downloadCount) {
                    downloadCount += 10;
                    // 更新进度
                    Message msg = mHandler.obtainMessage();
                    msg.what = 1;
                    msg.arg1 = downloadCount;
                    mHandler.sendMessage(msg);
                    if (callback != null)
                        callback.OnBackResult(progress);
                }
            }

            // 下载完成通知安装
            mHandler.sendEmptyMessage(0);
            // 下载完了,cancelled也要设置
            canceled = true;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
            if (is != null) {
                is.close();
            }
            if (fos != null) {
                fos.close();
            }
        }
        return totalSize;
    }

我这里使用到的是原始的网络请求 配合handler进行通知栏的更新

   private Handler mHandler = new Handler() {

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    // 下载完毕
                    mNotificationManager.cancel(NOTIFY_ID);
                    installApk();
                    break;
                case 2:
                    // 取消通知
                    mNotificationManager.cancel(NOTIFY_ID);
                    break;
                case 1:
                    int rate = msg.arg1;
                    if (rate < 100) {
                        contentView.setTextViewText(R.id.tv_download_state, mTitle + "(" + rate
                                + "%" + ")");
                        contentView.setProgressBar(R.id.pb_download, 100, rate,
                                false);
                    } else {
                        // 下载完毕后变换通知形式
                        NotificationBuilder.setAutoCancel(true);
                        serviceIsDestroy = true;
                        stopSelf();// 停掉服务自身
                    }
                    mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
                    break;
            }
        }

    };

对了 最后还需要注意一个问题:

就是程序如果是 System.exit(0); 这样退出的话,那么通知栏就会在下载了 service也会终止掉
效果就是这样
adnroid 使用service。 更新apk_第1张图片

参考

Android Service完全解析,关于服务你所需知道的一切(下)
Android 状态栏通知Notification详解
开源中国app源码

你可能感兴趣的:(源码,apk,开源中国)