Android学习(十)

Service 服务

服务是Android中实现程序后台运行的解决方案, 他非常适合去执行那些不需要和用户交互而且还要长期运行的任务, 服务的运行不依赖任何界面, 即使程序被切换到后台或者用户打开了另外一个程序,服务仍然能够保持正常运行,
不过需要注意的是服务并不是一个独立的进程,而是依赖于创建服务时所在的应用程序进程。

Android的UI和其他的Gui库一样,也是线程不安全的,如果想要更新程序内的ui,则必须在主线程中执行,否则就会出现异常。(
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

对于这种情况,Android提供了一套异步消息处理机制,完美的解决了在子线程中进行UI操作的问题,

  1. 创建AndroidThreadDemo项目, 然后修改activity_main中的代码如下。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <Button
            android:id="@+id/btn_change_text"
            android:text="修改内容"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/tv_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    
    </LinearLayout>
    
  2. 然后修改MainActivity中的代码如下:

    public class MainActivity extends AppCompatActivity {
        private Button btnChangeText;
        private TextView tvText;
    
        public static final int UPDATA_TEXT = 1;
    
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch(msg.what){
                    case UPDATA_TEXT:
                        //在这里可以进行Ui操作。
                        tvText.setText("修改好了");
                        break;
                }
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnChangeText = (Button) findViewById(R.id.btn_change_text);
            tvText = (TextView)findViewById(R.id.tv_text);
            btnChangeText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Message message = new Message();
                            message.what = UPDATA_TEXT;
                            handler.sendMessage(message); // 将Message对象发送出去
                        }
                    }).start();
                }
            });
        }
    }
    

    这里我们先是定义了一个整形常量UPDATE_TEXT,用于表示更新TextView这个动作,然后新增了一个Handler对象, 并重写了父类的handleMessage()方法,在这里对具体的Message进行处理 如果发现Message的what字段的值等于UPDATE_TEXT那么就将TextView中的内容修改。
    在子线程中没有对Ui进行直接操作,而是创建了一个Message对象,并且将他的what字段的值指定为UPDATA_TEXT然后调用Handler的sendMessage()方法,将这条message发送给handler对象让他进行处理。

解析一部消息处理机制。

ANdroid中的异步消息处理主要由四个部分组成, Message、 Handler、 MessageQueue、 Looper。

  1. Message
    Message是在线程之间传递的消息, 他可以在内部携带少量信息,用于在不同线程之间交换数据,Message中除了what字段还可以使用arg1和arg2字段来携带一些整形数据,使用obj字段携带一个Object对象。
  2. Handler
    Handler顾名思义也就是处理者的意思,他主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage()方法中。
  3. MessageQueue
    MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分消息会一致存在于消息队列中,等待被处理,每个线程中只会有一个MessageQueue对象
  4. Looper
    Looper是每个线程中的MessageQueue管家,调用Looper的Loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将他取出来,并传递到Handler的handleMessage()方法中,每个线程中也只会有一个Looper对象。

也就是说,首先我们要在主线程中创建一个Handlerduix 并重写他的handleMessage()方法,然后当子线程中要进行Ui操作的时候,就创建一个Message对象,并通过Handler将这条消息发送出去,之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一致尝试从MessageQueue中去除待处理消息,最后分发回Handler的handleMessage()方法中,由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中执行, 于是我们就可以安心的进行UI操作了。

使用AsyncTask

不过为了更加方便我们在子线程中 对Ui操作, android还提供了另外一些好用的工具 比如说AsyncTask 借助这个工具即使对一步消息处理机制不完全了解,也可以简单的从子线程切换到主线程。

用法:

由于AsyncTask是一个抽象类,所以如果我们想使用它就必须创建一个自雷去继承他, 在继承他的时候我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

Params 。 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
Progress 。 后台任务执行时,如果需要在界面上显示当前的进度, 则使用这里指定的泛型作为进度单位。,
Result 。 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

所以 一个最简单的AsyncTask就可以写成如下方式

public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    ...
}

这里我们把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务,第二个泛型参数指定为Integer,表示使用整形数据作为进度展示单位,第三个泛型参数指定为Boolean,表示使用布尔型数据来反馈执行结果。
我们还需要重写AsyncTask中的几个方法才能完成对任务的定制,经常需要重写的方法由一下4个

  1. onPreExecute() 这个方法会在后台任务开始执行之前调用,对于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  2. doInBackground(Params...) 这个方法中的所有代码都会在子线程中运行,我们应该 在这里去处理所有的耗时任务任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void就可以不返回任务执行结果,但是需要注意的是这个方法中是不可以进行Ui操作的,如果需要更新Ui则可以调用publishProgress(Progress...)方法来完成
  3. onProgressUpdate(Progress...) 当在后台任务重调用了publishProgress(Progress...)方法后,onProgressUpdate()方法就会很快被调用,该方法中携带参数就是在后台任务中传递过来的,在这个方法中可以对Ui进行操作,利用参数中的数值就可以对界面元素进行相应的更新,
  4. onPostExecute(Result) 当后台任务执行完毕通过return语句返回时 这个方法就会很快被调用,返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作。比如提醒任务执行的结果,以及关闭对话框等等。

