第11章 探究服务

本系列学习笔记第11章

前言

打算把android基本知识点写一个系列,旨在把android基础书,例如《Android 第一行代码 第2版》、《爱上android》、《疯狂android讲义》等书的一些知识点记录一下,会持续更新内容,为了方便自己复习,也希望可以帮助到大家!

1、服务是什么?

服务Service是android中实现程序后台运行的解决方案。它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

服务默认是不会开启线程,所有的代码都是默认运行在主线程当中,所以我们需要在服务的内部手动创建子线程,并且在这里执行具体的任务,否则就有可嫩出现主线程被塞的情况。

2、Android 多线程编程

2.1 线程的基本用法

2.1.1 继承Thread类的方式
    class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
        }
    }
        new MyThread().start();

使用继承的方式耦合性会有点高

2.1.2 实现Runnable接口的方式
    class MyThread implements Runnable{

        @Override
        public void run() {

        }
    }
        MyThread myThread = new MyThread();
        new Thread(myThread).start();

2.1.3 使用匿名内部类的方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).start();

2.2 在子线程中更新UI

Android的UI是线程不安全的,也就是说,如果 想要更新应用程序中的UI元素,则必须在主线程中进行,否则就会出现异常。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private TextView tvText;
    private Button btnChange;

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

        tvText = (TextView) findViewById(R.id.text);
        btnChange = (Button) findViewById(R.id.change_text);
        btnChange.setOnClickListener(this);

    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        tvText.setText("android");
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

image.png

正确的做法如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private TextView tvText;
    private Button btnChange;
    private static final int UPDATE_TEXT = 1;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case UPDATE_TEXT:
                    tvText.setText("Android");
                    break;
            }
        }
    };

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

        tvText = (TextView) findViewById(R.id.text);
        btnChange = (Button) findViewById(R.id.change_text);
        btnChange.setOnClickListener(this);

    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.change_text:
                Message message = new Message();
                message.what = UPDATE_TEXT;
                handler.sendMessage(message);
                break;
            default:
                break;
        }
    }
}

image.png

2.3 解析异步信息处理机制

android中异步信息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
1)、Message
在线程之间传递信息,它可以在内部携带少量的信息,用于在不同线程之间交互数据。
Message.what字段:携带int类型的标志
Message.arg1和Message.arg2字段:携带一些整数数据
Message.obj字段:携带一个Object对象

2)、Handler
顾明思义也就是处理者的意思,主要是用于发送和处理信息的。发送信息一般是使用Handler的sendMessage()方法,而发出的信息经过一系列辗转处理后,最终会传递到Handler的handleMessage()方法中

3)、MessageQueue
信息队列的意思,它主要用于存放所有通过Handler发送的信息,这部分信息会一直存放在信息队列中,等待被处理,每个线程中只会有一个MessageQueue对象。

4)、Looper
是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条信息,就会将它取出,并且传递到Hander的handleMessage()方法中,每个线程也只有一个Looper对象


image.png

2.4 使用AsyncTask

AsyncTask背后的原理是基于异步信息处理机制的,只是android帮我们做了很好的封装。


    /***
     * 在继承AsyncTask时会指定3个泛型参数:
     * Params:在执行AsyncTask时需要传入的参数,可以用于在后台任务中使用
     * Progress: 后台任务执行时,需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
     * Result: 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
     */
    class DownloadTask extends AsyncTask{


        /**
         * 在后台任务开始之前调用,用于在显示初始化操作,比如显示进度
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        /**
         * 处理耗时任务,任务一旦完成就可以通过return语句将任务结果返回
         * 第三个参数是Void,就表示不返回任务结果
         * 任务过程中可以调用publishProgress(Progress...)方法将进度发送出去
         * 注意:这个方法是不可以进行UI操作的
         * @param voids
         * @return
         */
        @Override
        protected Boolean doInBackground(Void... voids) {
            return null;
        }


        /**
         * 接收publishProgress(Progress...)方法发送的进度,可以在这里进行UI操作,比如更新进度
         * @param values
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        /**
         * 当后台任务执行完毕并且通过return语句返回时,这个方法就会被调用,可以作一些结尾性的UI操作,比如关掉进度条
         * @param aBoolean
         */
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
        }
    }
        new DownloadTask().execute();

3、 服务的基本用法

3.1 定义一个服务

        
public class MyService extends Service {

    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() {
        super.onCreate();
    }

