用Rxjava替代AsyncTask,完成《第一行代码》中的下载服务案例,以及一些问题和思考)

刚学习安卓编程,在学习《第一行代码》第二版中的服务时,我学习了多线程编程和服务的相关知识,也请教了很多大佬。但是遇到最后的服务下载部分时碰到了难题,当我照着书创建任务类继承AsyncTask时,发现AsyncTask因为内存泄露等问题,已经过时了。
在这里插入图片描述
因此我希望找到一个能够替代AsyncTask的异步线程处理工具,由于我并没有学习过kotlin的相关语法,也没有看过郭神的第三版书中的解决方式,于是我选择了大佬推荐的书写优雅、上手容易、线程调节灵活的Rxjava。
花了差不多一天时间,把Rxjava的代码思想和书写方式大致学习了一下,我尝试着改造原来利用AsyncTask的代码,用Rxjava替代实现下载的功能。
这里是介绍Rxjava很不错的参考文章:
https://www.jianshu.com/p/b00b2657d073
由于我用Rxjava替代,因此我主要修改了原来继承AsyncTask的自定义的类,其他部分的代码基本不变,花了一个下午加一个晚上的时间,终于搞定了,在安卓8.1.0版本的手机上完美测试运行。
下面贴一下修改后的代码:

public class DownloadTask {
	
	//修改了定义的变量,避免和0-99的porgress冲突
    public static final int TYPE_SUCCESS = 100;
    public static final int TYPE_FAILED = 101;
    public static final int TYPE_PAUSED = 102;
    public static final int TYPE_CANCELED = 103;

    private DownloadListener downloadListener;

    private boolean isCanceled = false;
    private boolean isPaused = false;
    private boolean isReading = false;

    private static final String TAG = "DownloadTask";

    public DownloadTask(DownloadListener listener) {
        downloadListener = listener;
    }

    private int lastProgress=0;

