Service

1. 服务是什么

  • 服务是Android中实现实现程序后台运行的解决方案,他非常适合用于去执行哪些不需要和用户交互而且还要求长期运行的任务。
  • 服务默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样运行在UI线程中。因此,不要在Service中执行耗时的操作。除非你在Service中创建了子线程来完成耗时的操作。
  • 服务不依赖于用户界面,及时程序被切换到后台,依然可以正常运行。但是应用程序的进程被杀掉时,所有依赖于该进程的Service也会停止运行。

2. 普通Service

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        doMyJob();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void doMyJob() {
        //从Intent中获取数据
        //执行相关操作
    new Thread(new Runnable() {
                @Override
                public void run() {

                }
            }).start();
        }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true">
</service>
Intent intent_start = new Intent(MainActivity.this,MyService.class);
startService(intent_start);
Intent intent_stop = new Intent(MainActivity.this,MyService.class);
stopService(intent_stop);
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        doMyJob();
        stopSelf();
        Log.e(TAG, "onStartCommand::::");
        return super.onStartCommand(intent, flags, startId);
    }

说明:
1. onBind是Service中唯一的抽象方法,必须在子类实现。
2. onCreate方法会在服务创建的时候调用,onStartCommand方法会在每次服务启动的时候调用,onDestroy方法会在服务销毁的时候调用。
3. 我们希望服务一旦启动就立刻执行某个动作,就可以将逻辑写在onStartCommand方法中,而当服务销毁时,我们应该在onDestroy方法中去回收哪些不再使用的资源。
4. 不要忘记在配置文件中进行注册:enabled属性表示是否启用这个服务,exported属性表示是否允许除了当前程序之外的其他程序访问这个服务。
5. startService开启服务,stopService关闭服务,这些是在服务类之外调用的,而stopSelf方法自己是在服务类之内调用的。
6. onCreate和onStartCommand方法的区别就是onCreate是在服务第一次创建的时候调用的,如果我们多次点击StartService,就会发现只有onStartCommand方法执行。

3. 前台Service

  • Service是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的情况时,它就有可能会被回收掉。如果希望Service可以一直保持运行状态,而不会由于系统不足被回收,可以将Service运行在前台。
  • 前台服务不仅不会被系统无情地回收,它还会在通知栏显示一条消息,下拉状态栏后可以看到更加详细的信息。
@Override
public void onCreate() {
    super.onCreate();
    Log.e(TAG, "onCreate::::");
    showForeApplication();
}

private void showForeApplication() {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is ContentTitle")
                .setContentText("This is ContentText")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1, notification);
    }

说明:
1. 创建前台Service,并没有用NotificationManager将Notification显示出来,而是调用了startForeground方法。调用这个方法Service就会变成一个前台服务,并在系统的状态栏显示出来。
2. startForeground方法接收两个参数:第一个参数是通知的id,类似于notify方法的第一个参数,第二个参数则是构建出的Notification对象。

4. IntentService

  • 首先:完成一个简单的后台任务还是比较麻烦的,需要自己开启一个子线程。其次:开启普通Service后,需要我们手动去关闭Service。基于这两点原因,Android为我们提供了IntentService。
  • IntentService将用户的请求执行在一个子线程中,用户只需要去覆写onHandlerIntent函数,并且在该函数中完成自己的耗时操作即可
  • 需要注意:在任务执行完毕之后IntentService会调用stopSelf自我销毁,因此,它适用于完成一些短期的耗时任务。
public class MyIntentService extends IntentService {


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

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //写耗时的操作
    }
}

5. Activity与Service通信

  • 上面我们在活动中启动了服务,但是启动完以后,两者就没有交集了。服务会一直运行,但具体运行什么逻辑,活动就控制不了了。也就是说服务在忙自己的事情,活动并不知道服务在忙什么,以及完成的如何。
  • 这里我们借助onBind方法,指挥服务干什么,服务就去干什么。

  • 比如说我们希望在MyService里提供一个下载功能,然后在活动中可以决定何时下载,以及随时查看下载进度。实现这个功能的思路就是创建一个专门的binder对象来对下载功能进行管理。

  • 首先我们定义我们的服务类MyService:

public class MyService extends Service {

    //创建实例
    private DownLoadBinder mDownLoadBinder = new DownLoadBinder();

    public MyService() {
    }

    //返回实例
    @Override
    public IBinder onBind(Intent intent) {
        return mDownLoadBinder;
    }

    //新建DownLoadBinder类继承Binder,然后在它的内部提供了开始下载以及查看下载的方法
    class DownLoadBinder extends Binder {

        public void startDownLoad() {
            Log.e("MyService", "startDownLoad executed");
        }

        public int getProgress() {
            Log.e("MyService", "getProgress executed");
            return 0;
        }

    }

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

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

说明:
说明:Activity中负责Activity与Service进行通信的核心为DownLoadBinder。

  • 创建我们的MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mButton_Bind;
    private Button mButton_Unbind;
    //拿到服务中的DownLoadBinder
    private MyService.DownLoadBinder mDownLoadBinder;
    //创建ServiceConnection匿名类,重写onServiceConnected和onServiceDisconnected方法
    private ServiceConnection connection = new ServiceConnection() {

        /*
        成功绑定;ServiceConnection提供一个IBinder,它向下转型就是服务中的Binder,就可以调用服务中的方法
        这样就可以指挥服务做事情
         */
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mDownLoadBinder = (MyService.DownLoadBinder) iBinder;
            mDownLoadBinder.startDownLoad();
            mDownLoadBinder.getProgress();
        }
        //解除绑定
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

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

    private void initView() {
        mButton_Bind = (Button) findViewById(R.id.bt_bind);
        mButton_Unbind = (Button) findViewById(R.id.bt_unbind);
        mButton_Bind.setOnClickListener(this);
        mButton_Unbind.setOnClickListener(this);
    }

    /**
     * 活动和服务绑定后,我们就可以用服务Binder中的方法了。
     * @param view
     */
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.bt_bind:
                /*
                活动和服务进行绑定,bindService第一个参数就是Service的Intent意图,第二个参数就是ServiceConnection
                第三个参数是一个标志位,传入BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务
                这会让MyService中的onCreate方法执行
                 */
                Intent bindIntent = new Intent(this,MyService.class);
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.bt_unbind:
                //活动和服务进行解绑
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

说明:Activity中负责Activity与Service进行通信的核心为ServiceConnection。

6. Service的生命周期

  • 启动方式一:startService会开启服务。如果服务还没有创建过,会先执行onCreate方法,然后执行onStartCommand方法。如果服务已经创建过,只会执行onStartCommand方法。服务一旦开启就会执行下去,直到stopService方法或者stopSelf方法被调用。需要注意的是,每调用一次startService方法,onStartCommand就会执行一次,但实际上每个服务都只会存在一个实例。不管调用多少次startService,只需调用一次stopService或stopService方法,服务就会停止下来。

  • 启动方式二:bindService会建立活动和服务的长久连接,这时会回调服务中的onBind方法。假如服务没有创建过,依然会先执行onCreate方法,然后再执行onBind方法,不过onStartCommand方法并不会执行,onStartCommand专属于startService这种启动方式。调用方可以获取到onBind方法里返回的IBinder实例,这样就可以自由地进行通信了,知道unBindService方法执行,服务都会一直继续下去。

  • 假如既调用了startService,又调用了bindService,那只有同时调用stopService和unBindService,onDestory方法才会得以执行。

7. 栗子:利用服务进行断点下载

  • 回调接口:
/**
 * Created by FuKaiqiang on 2017-09-27.
 * 用于对下载过程中的各种状态进行监听和回调
 */

public interface DownLoadListener {

    //用于通知当前的下载进度
    void onProgress(int progress);

    //同于通知下载成功事件
    void onSuccess();

