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

创建一个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) {

            }
        });
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容