    public void execute(String downloadUrl) {
        Log.e(TAG, "execute: " + downloadUrl);
        //创建被观察者
        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Throwable {
                InputStream inputStream = null;
                RandomAccessFile randomAccessFile = null;
                File file = null;
                try {
                    long downloadedLength = 0;
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                    file = new File(directory + fileName);
                    if (file.exists()) {
                        //已经下载的字节数
                        downloadedLength = file.length();
                        Log.e(TAG, "subscribe:已下载 " + downloadedLength);
                    }
                    long contentLength = getContentLength(downloadUrl);
                    Log.e(TAG, "subscribe:总大小 " + contentLength);
                    if (contentLength == 0) {
                    	//用onNext()方法即可发送事件
                        emitter.onNext(TYPE_FAILED);
                    } else if (contentLength == downloadedLength) {
                        emitter.onNext(TYPE_SUCCESS);
                    } else {
                        OkHttpClient okHttpClient = new OkHttpClient();
                        Request request = new Request.Builder()
                                .url(downloadUrl)
                                .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                                .build();
                        Response response = okHttpClient.newCall(request).execute();
                        if (response != null) {
                            inputStream = response.body().byteStream();
                            randomAccessFile = new RandomAccessFile(file, "rw");
                            randomAccessFile.seek(downloadedLength);//跳过已下载的字节
                            byte[] bytes = new byte[1024];
                            int total = 0;
                            int len = 0;
                            while ((len = inputStream.read(bytes)) != -1) {
                                isReading = true;
                                if (isCanceled) {
                                    emitter.onNext(TYPE_CANCELED);
                                    break;
                                } else if (isPaused) {
                                	//由于不像AsyncTask一样可以返回,因此只能用break退出循环并中断下载
                                    emitter.onNext(TYPE_PAUSED);
                                    break;
                                } else {
                                    total += len;
                                    randomAccessFile.write(bytes, 0, len);
                                    //计算百分比
                                    int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                                    emitter.onNext(progress);
                                }
                            }
                            isReading = false;
                            response.body().close();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (inputStream != null) {
                            inputStream.close();
                        }
                        if (randomAccessFile != null) {
                            randomAccessFile.close();
                        }
                        if (isCanceled && file != null) {
                            //这是主动停止删除
                            file.delete();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).subscribeOn(Schedulers.io())//耗时操作在IO线程
        .observeOn(AndroidSchedulers.mainThread())//在主线程中更新UI
        .subscribe(new Observer<Integer>() {
            @Override
                public void onSubscribe(@NonNull Disposable d) {
            }

            @Override
            public void onNext(@NonNull Integer integer) {
                Log.e(TAG, "onNext" + integer);
                //接收到integer类型的事件,进行处理
                switch (integer) {
                    case TYPE_SUCCESS:
                        downloadListener.onSuccess();
                        lastProgress=integer;
                        break;
                    case TYPE_FAILED:
                        downloadListener.onFailed();
                        break;
                    case TYPE_PAUSED:
                        downloadListener.onPaused();
                        break;
                    case TYPE_CANCELED:
                        downloadListener.onCanceled();
                        break;
                    default:
                    	//如果是0-99,则更新progress
                        if (integer > lastProgress) {
                            downloadListener.onProgress(integer);
                            lastProgress = integer;
                        }
                    break;
                }
            }

                    @Override
                    public void onError(@NonNull Throwable e) {
                    }

                    @Override
                    public void onComplete() {
                    }
        });

    }

    public void pauseDownload() {
        if (isReading) {
            isPaused = true;
        } else {
            downloadListener.onPaused();
        }
    }

    public void cancelDownload() {
        if (isReading) {
            isCanceled = true;
        } else {
            downloadListener.onCanceled();
        }
    }

    //请求网络并获取文件长度
    private long getContentLength(String downloadUrl) throws IOException {

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)
                .build();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        try {
        	//由于可能会给出连接错误的异常,捕捉异常才能返回0
            Response response = client.newCall(request).execute();
            if (response != null && response.isSuccessful()) {
                long contentLength = response.body().contentLength();
                //释放资源
                response.body().close();
                return contentLength;
            }
        } catch (ConnectException e) {
            e.printStackTrace();
        }
        return 0;
    }

}

可以看到,我自定义了DownloadTask的类,在类中定义了execute方法,这样就可以和AsyncTask的execute方法一样,用downloadTask.execute()启动下载。
主要修改的部分有:

  1. return TYPE_…改成了emitter.onNext(TYPE_…),因为Rxjava没法返回,于是我就选择直接传递,反正还是要回到主线程在观察者中进行处理。
  2. 原来用publishProgress方法,来进行progress的实时更新。现在选择emitter.onNext(progress)来直接传递,直接在主线程中的观察者中进行更新。
  3. 原来下载状态发生改变之后,调用downloadListener的方法发布通知。现在只需在观察者的onNext方法中进行处理。
    可以看到,用Rxjava替代之后,原来需要重写三个函数,现在只需简化成构建一对观察和被观察的对象,被观察者发送事件,观察者在主线程中处理事件,三合一更加简洁、直观。
    此外,我还修改了最后面的请求代码。我发现如果连接不上,程序提出警告之后就不会继续运行。我搞了老长时间,怎么也得不到response返回的数据,后来才发现是我连接错误了,没有返回值。因此,可以用一个try\catch的结构捕捉异常,返回字节长度0。
    还有初始定义的变量值我改为了从100开始,是为了防止和0-99的progress冲突,当然当progress为100时,自然下载状态就是TYPE_SUCCESS.
    通过学习,我基本体会到了Rxjava的强大功能和链式的处理方式,整个下载服务就是在主函数中绑定服务,利用downloadBinder对象进行活动和服务之间的控制,在downloadBinder对象的方法中开启downloadTask,当下载状态改变时,调用监听downloadListener发布通知和更改状态,形成前台后台活动整体的控制。
    在下载的时候,我把郭神提供的安装包下载下来,放到本地服务器的文件夹下,Url就是本地服务器的地址。当我试了试之前做其他项目时成功访问的IPV4地址时,发现怎么也连接不上,搞了好久才发现,

本地的IPV4地址是变化的!!!

修改为正确的IPV4地址后,程序成功运行。
大致的修改就是这样,以防万一,我把活动和服务的代码全部贴出来,下面是活动代码:

public class SecondActivity extends AppCompatActivity {

    @BindView(R.id.button2)
    Button button2;
    @BindView(R.id.start_download)
    Button startDownload;
    @BindView(R.id.pause_download)
    Button pauseDownload;
    @BindView(R.id.cancel_download)
    Button cancelDownload;

    private DownloadService.DownloadBinder downloadBinder;
    private ServiceConnection serviceConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("main", "onServiceConnected executed ");
            downloadBinder= (DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        ButterKnife.bind(this);

        startDownload.setOnClickListener(v -> {
            if(downloadBinder!=null) {
                Log.e("main", "onCreate success" );
                String Url = "http://10.128.73.133/eclipse-inst-win64.exe";
                downloadBinder.startDownload(Url);
            }else{
                Log.e("main", "onCreate false");
            }
        });

        pauseDownload.setOnClickListener(v -> {
            if(downloadBinder!=null) {
                downloadBinder.pauseDownload();
            }
        });

        cancelDownload.setOnClickListener(v -> {
            if(downloadBinder!=null) {
                downloadBinder.cancelDownload();
            }
        });

        Intent intent=new Intent(this,DownloadService.class);
        startService(intent);//启动服务
        bindService(intent,serviceConnection,BIND_AUTO_CREATE);//绑定服务

        if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if(grantResults.length>0 && grantResults[0]!=PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "权限获取失败", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

下面是服务代码:

public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private NotificationManager notificationManager;

    private String downloadUrl;

    private static final String TAG = "DownloadService";

    //获取通知对象
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    private Notification getNotification(String title, int progress){
        NotificationManagerCompat notificationManagerCompat=NotificationManagerCompat.from(this);
        if(!notificationManagerCompat.areNotificationsEnabled()) {
            Intent intent=new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            intent.setData(Uri.fromParts("package", getPackageName(), null));
            startActivity(intent);
        }
        Notification.Builder builder = new Notification.Builder(this)
                .setContentTitle(title)
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher);
        if(progress>=0){
            builder.setContentText(progress+"%");
            builder.setProgress(100,progress,false);
        }
        if (Build.VERSION.SDK_INT > 26) {
            NotificationChannel notificationChannel = new NotificationChannel("1", "download", NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(notificationChannel);
            builder.setChannelId("1");
        }
        return builder.build();
    }

    public DownloadService() {
    }

    @Override
    public void onCreate() {
        notificationManager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Log.e(TAG, "onCreate executed");
    }

    //定义监听器
    private DownloadListener downloadListener=new DownloadListener() {
        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
        @Override
        public void onProgress(int progress) {
            notificationManager.notify(1,getNotification("Downloading...",progress));
        }

        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
        @Override
        public void onSuccess() {
            downloadTask=null;
            //前台通知关闭,创建一个下载成功的通知
            stopForeground(true);
            notificationManager.notify(1,getNotification("Download success",-1));
            Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();
        }

        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
        @Override
        public void onFailed() {
            downloadTask=null;
            stopForeground(true);
            notificationManager.notify(1,getNotification("Download Failed",-1));
            Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPaused() {
            downloadTask=null;
            Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCanceled() {
            downloadTask=null;
            stopForeground(true);
            Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
        }
    };

    private DownloadBinder downloadBinder=new DownloadBinder();
    class DownloadBinder extends Binder {
        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
        public void startDownload(String Url){
            if(downloadTask==null){
                downloadUrl=Url;
                downloadTask=new DownloadTask(downloadListener);
                downloadTask.execute(downloadUrl);
                Log.e(TAG, "startDownload executed");
                //开启前台通知服务
                startForeground(1,getNotification("Downloading...",0));
                Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
            }else{
                Log.e(TAG, "startDownload not null");
            }
        }

        public void pauseDownload(){
            if(downloadTask!=null){
                downloadTask.pauseDownload();
            }
        }

        public void cancelDownload(){
            if(downloadTask!=null){
                downloadTask.cancelDownload();
            }
            if(downloadUrl!=null){
                //取消下载时将文件删除
                String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                File file=new File(directory+fileName);

                if(file.exists()){
                    file.delete();
                }
                notificationManager.cancel(1);
                stopForeground(true);
                Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return downloadBinder;
    }

}

最终结果:
在这里插入图片描述
我对于Rxjava的理解还十分浅薄,如果有任何错误欢迎指正。如果有任何的问题,也欢迎和我交流,希望大家共同进步!!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值