因此 一个比较完整的自定义AsyncTask就可以写成如下的方式:

public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show(); // 显示进度条对话框
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        progressDialog.dismiss();
        if(aBoolean){
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("Downloaded "+ values[0] + "%");
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        while(true){
            try{
                int downloadPercent = doDownload();
                publishProgress(downloadPercent);
                if(downloadPercent >= 100){
                    break;
                }
            }catch (Exception e){
                return false;
            }
        }
        return true;
    }
}

使用AsyncTask的诀窍就是在doInBackground()方法中执行一些耗时的任务,在onProgressUpdate()方法中进行ui操作,在onPostExecute方法中执行一些任务的收尾工作。
如果想要启动这个任务

new DownloadTask().execute();

Service服务的基本用法。

定义一个服务。在Android中new->Service->Service即可创建一个服务。
也可以手动创建一个类然后去继承Service最后在AndroidManifest中对这个服务进行注册。

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"></service>

通常情况下我们会重写Service类中的3个方法

onCreate() 方法会在服务创建的时候调用。
onStartCommand() 方法会在每次启动服务的时候调用
onDestory() 方法会在服务销毁的时候调用。

启动和停止服务

  1. 首先修改activity_mainz中的布局代码 并添加两个按钮分别是启动服务和停止服务。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <Button
            android:id="@+id/btn_start_service"
            android:text="启动服务"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btn_stop_service"
            android:text="停止服务"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </LinearLayout>
    
  2. 然后创建一个Service服务 代码如下

    public class MyService extends Service {
        public static final String TAG = "MyService";
        public MyService() {
        }
        @Override
        public IBinder onBind(Intent intent) {
            // TODO: Return the communication channel to the service.
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public void onCreate() {
            Log.d(TAG, "onCreate: ");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.d(TAG, "onStartCommand: ");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d(TAG, "onDestroy: ");
        }
    }
    
  3. 修改MainActivity中的代码如下:

    public class MainActivity extends AppCompatActivity {
    
        private Button btnStartService;
        private Button btnStopService;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnStartService = (Button) findViewById(R.id.btn_start_service);
            btnStopService = (Button) findViewById(R.id.btn_stop_service);
    
            btnStartService.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 通过intent启动服务
                    Intent intent = new Intent(MainActivity.this,MyService.class);
                    startService(intent);
                }
            });
    
            btnStopService.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    Intent intent = new Intent(MainActivity.this,MyService.class);
                    stopService(intent);
                }
            });
    
    
        }
    }
    

    其中onCreate()是第一次启动服务时候执行的,onStartCommand()方法会在每次服务启动时都会执行,

活动和服务进行通信。

比如我们向在MyService中提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度,实现这个功能就需要创建一个专门的Binder对象来对下载功能,进行管理,修改MyService中的代码如下

public class MyService extends Service {
    public static final String TAG = "MyService";

    private DownloadBinder downloadBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload(){
            Log.d(TAG, "startDownload: ");
        }
        public int getProgress(){
            Log.d(TAG, "getProgress: ");
            return 0;
        }
    }
    public MyService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        return downloadBinder;
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}

这里我们创建了一个DownloadBinder类,并继承自Binder然后在他的内部提供了开始下载和查看下载进度的方法,
然后在Service中创建了一个DownloadBinder的实例,在onBind()方法中返回了这个实例,
然后我们在activity_main中新增两个按钮

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_start_service"
        android:text="启动服务"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btn_stop_service"
        android:text="停止服务"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_bind_service"
        android:text="绑定服务"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btn_unbind_service"
        android:text="取消绑定服务"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

最后修改MainActivity中的代码如下

public class MainActivity extends AppCompatActivity {