    /**
     * 服务启动成功时调用,逻辑操作在这里写,每次点击开始服务的按钮,这个方法都会执行一次
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 服务销毁时调用
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

3.2 启动和停止服务




    
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button btnStart;
    private Button btnStop;

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

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_start:
                Intent intent = new Intent(this,MyService.class);
                //启动服务
                startService(intent);
                break;
            case R.id.btn_stop:
                Intent intent = new Intent(this,MyService.class);
                //停止服务
                //如果没有点击停止按钮,如果想要服务停止,则需要需要在Service类中调用stopSelf()方法
                stopService(intent);
                break;
            default:
                break;
        }
    }
}

3.3 活动服务进行通信

public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    private DownloadBinder mBinder = new DownloadBinder();

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 服务创建的时候调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG,"onCreate===============");
    }


    /**
     * 服务销毁时调用
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy===============");
    }

    class DownloadBinder extends Binder{

        public void startDownload(){
            Log.d(TAG,"startDownload===============");
        }

        public int getProgress(){
            Log.d(TAG,"getProgress===============");
            return 0;
        }
    }
}

public class MainActivity3 extends AppCompatActivity implements View.OnClickListener{

    private Button btnStart;
    private Button btnStop;

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

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_start:
                Intent intent = new Intent(this,MyService.class);
                //启动服务
                startService(intent);
                break;
            case R.id.btn_stop:
                Intent intent2 = new Intent(this,MyService.class);
                //停止服务
                //如果没有点击停止按钮,如果想要服务停止,则需要需要在Service类中调用stopSelf()方法
                stopService(intent2);
                break;
            default:
                break;
        }
    }
}
image.png
image.png

4、 服务的生命周期

image.png

注意,如果一个服务调用了startService()方法,有调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?

根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService() 和 unbindService()方法,onDestroy()方法才会执行。

下面开始对生命周期进行模拟,以下面的这个图为例:


image.png

1)、多次点击开始服务以及多次点击停止服务


image.png

值得注意的是,每一次调用startService()方法,onStartCommon()方法就会执行一次,但是服务的实例只会有一个,由stopService()调用一次,服务就销毁可得知。

2)、多次点击绑定服务以及点击一次解绑服务


image.png

3)、在2的基础上,再点击一次解绑服务,如果代码是如下写的就会报错


image.png
image.png

应对错误,正确的写法如下所示:


image.png

4)、开始服务 ---> 绑定服务 ---> 解绑服务 ---> 停止服务


image.png

5)、开始服务 ---> 绑定服务 ---> 停止服务 ---> 解绑服务

image.png

6)、绑定服务 --->开始服务 ---> 停止服务 ---> 解绑服务

image.png

代码如下:




    
public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();

    private DownloadBinder mBinder = new DownloadBinder();

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind===============");
        return mBinder;
    }

    /**
     * 服务创建的时候调用
     */
    @Override
    public void onCreate() {
        super.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 boolean onUnbind(Intent intent) {
        Log.d(TAG,"onUnbind===============");
        return super.onUnbind(intent);
    }


    @Override
    public boolean stopService(Intent name) {
        Log.d(TAG,"stopService===============");
        return super.stopService(name);
    }

    /**
     * 服务销毁时调用
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy===============");
    }

    class DownloadBinder extends Binder{

        public void startDownload(){
            Log.d(TAG,"startDownload===============");
        }

        public int getProgress(){
            Log.d(TAG,"getProgress===============");
            return 0;
        }
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button btnBind;
    private Button btnUnBind;
    private Button btnStart;
    private Button btnStop;

    private MyService.DownloadBinder downloadBinder;

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

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
    private boolean bindService;

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

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        btnBind = (Button) findViewById(R.id.btn_bind);
        btnUnBind = (Button) findViewById(R.id.btn_unbind);

        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        btnBind.setOnClickListener(this);
        btnUnBind.setOnClickListener(this);
    }



    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_start:
                Intent startServiceIntent = new Intent(this,MyService.class);
                //开启服务
                startService(startServiceIntent);
                break;
            case R.id.btn_stop:
                //停止服务
                Intent stopServiceIntent = new Intent(this,MyService.class);
                //开启服务
                stopService(stopServiceIntent);
                break;
            case R.id.btn_bind:
                Intent bindIntent = new Intent(this,MyService.class);
                //绑定服务
                bindService = bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbind:

                if (bindService){
                    //解绑服务
                    unbindService(connection);
                    bindService = false;
                }

                break;

            default:
                break;
        }
    }
}

5、 服务的更多技巧

5.1 使用前台服务

服务几乎都是在后台运行的,但是服务的系统优先级还是比较低,当系统出现内存不足的情况下,就有可能会回收正在后台运行的服务。如果希望服务可以一直保持运行状态,而且不会因为系统内存不足的情况下导致被回收,就可以考虑使用前台服务。

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


image.png
image.png
public class MyService extends Service {

    private static final String TAG = MyService.class.getSimpleName();


    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind===============");
        return null;
    }

    /**
     * 服务创建的时候调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG,"onCreate===============");

        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("这是一个前台服务")
                .setContentText("只要敲不死就往死里敲")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();

        startForeground(1,notification);
    }

    @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===============");
    }


}

5.2 使用IntentService

服务是默认在主线程里面的,并且一旦开启如果不点击关闭,服务是不会关闭的,很容易出现ANR的情况,所以我们应该在服务的每个具体方法中去开启一个子线程,然后去做耗时操作,一个标准的写法如下:


image.png

但是如果忘记去调用stopSelf()方法的话,服务就一直不会关闭,这就很尴尬了,正是因为这个原因,才会有IntentService的出现。




    
public class MyService extends IntentService {

    private static final String TAG = MyService.class.getSimpleName();

    public MyService() {
        super("MyService");
    }


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

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Log.d(TAG,"onStart==============");
        super.onStart(intent, startId);
    }

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

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.d(TAG,"onHandleIntent==============");
        Log.d(TAG,"Thread namw is ====" + Thread.currentThread().getName()+"=======Thread id is ====" + Thread.currentThread().getId());
    }

    @Override
    public boolean stopService(Intent name) {
        Log.d(TAG,"stopService==============");
        return super.stopService(name);
    }

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = MyService.class.getSimpleName();

    private Button btnStart;


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

        btnStart = (Button) findViewById(R.id.btn_start);


        btnStart.setOnClickListener(this);

        Log.d(TAG,"MainActivity  Thread namw is ====" + Thread.currentThread().getName()+"=======MainActivity  Thread id is ====" + Thread.currentThread().getId());

    }



    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_start:
                Intent startServiceIntent = new Intent(this,MyService.class);
                //开启服务
                startService(startServiceIntent);
                break;

            default:
                break;
        }
    }
}

image.png

5.3 使用IntentService

6、 服务的最佳实践-----完整版的㐇下载示例

DownloadListener.java文件

public interface DownloadListener {

    void onProgress(int progress);

    void onSuccess();

    void onFailed();

    void onPaused();

    void onCanceled();

}

DownloadTask.java文件

public class DownloadTask extends AsyncTask {

    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_CANCELED = 3;


    private DownloadListener listener;

    private boolean isCanceled = false;

    private boolean isPaused = false;

    private int lastProgress;

    public DownloadTask(DownloadListener listener) {
        this.listener = listener;
    }

    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadedLength = 0; // 记录已下载的文件长度
            String downloadUrl = params[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();
            }
            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", "bytes=" + 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_CANCELED;
                    } else if(isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        // 计算已下载的百分比
                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                        publishProgress(progress);
                    }
                }
                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 status) {
        switch (status) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
            default:
                break;
        }
    }

    public void pauseDownload() {
        isPaused = true;
    }


    public void cancelDownload() {
        isCanceled = true;
    }

    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null && response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.close();
            return contentLength;
        }
        return 0;
    }

}

DownloadService,java文件

public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private String downloadUrl;

    private DownloadListener listener = new DownloadListener() {
        @Override
        public void onProgress(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
        public void onSuccess() {
            downloadTask = null;
            // 下载成功时将前台服务通知关闭,并创建一个下载成功的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Success", -1));
            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onFailed() {
            downloadTask = null;
            // 下载失败时将前台服务通知关闭,并创建一个下载失败的通知
            stopForeground(true);
            getNotificationManager().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 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);
                startForeground(1, getNotification("Downloading...", 0));
                Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
            }
        }

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

        public void cancelDownload() {
            if (downloadTask != null) {
                downloadTask.cancelDownload();
            } else {
                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, "Canceled", 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);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress >= 0) {
            // 当progress大于或等于0时才需显示下载进度
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }

}

MainActivity.java文件

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = MyService.class.getSimpleName();

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (DownloadService.DownloadBinder) service;
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startDownload = (Button) findViewById(R.id.start_download);
        Button pauseDownload = (Button) findViewById(R.id.pause_download);
        Button cancelDownload = (Button) findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this, DownloadService.class);
        startService(intent); // 启动服务
        bindService(intent, connection, BIND_AUTO_CREATE); // 绑定服务
        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);
        }
    }

    @Override
    public void onClick(View v) {
        if (downloadBinder == null) {
            return;
        }
        switch (v.getId()) {
            case R.id.start_download:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, 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:
        }
    }

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

一开始跑程序的时候,一直提示下载出错,看来看去不知道问题在哪里,把下载链接放到pc端下载,发现是可以下载的,后来找了网上搜索了一下错误,尼玛,最后发现是自己的手机没有联网,坑爹啊!


image.png
image.png
image.png
image.png

7、Service 和 BroadCastReceiver结合使用的下载案例(兼容Android7.0)

7.1 简单聊聊android6.0 和 android7.0 的区别

android6.0 有运行时权限

android7.0引入了:
1)私有目录被限制访问
为了提高私有文件的安全性,面向Android N或者更高版本的私有目录将被限制访问,有点类似IOS的沙箱机制

2)StrictMode API 政策
禁止向你应用外公开file://URI , 如果一项包含文件file://URI 类型的intent离开你的应用,应用会出现FileUriExposedException异常,要使用FileProvider来解决问题。

7.2 运行时权限Rxpermissions的使用

    implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'
    implementation 'io.reactivex:rxjava:1.1.6' //需要引入RxJava

    
    
        //下载需要写SD卡权限, targetSdkVersion>=23 需要动态申请权限
        RxPermissions.getInstance(this)
                // 申请权限
                .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe(new Action1() {
                    @Override
                    public void call(Boolean granted) {
                        if(granted){
                            //请求成功
                            startDownload(downloadUrl);
                        }else{
                            // 请求失败回收当前服务
                            stopSelf();

                        }
                    }
                });

7.3 使用FileProvider的步骤

1)清单文件注册

        
            
        

android:exported 必须为false,为true则报应用安全问题

android:grantUriPermissions="true" URI临时访问权限

android:authorities 组件标识,一般是包名开头 + fileprovider

2)指定共享目录

android:resource="@xml/file_paths"

image.png


    
        
    

image.png
image.png

3)使用FileProvider

   /**
     * 通过隐式意图调用系统安装程序安装APK
     */
    public static void install(Context context) {
        File file = new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                , "myApp.apk");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        // 由于没有在Activity环境下启动Activity,设置下面的标签
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if(Build.VERSION.SDK_INT>=24) { //判读版本是否在7.0以上
            //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
            Uri apkUri =
                    FileProvider.getUriForFile(context, "com.a520wcf.chapter11.fileprovider", file);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        }else{
            intent.setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

7.4 完整代码展示

public class DownLoadService extends Service {
    /**广播接受者*/
    private BroadcastReceiver receiver;
    /**系统下载管理器*/
    private DownloadManager dm;
    /**系统下载器分配的唯一下载任务id,可以通过这个id查询或者处理下载任务*/
    private long enqueue;
    /**下载地址*/
//    private String downloadUrl="http://dakaapp.troila.com/download/daka.apk?v=3.0";

    private String downloadUrl = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                install(context);
                /*销毁当前的service*/
                stopSelf();
            }
        };

        registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        //下载需要写SD卡权限, targetSdkVersion>=23 需要动态申请权限
        RxPermissions.getInstance(this)
                // 申请权限
                .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe(new Action1() {
                    @Override
                    public void call(Boolean granted) {
                        if(granted){
                            //请求成功
                            startDownload(downloadUrl);
                        }else{
                            // 请求失败回收当前服务
                            stopSelf();

                        }
                    }
                });

        return Service.START_STICKY;
    }