    //用于通知下载失败事件
    void onFailed();

    //用于通知下载暂停事件
    void onPaused();

    //用于通知下载取消事件
    void onCanceled();

}
  • 下载工具类:
/**
 * Created by FuKaiqiang on 2017-09-27.
 * 角色:工具类
 * 任务:下载
 * 第一个泛型参数指定为String:表示在执行AsyncTask的时候需要传入一个字符串参数给后台任务
 * 第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位
 * 第三个泛型参数指定为Integer,表示使用整形数据来反馈执行结果
 */

public class DownloadTask extends AsyncTask<String, Integer, Integer> {
    //下载成功
    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 mListener;
    private boolean isCanceled = false;
    private boolean isPaused = false;
    private int lastProgress;

    //通过构造方法,把我们定义的接口,以此来回调下载的状态
    public DownloadTask(DownLoadListener listener) {
        mListener = listener;
    }

    /**
     * 用于在后台执行具体的下载逻辑
     *
     * @param strings
     * @return
     */
    @Override
    protected Integer doInBackground(String... strings) {
        //读取用的是inputStream
        InputStream is = null;
        //断点写入用的是RandomAccessFile
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadedLength = 0; //记录已下载的文件长度
            String downloadUrl = strings[0]; //解析出下载的地址
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/")); //并根据下载的地址解析户文件名
            //指定文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的DownLoad目录
            //getExternalStoragePublicDirectory该函数返回特定类型的目录,比如音乐或者铃声或者相册的位置的目录
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
            //判断下DownLoad目录中是否已经存在要下载的文件了
            file = new File(directory + fileName);
            if (file.exists()) {
                //如果存在,则读取已下载的字节数,这样就可以在后面启用断电续传的功能
                downloadedLength = file.length();
            }
            //获取要下载的文件的总长度
            long contentLength = getContentLength(downloadUrl);
            //如果长度为0,说明文件有问题,直接返回TYPE_FAILED
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                //如果已下载字节和文件总字节相等,说明已经下载完成,直接返回TYPE_SUCCESS
                return TYPE_SUCCESS;
            }
            //开启Okhttp下载
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    //在请求中添加第一个header,用于告诉"服务器"要从哪个字节开始下载,因为已经下载的部分就不需要重新下载。
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            //读取服务器相应的数据response
            if (response != null) {
                is = response.body().byteStream(); //转换为java文件流
                //RandomAccessFile非常适合多线程下载和断点续传的IO类:第一个参数File对象,第二个参数是文件的读取权限
                savedFile = new RandomAccessFile(file, "rw");
                //seek用来定位到某个字节处,即某个断电处
                savedFile.seek(downloadedLength);
                //一次读取1024个字节
                byte[] b = new byte[1024];
                //定义总量
                int total = 0;
                //定义一次每次读取的量
                int len;
                while ((len = is.read(b)) != -1) {
                    //判断用户有没有触发暂停或取消的操作,如果有返回TYPE_CANCELED或者TYPE_PAUSED来中断下载
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        //每次读取的和之前读取的加起来就是读取的总量
                        total += len;
                        //用RandomAccessFile来进行写的操作,把数组b中,从索引0开始,长度为len的字节,也就是一次写入len个字节
                        savedFile.write(b, 0, len);
                        /*
                        如果用户没有触发暂停或取消的操作,则实时计算当前的下载进度
                        总共下载的进度:downloadedLength意思是以前下载的总量+这次下入的总量=总的下载量
                         */
                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                        //AsyncTask中的方法:用于通知进度
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                //
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }


    /**
     * 用于在界面更新当前的下载进度
     * 首先从参数中获取到当前的下载进度,然后和上一次的下载进度进行对比,有变化的话调用DownLoadListener中的onProgress
     * 方法来通知下载进度更新。
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            mListener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 用于通知最终的下载结果
     * 根据参数中传入的下载状态进行回调
     * @param integer
     */
    @Override
    protected void onPostExecute(Integer integer) {
        switch (integer) {
            case TYPE_SUCCESS:
                mListener.onSuccess();
                break;
            case TYPE_FAILED:
                mListener.onFailed();
                break;
            case TYPE_PAUSED:
                mListener.onPaused();
                break;
            case TYPE_CANCELED:
                mListener.onCanceled();
            default:
                break;
        }
    }

    public void pauseDownload() {
        isPaused = true;
    }

    public void cancelDownload() {
        isCanceled = true;
    }

    /**
     * 用于获取下载文件的总长度
     * @param downloadUrl
     * @return
     * @throws IOException
     */
    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;
    }
}
  • 服务类:
/**
 * 服务类:听指挥的类,负责下载的小兵
 */

public class MyDownloadService extends Service {