    private Button btnStartService;
    private Button btnStopService;
    private Button btnBindService;
    private Button btnUnbindService;

    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder)service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStartService = (Button) findViewById(R.id.btn_start_service);
        btnStopService = (Button) findViewById(R.id.btn_stop_service);

        btnStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 通过intent启动服务
                Intent intent = new Intent(MainActivity.this,MyService.class);
                startService(intent);
            }
        });

        btnStopService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent(MainActivity.this,MyService.class);
                stopService(intent);
            }
        });

        btnBindService = (Button)findViewById(R.id.btn_bind_service);
        btnUnbindService = (Button)findViewById(R.id.btn_unbind_service);

        btnBindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                bindService(intent,connection ,BIND_AUTO_CREATE);//绑定服务
            }
        });
        btnUnbindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(connection);
            }
        });
    }
}

首先创建了一个ServiceConnection的匿名类,这里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定时候和解除绑定的时候调用,在onServiceConnected()方法中通过向下转型,得到了DownloadBinder的实例,有了这个实例活动和服务之间的关系就变的非常紧密了。我们就可以在活动中根据具体需求调用DownloadBinder中的方法了。
在bindService()方法接受三个参数,第一个参数就是构建出来的intent对象,然后第二个参数是ServiceConnection的实例,第三个参数是一个标志位,这里传入的BIND_AUTO_CREATE表示在活动和服务进行绑定后会自动创建服务,这会使myService中的onCreate()方法得到执行,但是onStartCommand()方法不会得到执行,。

服务的声明周期

  1. 启动服务

    一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法,如果这个服务之前还没有创建过,onCreate()方法会有限执行,服务启动了之后会一致保持运行状态直到stopService()或stopSelf()方法会被调用,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例,所以不管调用了多少次startService()方法知道用stopService()或者stopSelf()方法服务就会停止下来了。

  2. 绑定服务

    另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

  3. 停止和解除绑定服务

    当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了 bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

使用前台服务

前台服务与普通服务最大的区别就在于他会有一个正在运行的图标在系统的状态栏显示,下来状态栏后可以看到更加详细的信息,非常类似于通知的效果。

修改MyService中的onCreate代码如下。

public void onCreate() {
    super.onCreate();
    Log.d(TAG, "onCreate: ");
    Intent intent = new Intent(this,MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);

    // 适配8.0以上系统
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        NotificationChannel mChannel = null;
        mChannel = new NotificationChannel("builder", "laji", NotificationManager.IMPORTANCE_HIGH);
        manager.createNotificationChannel(mChannel);
        Notification notification = new NotificationCompat.Builder(this,"builder")
                .setContentTitle("这是标题")
                .setContentText("这是内容")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pi)
                .setSmallIcon(R.mipmap.ic_launcher)
                .build();
        startForeground(1, notification);
    }else{
        Notification notification = new NotificationCompat.Builder(this,"builder")
                .setContentTitle("这是1标题")
                .setContentText("这是1内容")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pi)
                .setSmallIcon(R.mipmap.ic_launcher)
                .build();
        startForeground(1, notification);
    }

}

使用IntentService

服务中的代码都是默认运行在主线程中的,如果要运行一些比较费事的操作需要在服务中的具体方法中开启一个子线程,然后去处理一些耗时的逻辑

但是这种服务一旦启动之后就会一致处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停下来,所以要在run()方法的最后一行添加一个stopSelf()方法,

虽然这种方法并不复杂但是总会由一些程序员忘记开启线程,或者忘记调用stopSelf()为了可以简单的创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类很好的解决了这些问题,

新建一个MyIntentService类继承IntentService

public class MyIntentService extends IntentService {
    public static final String TAG = "MyIntentService";
    public MyIntentService(){
        //调用父类的有参构造函数
        super("MyIntentService");
    }

    public MyIntentService(String name) {
        super(name);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 打印当前线程的id
        Log.d(TAG, "Thread is : "+ Thread.currentThread().getId());
    }
}

这里首先要提供一个无参构造函数,并且必须在内部调用父类的有参构造函数,然后在子类中实现onHandlerIntent()这个抽象方法,这个方法中可以处理一些具体额逻辑。这个方法已经在子线程中运行了,

修改activity_main.xml中的代码加入一个用于启动MyIntentService这个服务的按钮

然后修改MainActivity中的代码

 btnStartIntentService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d(TAG, "Thread = "+ Thread.currentThread().getId());
            Intent intent = new Intent(MainActivity.this,MyIntentService.class);
            startService(intent);

        }
    });

然后在AndroidManifest中注册这个服务

<service android:name=".MyIntentService"/>

可以看到不仅线程id不同,而且销毁方法也得到了执行,

