简单的使用服务
服务的概念:
Service分为本地服务(LocalService)和远程服务(RemoteService):
本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。
远程服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
- 按使用方式可以分为以下三种:
startService 启动的服务:主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService;
bindService 启动的服务:该方法启动的服务可以进行通信。停止服务使用unbindService;
startService 同时也 bindService 启动的服务:停止服务应同时使用stopService与unbindService
测试->
先创建一个服务:
public class ServiceTest extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.d("ServiceTest", " -----> onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("ServiceTest", " -----> onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("ServiceTest", " -----> onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
return new MyBind();
}
class MyBind extends Binder {
public void getString() {
Log.d("ServiceTest", " -----> getString");
}
}
}
修改布局文件在活动中启动服务
在活动中启动或停止服务
public class MainActivity extends Activity {
private Button startService,stopService,bindService,unbindService;
private ServiceTest.MyBind myBind;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBind = (ServiceTest.MyBind) service;
myBind.getString(); //获取到getString方法
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService = findViewById(R.id.start_service);
stopService = findViewById(R.id.stop_service);
bindService = findViewById(R.id.bind_service);
unbindService = findViewById(R.id.unbind_service);
/**
* 开启服务
*/
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startService = new Intent(MainActivity.this,ServiceTest.class);
startService(startService);
}
});
/**
* 停止服务
*/
stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent stopService = new Intent(MainActivity.this,ServiceTest.class);
stopService(stopService);
}
});
/**
* 绑定服务
*/
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent bindService = new Intent(MainActivity.this,ServiceTest.class);
bindService(bindService,connection,BIND_AUTO_CREATE);
}
});
/**
* 解绑服务
*/
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(connection);
}
});
}
}
- 可以看到,这里我们首先创建了一个ServiceConnection的匿名类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。在onServiceConnected()方法中,我们又通过向下转型得到了MyBind的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用MyBind中的任何public方法,即实现了Activity指挥Service干什么Service就去干什么的功能。
注:
被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。
被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。
被启动又被绑定的服务的生命周期:当我们对一个服务既调用了 startService() 方法,又调用了 bindService() 方法时,要同时调用 stopService() 和 unbindService() 方法,onDestroy() 方法才会执行。
点击几次开启服务后点击一次停止服务结果图:
只有点击开启服务才会执行onStartCommand方法
只有点击绑定服务才会执行getString方法
使用前台服务,避免系统出现内存不足的情况时回收掉正在运行的后台服务
创建服务
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload(){
Log.d("MyService","startDownload executed");
}
public int getProgress(){
Log.d("MyService","getProgress executed");
return 0;
}
}
public MyService() {
}
public void onCreate(){
super.onCreate();
Log.d("MyService","onCreate executed");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new Notification.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);
}
public int onStartCommand(Intent intent,int flags,int startId){
Log.d("MyService","onStartCommand executed");
return super.onStartCommand(intent,flags,startId);
}
public void onDestroy(){
super.onDestroy();
Log.d("MyService", "onDestroy executed");
}
@Override
public IBinder onBind(Intent intent) {
Log.e("MyService", "onBind(Intent intent)");
return mBinder;
}
}
修改布局:
修改活动:
public class MainActivity extends Activity {
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 componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button startService = findViewById(R.id.start_service);
Button stopService = findViewById(R.id.stop_service);
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent startIntent = new Intent(MainActivity.this,MyService.class);
startService(startIntent);
}
});
stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent stopIntent = new Intent(MainActivity.this,MyService.class);
stopService(stopIntent);
}
});
final Button bindService = (Button) findViewById(R.id.bind_service);
final Button unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent bindIntent = new Intent(MainActivity.this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection);
}
});
}
}
结果图:
- 在Activity中,启动Service有两种方式:startService方式,bindService方式。
如果想要调用Service中的方法,只能使用bindService方式。因为这种方式可以拿到Service的Binder对象,从而可以调用Service中的方法。
IntentService的使用
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
//一定要有这个构造函数
public MyIntentService() {
super(MyIntentService.class.getName());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 处理耗时操作,已经开起来新的线程
Log.d(TAG, "onHandleIntent: ");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
在活动里开启服务即可
Intent intentService=new Intent(MainActivity.this,MyIntentService.class);
startService(intentService)
Serviec与IntentService的区别
- Service中的程序仍然运行于主线程中,而在IntentService中的程序运行在我们的异步后台线程中。在接触到IntentService之前,我在项目中大多是自己写一个Service,在Service中自己写一个后台线程去处理相关事务,而这些工作Android其实早已经帮我们封装的很好。
- 在Service中当我的后台服务执行完毕之后需要在外部组件中调用stopService方法销毁服务,而IntentService并不需要,它会在工作执行完毕后自动销毁。
IntentService具有以下特点:
- IntentService自带一个工作线程,当我们的Service需要做一些可能会阻塞主线程的工作的时候可以考虑使用IntentService。
- 我们需要将要做的实际工作放入到IntentService的onHandleIntent回到方法中,当我们通过startService(intent)启动了IntentService之后,最终Android Framework会回调其onHandleIntent方法,并将intent传入该方法,这样我们就可以根据intent去做实际工作,并且onHandleIntent运行在IntentService所持有的工作线程中,而非主线程。
- 当我们通过startService多次启动了IntentService,这会产生多个job,由于IntentService只持有一个工作线程,所以每次onHandleIntent只能处理一个job。面多多个job,IntentService会如何处理?处理方式是one-by-one,也就是一个一个按照先后顺序处理,先将intent1传入onHandleIntent,让其完成job1,然后将intent2传入onHandleIntent,让其完成job2…这样直至所有job完成,所以我们IntentService不能并行的执行多个job,只能一个一个的按照先后顺序完成,当所有job完成的时候IntentService就销毁了,会执行onDestroy回调方法。
使用服务实现下载功能:
- 添加权限
- 定义一个接口
public interface DownloadListener {
void progress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}
- 定义一个异步下载机制
public class DownloadTask extends AsyncTask {
private static final int TYPE_SUCCESS = 0;
private static final int TYPE_FAILED = 1;
private static final int TYPE_PAUSED = 2;
private static final int TYPE_CANCELED = 3;
private DownloadListener listener;
private boolean isCanceled = false;
private boolean isPause = false;
private int lastProgress;
public DownloadTask(DownloadListener downloadListener) {
listener = downloadListener;
}
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadLength = 0;//记录已经下载过文件的长度
String downloadUrl = params[0];
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));//解析 url 文件名
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//sd卡 DownLoad 目录
file = new File(directory + fileName);
if (file.exists()) {
//如果已经存在读取已下载的字节数
downloadLength = file.length();
}
long contentLength = getContentLength(downloadUrl);//获取文件总长度
if (contentLength == 0) {
return TYPE_FAILED;
} else if (contentLength == downloadLength) {
//已下载字节和文件总字节相等,说明已经下载完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//断点下载,指定从哪个字节开始下载
//告诉服务器 我想要从哪个字节开始下载
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();//使用文件流方式读取
savedFile = new RandomAccessFile(file, "rw");
savedFile.seek(downloadLength);//跳过已经下载的字节
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1) {
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPause) {
return TYPE_PAUSED;
} else {
total += len;
savedFile.write(b, 0, len);
int progress = (int) ((total + downloadLength) * 100 / contentLength);
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;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
//和上次下载进度 比 有变化调用接口
if (progress > lastProgress) {
listener.progress(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_CANCELED:
listener.onCanceled();
break;
}
}
void pauseDownload() {
isPause = true;
}
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 conentLength = response.body().contentLength();
response.body().close();
return conentLength;
}
return 0;
}
}
4.定义下载服务
public class DownloadService extends Service {
private DownloadTask downloadTask;
private String downloadUrl;
private DownloadListener listener = new DownloadListener() {
@Override
public void progress(int progress) {
getNotificationManager().notify(1, getNotification("Downloading...", progress));
}
@Override
public void onSuccess() {
downloadTask = null;
stopForeground(true);
getNotificationManager().notify(1, getNotification("Download Sucess", -1));
Toast.makeText(DownloadService.this, "DownloadSucess", 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, "Download onPaused", Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "Download onCanceled", Toast.LENGTH_SHORT).show();
}
};
private Notification getNotification(String title, int progress) {
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification.Builder builder = new Notification.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();
}
private NotificationManager getNotificationManager(){
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
public DownloadService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return new DownloadBinder();
}
class DownloadBinder extends Binder{
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();
}
}
void pauseDownload(){
if (downloadTask != null) {
downloadTask.pauseDownload();
}
}
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, "Canceled", Toast.LENGTH_SHORT).show();
}
}
}
}
5.修改主活动
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
private Button startService,pauseService,cancelService;
private DownloadService.DownloadBinder downloadBinder;
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 onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService = (Button) findViewById(R.id.start);
pauseService = (Button) findViewById(R.id.pause);
cancelService = (Button) findViewById(R.id.cancel);
startService.setOnClickListener(this);
pauseService.setOnClickListener(this);
cancelService.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:
String url = "https://raw.githubusercontent.com/guolindev/eclipse/" +
"master/eclipse-inst-win64.exe";
downloadBinder.startDownload(url);
break;
case R.id.pause:
downloadBinder.pauseDownload();
break;
case R.id.cancel:
downloadBinder.cancelDownload();
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(MainActivity.this, "拒绝权限将无法使用程序!",
Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
//如果活动被销毁了,那么一定要记得对服务进行解绑,不然就有可能会造成内存泄露。
@Override
protected void onDestroy()
{
super.onDestroy();
unbindService(connection);
}
}
结果图: