美文网首页
android开辟新进程,远程服务的方式实现app升级

android开辟新进程,远程服务的方式实现app升级

作者: yu_yue | 来源:发表于2018-09-03 09:51 被阅读350次

    创建一个Service,用于下载apk文件,service下载的时候实时更新通知栏的下载进度,同时通过回调,把下载情况的回调传给调用者,实现远程服务下载功能

    必须在Manifest中注册Service:


    <service android:name="com.uadzd.update.service.DownloadService"
    android:exported="true"
    android:process=":update"
    />
    public class DownloadService extends Service {

    private static final int NOTIFY_ID = 0;
    private static final String CHANNEL_ID = "app_update_id";
    private static final CharSequence CHANNEL_NAME = "app_update_channel";
    private static ServiceConnection connection;
    private static Context context;
    private NotificationManager mNotificationManager;
    private DownloadBinder binder = new DownloadBinder();
    private NotificationCompat.Builder mBuilder;
    private static IDownloadCallback mCallBack = null;
    public static void bindService(Context context, ServiceConnection connection) {
        DownloadService.context = context;
        DownloadService.connection = connection;
        Intent intent = new Intent(context, DownloadService.class);
        context.startService(intent);
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    
    }
    public static void unBindService(){
        if (context != null && connection != null){
            context.unbindService(connection);
            mCallBack = null;
            connection = null;
        }
    }
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        // 返回自定义的DownloadBinder实例
        return binder;
    }
    
    @Override
    public void onDestroy() {
        mNotificationManager = null;
        super.onDestroy();
    }
    
    /**
     * 创建通知
     */
    private void setUpNotification() {
        if (android.os.Build.VERSION.SDK_INT >=   android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
    channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
            channel.enableVibration(false);
            channel.enableLights(false);
            mNotificationManager.createNotificationChannel(channel);
        mBuilder = new NotificationCompat.Builder(this);
        mBuilder.setContentTitle("开始下载")
                .setContentText("正在连接服务器")
                .setSmallIcon(R.drawable.lib_update_app_update_icon)
                .setLargeIcon(AppUpdateUtils.drawableToBitmap(AppUpdateUtils.getAppIcon(DownloadService.this)))
                .setOngoing(true)
                .setAutoCancel(true)
                .setWhen(System.currentTimeMillis());
        mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
    }
    
    /**
     * 下载模块
     */
    private void startDownload(UpdateAppBean updateApp,IDownloadCallback callback) {
        UpdateTask updateTask = new UpdateTask(new FileDownloadCallBack(callback));
        updateTask.execute(updateApp);
    }
    
    private void stop(String contentText) {
        if (mBuilder != null) {
            mBuilder.setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
                    .setContentText(contentText);
            Notification notification = mBuilder.build();
            notification.flags = Notification.FLAG_AUTO_CANCEL;
            mNotificationManager.notify(NOTIFY_ID, notification);
        }
        close();
    }
    
    private void close() {
        stopSelf();
    }
    
    /**
     * DownloadBinder中定义了一些实用的方法
     *
     * @author user
     */
    public class DownloadBinder extends IDownloadlInterface.Stub{
        @Override
        public void start(UpdateAppBean bean, IBinder iBinder) throws RemoteException {
            /**
             * 开始下载
             *
             */
            IDownloadCallback callback = IDownloadCallback.Stub.asInterface(iBinder);
            startDownload(bean,callback);
        }
    
        @Override
        public void stop(String msg) throws RemoteException {
    
        }
    
    }
    
    public class FileDownloadCallBack implements FileCallBack {
        int oldRate = 0;
    
        public FileDownloadCallBack(@Nullable IDownloadCallback callback) {
            super();
            mCallBack = callback;
        }
    
        @Override
        public void onBefore() {
            //初始化通知栏
            setUpNotification();
            if (mCallBack != null) {
                try {
                    mCallBack.onStart();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void onProgress(float progress, long total) {
            //做一下判断,防止自回调过于频繁,造成更新通知栏进度过于频繁,而出现卡顿的问题。
            int rate = Math.round(progress * 100);
            if (oldRate != rate) {
                if (mCallBack != null) {
                    try {
                        mCallBack.setMax(total);
                        mCallBack.onProgress(progress, total);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                if (mBuilder != null) {
                    // 创建一个数值格式化对象
                    NumberFormat numberFormat = NumberFormat.getInstance();
                    // 设置精确到小数点后2位
                    numberFormat.setMaximumFractionDigits(2);
                    String result = numberFormat.format((float) progress / (float) total * 100);
                    mBuilder.setContentTitle("正在下载:" + AppUpdateUtils.getAppName(DownloadService.this))
                            .setContentText(result+"%")
                            .setProgress((int) total, (int) progress, false)
                            .setWhen(System.currentTimeMillis());
                    Notification notification = mBuilder.build();
                    notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE;
                    mNotificationManager.notify(NOTIFY_ID, notification);
                }
                //重新赋值
                oldRate = rate;
            }
    
        }
    
        @Override
        public void onError(String error) {
            Toast.makeText(DownloadService.this, "更新新版本出错," + error, Toast.LENGTH_SHORT).show();
            //App前台运行
            if (mCallBack != null) {
                try {
                    mCallBack.onError(error);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            try {
                mNotificationManager.cancel(NOTIFY_ID);
                close();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    
        @Override
        public void onResponse(String filePath) {
            try {
                if (AppUpdateUtils.isAppOnForeground(DownloadService.this) || mBuilder == null) {
                    //App前台运行
                    mNotificationManager.cancel(NOTIFY_ID);
                    if (mCallBack != null) {
                        boolean temp = mCallBack.onInstallAppAndAppOnForeground(filePath);
                        mCallBack.onFinish(filePath);
                        if (!temp) {
                            AppUpdateUtils.installApp(DownloadService.this, filePath);
                        }
                    } else {
                        AppUpdateUtils.installApp(DownloadService.this, filePath);
                    }
    
                } else {
                    //App后台运行, 点击通知栏安装
                    //更新参数,注意flags要使用FLAG_UPDATE_CURRENT
                    Intent installAppIntent = AppUpdateUtils.getInstallAppIntent(DownloadService.this,filePath);
                    PendingIntent contentIntent = PendingIntent.getActivity(DownloadService.this, 0, installAppIntent, PendingIntent.FLAG_UPDATE_CURRENT);
                    mBuilder.setContentIntent(contentIntent)
                            .setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
                            .setContentText("下载完成,请点击安装")
                            .setProgress(0, 0, false)
                            //                        .setAutoCancel(true)
                            .setDefaults((Notification.DEFAULT_ALL));
                    Notification notification = mBuilder.build();
                    notification.flags = Notification.FLAG_AUTO_CANCEL;
                    mNotificationManager.notify(NOTIFY_ID, notification);
                }
                //下载完关闭
                close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close();
            }
        }
    }
    

    }

    创建AIDL文件,android中跨进程通讯只能使用AIDL

    AIDL文件中接口函数的参数只支持以下几种方式

    1. Java 的原生类型

    2. String 和CharSequence

    3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型; 以上三种类型都不需要导入(import)

    4. AIDL 自动生成的接口 需要导入(import)

    5. 实现android.os.Parcelable 接口的类. 需要导入(import)。

    service中的binder类

    interface IDownloadlInterface {

     void start(in UpdateAppBean bean,IBinder callback);
    
     void stop(String msg);
    

    }

    接口中start(in UpdateAppBean bean,IBinder callback);方法的第一个参数,是一个类UpdateAppBean.class必须实现Parcelable接口。而且必须在与UpdateAppBean.class同一个包下增加UpdateAppBean.aidl

    UpdateAppBean.aidl文件内容:

    package com.uadzd.update;//包名

    parcelable UpdateAppBean;//类名

    public class UpdateAppBean implements Parcelable {

    private String newVersionName;
    private String newVersionCode;
    private String oldVersionCode;
    private String downloadUrl;
    private String updateMsg;
    
    private String targetPath;
    public UpdateAppBean(){
    
    }
    protected UpdateAppBean(Parcel in) {
        newVersionName = in.readString();
        newVersionCode = in.readString();
        oldVersionCode = in.readString();
        downloadUrl = in.readString();
        updateMsg = in.readString();
        targetPath= in.readString();
    }
    
    public String getNewVersionName() {
        return newVersionName;
    }
    
    public void setNewVersionName(String newVersionName) {
        this.newVersionName = newVersionName;
    }
    
    public String getNewVersionCode() {
        return newVersionCode;
    }
    
    public void setNewVersionCode(String newVersionCode) {
        this.newVersionCode = newVersionCode;
    }
    
    public String getOldVersionCode() {
        return oldVersionCode;
    }
    
    public void setOldVersionCode(String oldVersionCode) {
        this.oldVersionCode = oldVersionCode;
    }
    
    public String getDownloadUrl() {
        return downloadUrl;
    }
    
    public void setDownloadUrl(String downloadUrl) {
        this.downloadUrl = downloadUrl;
    }
    
    public String getUpdateMsg() {
        return updateMsg;
    }
    
    public void setUpdateMsg(String updateMsg) {
        this.updateMsg = updateMsg;
    }
    
    public String getTargetPath() {
        return targetPath;
    }
    
    public void setTargetPath(String targetPath) {
        this.targetPath = targetPath;
    }
    
    public static final Creator<UpdateAppBean> CREATOR = new Creator<UpdateAppBean>() {
        @Override
        public UpdateAppBean createFromParcel(Parcel in) {
            return new UpdateAppBean(in);
        }
    
        @Override
        public UpdateAppBean[] newArray(int size) {
            return new UpdateAppBean[size];
        }
    };
    
    @Override
    public int describeContents() {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(newVersionName);
        dest.writeString(newVersionCode);
        dest.writeString(oldVersionCode);
        dest.writeString(downloadUrl);
        dest.writeString(updateMsg);
        dest.writeString(targetPath);
    }
    

    }

    接口中start(in UpdateAppBean bean,IBinder callback);方法的第二个参数是一个接口类型,使用接口类型作为参数(用于回调)的时候,接口文件必须定义为***.aidl,不能跟平时定义接口一样直接 ***.java,IDownloadCallback 为IBinder的基类,网上看了很多写法都是直接第二个参数为IDownloadCallback ,然后导入对应的包。start(in UpdateAppBean bean,IDownloadCallback callback);

    这里有坑,一直在service拿到的回调是空,应该使用IBinder 最为参数,然后再通过

    IDownloadCallback callback = IDownloadCallback.Stub.asInterface(iBinder);进行转换。

    interface IDownloadCallback {

    void onStart();
    
    /**
     * 进度
     *
     * @param progress  进度 0.00 -1.00 ,总大小
     * @param totalSize 总大小 单位M
     */
    void onProgress(float progress, long totalSize);
    
    /**
     * 总大小
     *
     * @param totalSize 单位M
     */
    void setMax(long totalSize);
    
    /**
     * 下载完了
     *
     * @param filePath 下载的app
     * @return true :下载完自动跳到安装界面,false:则不进行安装
     */
    boolean onFinish(String filePath);
    
    /**
     * 下载异常
     *
     * @param msg 异常信息
     */
    void onError(String msg);
    
    /**
     * 当应用处于前台,准备执行安装程序时候的回调,
     *
     * @param filePath 当前安装包路径
     * @return false 默认 false ,当返回时 true 时,需要自己处理 ,前提条件是 onFinish 返回 false 。
     */
    boolean onInstallAppAndAppOnForeground(String filePath);
    

    }

    FileCallback接口类,文件下载进度回调接口

    public interface FileCallBack {

    void onProgress(float progress, long total);
    
    /**
     * 错误回调
     *
     * @param error 错误提示
     */
    void onError(String error);
    
    /**
     * 结果回调
     *
     * @param filePaht 下载好的文件路径
     */
    void onResponse(String filePaht);
    
    /**
     * 请求之前
     */
    void onBefore();
    

    }

    下载工具类

    package com.uadzd.update;

    public class UpdateTask extends AsyncTask<UpdateAppBean, Integer, String> {

    private DownloadService.FileDownloadCallBack fileDownloadCallBack;
    
    public UpdateTask(DownloadService.FileDownloadCallBack fileDownloadCallBack) {
    
        this.fileDownloadCallBack = fileDownloadCallBack;
    }
    
    @Override
    protected void onPreExecute() {
        if (fileDownloadCallBack != null){
            fileDownloadCallBack.onBefore();
        }
        super.onPreExecute();
    }
    
    @Override
    protected String doInBackground(UpdateAppBean... params) {
        UpdateAppBean updateAppBean = params[0];
        HttpURLConnection conn = null;
        File file = null;
        String path = AppUpdateUtils.appIsDownloaded(updateAppBean);
        if (path != null){
            return path;
        }
        //如果相等的话表示当前的sdcard挂载在手机上并且是可用的
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            final OkHttpClient okHttpClient = new OkHttpClient();
            final Request request = new Request.Builder()
                    .url(updateAppBean.getDownloadUrl())
                    .build();
            Call call = okHttpClient.newCall(request);
            try {
                Response response = call.execute();
                long totalLen = response.body().contentLength();
                InputStream is = response.body().byteStream();
                //下载到本地的app名
                file = AppUpdateUtils.getAppFile(updateAppBean);
                FileOutputStream fos = new FileOutputStream(file);
                BufferedInputStream bis = new BufferedInputStream(is);
                byte[] buffer = new byte[1024];
                int len;
                int total = 0;
                while ((len = bis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                    total += len;
                    //获取当前下载量
                    publishProgress(total, (int) totalLen);
                }
                fos.close();
                bis.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return file.getAbsolutePath();
        }else {
            return null;
        }
    
    }
    
    @Override
    protected void onProgressUpdate(Integer... values) {
        if (fileDownloadCallBack != null){
            fileDownloadCallBack.onProgress(values[0] / 1024 / 1024, values[1] / 1024 / 1024);
        }
        super.onProgressUpdate(values);
    }
    
    @Override
    protected void onPostExecute(String filePaht) {
        if (filePaht != null) {
            if (fileDownloadCallBack != null){
                fileDownloadCallBack.onResponse(filePaht);
            }
        } else {
            if (fileDownloadCallBack != null){
                fileDownloadCallBack.onError("没有可用于下载的存储空间!");
            }
        }
    
    }
    

    }

    调用远程服务的方法,与上面service不在同一个进程

    private void startUpdate(final boolean isInBackground) {
        String updateUrl = "http://imtt.dd.qq.com/16891/E29FAA923B6DA21BEA23C2EC4C6F7BC9.apk?fsname=com.uadzd_2.0_6.apk&csr=1bbd";
        final UpdateAppBean updateAppBean = new UpdateAppBean();
        updateAppBean.setDownloadUrl(updateUrl);
        updateAppBean.setNewVersionName("1.1.4");
        DownloadService.bindService(this.getApplicationContext(), new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IDownloadlInterface iDownloadlInterface = IDownloadlInterface.Stub.asInterface(service);
                try {
                    iDownloadlInterface.start(updateAppBean, new IDownloadCallback.Stub() {
                        @Override
                        public void onStart() throws RemoteException {
                            if (isInBackground){
                                goNext();
                            }else {
                                if (pd == null){
                                    showProgressDialog();
                                }
                            }
                        }
    
                        @Override
                        public void onProgress(float progress, long totalSize) throws RemoteException {
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            numberFormat.setMaximumFractionDigits(2);
                            String result = numberFormat.format((float) progress / (float) totalSize * 100);
                            Log.i("aaa", "result"+result);
                            if (!isInBackground){
                                if (pd == null){
                                    showProgressDialog();
                                }else {
                                    pd.setMax((int) totalSize);
                                    pd.setProgress((int) progress);
                                }
                            }
                        }
    
                        @Override
                        public void setMax(long totalSize) throws RemoteException {
    
                        }
    
                        @Override
                        public boolean onFinish(String filePath) throws RemoteException {
                            Log.i("aaa", "onFinish:"+filePath);
                            if (pd != null ){
                                pd.dismiss();
                            }
                            return false;
                        }
    
                        @Override
                        public void onError(String msg) throws RemoteException {
    
                        }
    
                        @Override
                        public boolean onInstallAppAndAppOnForeground(String filePath) throws RemoteException {
                            //false 后台下载完提示安装,true下载完不进行操作,自行处理
                            return false;
                        }
    
                        @Override
                        public IBinder asBinder() {
                            return null;
                        }
                    });
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        });
    }
    

    相关文章

      网友评论

          本文标题:android开辟新进程,远程服务的方式实现app升级

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