完整版的下载实例。

  1. 首先我们需要添加好依赖 编辑app/build.gradle文件在dependencies中添加OkHttp

    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    
  2. 接下来需要定义一个回调接口,新建一个DownloadListener接口定义如下

    public interface DownloadListener {
        void onProgress(int progress);
        void onSuccess();
        void onFailed();
        void onPaused();
        void onCanceled();
    }
    

    可以看到我们一共定义了5个回调方法,

    onProgress()方法用于通知当前的下载进度,
    onSucess()方法用于通知下载成功事件,
    onFailed()方法用于通知下载失败事件,
    onPause()方法用于通知下载暂停事件,
    onCanceled()方法用于通知下载取消事件,
    
  3. 回调接口定义好后就可以开始编写下载功能了,这里使用AsyncTask来进行实现,新建一个DownloadTask继承AsyncTask 代码如下。

    public class DownloadTask extends AsyncTask<String, Integer, Integer> {
        public static final String TAG = "MainActivity";
        public static final int TYPE_SUCCESS = 0;
        public static final int TYPE_FAILED = 1;
        public static final int TYPE_PAUSED = 2;
        public static final int TYPE_CANCELEDE = 3;
    
        private DownloadListener listener;
        private boolean isCanceled = false;
        private boolean isPause = false;
        private int lastProgress;
    
        public DownloadTask(DownloadListener listener){
            this.listener = listener;
        } //初始化回调方法
    
        @Override
        protected Integer doInBackground(String... strings) { //在后台执行的方法 会被 execute()方法调用后执行。
            InputStream is = null;
            RandomAccessFile savedFile = null; // 随机读写文件的类。
            File file = null;
    
            try{
                long downloadedLength = 0; // 记录下载的文件长度
                String downloadUrl = strings[0];
                String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                // 所对应的目录是手机的download文件夹
                String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                file = new File(directory+fileName);
                if(file.exists()){
                    downloadedLength = file.length();// 取的是文件已经下载的长度
                }
                long contentLength = getContentLength(downloadUrl); // 获取需要下载的文件的长度
    
                if(contentLength == 0){
                    return TYPE_FAILED;
                }else if(contentLength == downloadedLength){
                    return TYPE_SUCCESS; // 如果已下载字节和文件总字节相等说明下载完成了。
                }
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        // 断点下载,指定从那个字节开始下载
                        .addHeader("RANGE","byte="+downloadedLength+"-")
                        .url(downloadUrl)
                        .build();
                Response response = client.newCall(request).execute();
                if(response != null) {
                    is = response.body().byteStream();
                    savedFile = new RandomAccessFile(file, "rw");
                    savedFile.seek(downloadedLength); // 跳过已下载字节
                    byte[] b = new byte[1024];
                    int total = 0;
                    int len;
                    while ((len = is.read(b)) != -1) {
                        if (isCanceled) {
                            return TYPE_CANCELEDE;
                        } else if (isPause) {
                            return TYPE_PAUSED;
                        } else {
                            total += len;
                            savedFile.write(b, 0, len); // 写入文件
                            int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                            publishProgress(progress); // 尽快调用onProgressUpdate方法
                        }
                    }
                    response.body().close();
                    return TYPE_SUCCESS;
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                    try{
                        if(is != null){
                            is.close();
                        }
                        if(savedFile != null){
                            savedFile.close();
                        }
                        if(isCanceled && file != null){
                            file.delete();
                        }
                    }catch(Exception e){
                        e.printStackTrace();
                    }
            }
            return TYPE_FAILED;
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            int progress = values[0];
            if(progress> lastProgress){ // 大于上次的进度
                listener.onProgress(progress);
                lastProgress = progress;
            }
        }
    
        @Override
        protected void onPostExecute(Integer integer) {// 执行后台任务完成之后调用这个方法
            switch (integer){
                case TYPE_SUCCESS:
                    listener.onSuccess();;
                    break;
                case TYPE_FAILED:
                    listener.onFailed();
                    break;
                case TYPE_PAUSED:
                    listener.onPaused();
                    break;
                case TYPE_CANCELEDE:
                    listener.onCanceled();
                    break;
            }
        }
    
        public void pauseDownload(){
            isPause = true;
        }
    
        public void cancelDownload(){
            isCanceled = true;
        }
    
        private long getContentLength(String downloadUrl){
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(downloadUrl)
                    .build();
            Response response = null;
            try {
                response = client.newCall(request).execute();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(response != null && response.isSuccessful()){
                long contentLength = response.body().contentLength(); // 取文件的相应长度。
                response.body().close();
                return contentLength;
            }
            return 0;
        }
    }
    

    AsyncTask中的三个泛型参数第一个泛型参数指定为String,表示在执行的时候需要传入一个字符串参数给后台任务。第二个参数指定为Integer表示用整形数据来作为进度显示单位,第三个参数是Integer表示用整形反馈执行结果。
    在DoInBackground()方法中我们从参数中获取了下载的URL,并根据地址解析出来文件名,然后指定文件下载到Download目录下,然后判断这个文件是否已经下载了, 这样可以在后面使用断电续传功能。接下来调用了getContentLength()方法来获取待下载文件的总程度,如果文件长度是0说明文件是有问题的,如果文件长度等于已经下载的长度说明文件已经下载完成了。

  4. 然后创建一个service服务 起名DownloadService 并修改其中的代码如下。

    public class DownloadService extends Service {
        public static final String TAG = "MainActivity";
    
    
        private DownloadTask downloadTask;
        private String downloadUrl;
    
        private DownloadListener listener = new DownloadListener() {
            @Override
            public void onProgress(int progress) {
                getNotificationManager().notify(1,getNotification("下载中...",progress));
            }
    
            @Override
            public void onSuccess() {
                downloadTask = null;
                // 下载成功时候将前台服务通知关闭,并创建一个下载成功通知
                stopForeground(true);
                getNotificationManager().notify(1,getNotification("下载成功。",-1));
                Toast.makeText(DownloadService.this, "下载成功", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onFailed() {
                downloadTask = null;
                // 下载失败时将前台服务通知关闭, 并创建一个下载失败的通知
                stopForeground(true);
                getNotificationManager().notify(1,getNotification("下载失败。",-1));
                Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onPaused() {
                downloadTask = null;
                Toast.makeText(DownloadService.this, "下载暂停了", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onCanceled() {
                downloadTask = null;
                stopForeground(true);
                Toast.makeText(DownloadService.this, "取消下载", Toast.LENGTH_SHORT).show();
            }
        };
        private DownloadBinder mBinder = new DownloadBinder();
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
        class DownloadBinder extends Binder {
            public void startDownload(String url){
                if(downloadTask == null){
                    downloadUrl = url;
                    downloadTask = new DownloadTask(listener);
                    downloadTask.execute(downloadUrl); // 定DownloadTask中的doInBackground方法。
                    startForeground(1,getNotification("下载中...",0));
                    Toast.makeText(DownloadService.this, "下载中", Toast.LENGTH_SHORT).show();
                }
            }
    
            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();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                    Toast.makeText(DownloadService.this, "取消", Toast.LENGTH_SHORT).show();
                }
            }
        }
    
        private NotificationManager getNotificationManager(){
            return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        }
        private Notification getNotification(String title , int progress){
    
            Intent intent = new Intent(this, MainActivity.class);
            PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
            NotificationChannel channel = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                channel = new NotificationChannel("前台下载服务", "随随便便", NotificationManager.IMPORTANCE_LOW);
                getNotificationManager().createNotificationChannel(channel);
            }else{
    
            }
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"前台下载服务");
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setContentIntent(pi);
            builder.setContentText(title);
            if(progress>=0){
                builder.setContentText(progress+"%");
                builder.setProgress(100,progress,false);
            }
            return builder.build();
    
        }
    }
    
  5. 修改activity_main中的代码如下。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <Button
            android:id="@+id/btn_start_download"
            android:text="开始下载"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btn_pause_download"
            android:text="暂停下载"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btn_cancel_download"
            android:text="开停止下载"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    
  6. 修改MainActivity中的代码如下:

    public class MainActivity extends AppCompatActivity {
        public static final String TAG = "MainActivity";
    
        private DownloadService.DownloadBinder downloadBinder;
        private String url = "http://downmini.kugou.com/web/kugou8275.exe";
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                downloadBinder = (DownloadService.DownloadBinder)service;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(connection);
        }
    
        @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();
                    }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button btnStartDownload = (Button)findViewById(R.id.btn_start_download);
            Log.d(TAG, "onCreate: ????");
            btnStartDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d(TAG, "onClick: 开始按钮点击了。");
                    downloadBinder.startDownload(url);
                    Log.d(TAG, "onClick: downloadBinder启动下载之后了。");
    
    
                }
            });
            Button btnPuaseDownload = (Button)findViewById(R.id.btn_pause_download);
            btnPuaseDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    downloadBinder.pauseDownload();
                }
            });
            Button btnCancelDownload = (Button)findViewById(R.id.btn_cancel_download);
            btnCancelDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    downloadBinder.cancelDownload();
                }
            });
    
    
            if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    !=PackageManager.PERMISSION_GRANTED){
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
            }
    
            Intent intent = new Intent(this,DownloadService.class);
            startService(intent);
            bindService(intent,connection,BIND_AUTO_CREATE);
        }
    }
    




posted @ 2018-11-26 16:12  Zbuter  阅读(95)  评论(0编辑  收藏  举报