    private DownloadTask mDownloadTask;

    private String downloadUrl;

    //匿名类实例,实现五个方法,负责成功、失败、暂停、取消,进度的具体逻辑
    private DownLoadListener mDownLoadListener = new DownLoadListener() {

        @Override
        public void onProgress(int progress) {
            //标题+进度
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

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

        @Override
        public void onFailed() {
            mDownloadTask = null;
            //下载失败时将前台服务通知关闭,并创建一个下载失败的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(MyDownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
        }

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

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

    private DownloadBinder mBinder = new DownloadBinder();

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

    //为了让活动和服务通信,我们创建了DownloadBinder
    class DownloadBinder extends Binder {
        //开始下载
        public void startDownload(String url) {
            if (mDownloadTask == null) {
                downloadUrl = url;
                //创建DownloadTask实例,传入DownLoadListener监听
                mDownloadTask = new DownloadTask(mDownLoadListener);
                //execute启动下载,传入url
                mDownloadTask.execute(downloadUrl);
                //在系统的状态栏创建一个持续运行的通知
                startForeground(1, getNotification("Downloading..", 0));
                Toast.makeText(MyDownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
            }
        }

        //暂停下载
        public void pauseDownload() {
            if (mDownloadTask != null) {
                mDownloadTask.pauseDownload();
            }
        }

        //取消下载(cancelDownload同名一定要分清)
        public void cancelDownload() {
            if (mDownloadTask != null) {
                mDownloadTask.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(MyDownloadService.this, "之前下载文件被删除", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    //封装一个NotificationManager
    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    //封装一个Notification
    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.setContentTitle(title);
        builder.setLights(Color.GREEN, 1000, 1000);
        builder.setPriority(NotificationCompat.DEFAULT_ALL);
        if (progress >= 0) {
            //当progress大于或者等于0时才需显示下载进度
            builder.setContentText(progress + "%");
            //第一个参数传入通知的最大进度;第二个参数传入通知的当前进度;第三个参数表示是否使用模糊进度条
            builder.setProgress(100, progress, false);
        }
        builder.setContentIntent(pi);
        return builder.build();
    }
}
  • 活动类:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyDownloadService.DownloadBinder mDownloadBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mDownloadBinder = (MyDownloadService.DownloadBinder) iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
    private Intent mIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bt_start = (Button) findViewById(R.id.start_download);
        Button bt_pause = (Button) findViewById(R.id.pause_download);
        Button bt_cancel = (Button) findViewById(R.id.cancel_download);
        bt_start.setOnClickListener(this);
        bt_pause.setOnClickListener(this);
        bt_cancel.setOnClickListener(this);
        //启动服务
        mIntent = new Intent(this, MyDownloadService.class);
        startService(mIntent);
        //绑定服务
        bindService(mIntent, mConnection, 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 view) {
        if (mDownloadBinder == null) {
            return;
        }
        switch (view.getId()) {
            case R.id.start_download:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                mDownloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                mDownloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                mDownloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    @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;

        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(mIntent);
        unbindService(mConnection);
    }
}

说明:活动和服务建立通信,服务利用异步进行下载,上面每个类注释都非常详细,就不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值