    /**
     * 通过隐式意图调用系统安装程序安装APK
     */
    public static void install(Context context) {
        File file = new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                , "myApp.apk");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        // 由于没有在Activity环境下启动Activity,设置下面的标签
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if(Build.VERSION.SDK_INT>=24) { //判读版本是否在7.0以上
            //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
            Uri apkUri =
                    FileProvider.getUriForFile(context, "com.a520wcf.chapter11.fileprovider", file);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        }else{
            intent.setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

    @Override
    public void onDestroy() {
        //服务销毁的时候 反注册广播
        unregisterReceiver(receiver);
        super.onDestroy();
    }

    private void startDownload(String downUrl) {
        /**获得系统下载器*/
        dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        /**设置下载地址*/
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downUrl));
        /**设置下载文件的类型*/
        request.setMimeType("application/vnd.android.package-archive");
        /**设置下载存放的文件夹和文件名字*/
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "myApp.apk");
        /**设置下载时或者下载完成时,通知栏是否显示*/
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setTitle("下载新版本");
        /**执行下载,并返回任务唯一id*/
        enqueue = dm.enqueue(request);
    }
}

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = MyService.class.getSimpleName();
    private Intent intent;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnDownLoad = (Button) findViewById(R.id.btn_download);
        btnDownLoad.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.btn_download:
                intent = new Intent(this, DownLoadService.class);
                startService(intent); // 启动服务
                break;
            default:
                break;
        }
    }



    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(intent);
    }
}

image.png
image.png

你可能感兴趣的:(第11章 探究服务)