开发Android服务
来源:互联网 发布:小米电力猫网络密码 编辑:程序博客网 时间:2024/06/07 12:29
前言
服务是Android中的一个应用,它在后台运行,不需要与用户有任何的交互。例如,当使用一个应用的时候,你希望同时可以在后台播放音乐。这时,在后台播放音乐的代码不需要与用户交互;因此,它可以作为一个服务运行。同时,当应用不需要提供用户界面(UI)的时候,服务也是理想的选择。对于这种情况由一个很好的示例应用是持续记录设备的地理坐标。这时,可以编写一个服务在后台运行。
在服务中执行长时间运行的任务
MainActivity
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onStartService(View view) { startService(new Intent(getApplicationContext(), MyService.class)); } public void onStopService(View view) { stopService(new Intent(getApplicationContext(), MyService.class)); }}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="link_work.myapplication.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/startService" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onStartService" android:text="@string/startService" /> <Button android:id="@+id/stopService" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onStopService" android:text="@string/stopService" /> </LinearLayout></LinearLayout>
这个实例演示了最简单的一个Service。服务本身不做任何有用的工作,当然它只是为了说明如何创建一个服务。
首先定义一个继承于Service基类的子类。所有的服务都继承于Service类:
public class MyService extends Service {}
在MyService类中,实现了三个方法:
public class MyService extends Service { @Override public IBinder onBind(Intent arg0) { ... } @Override public int onStartCommand(Intent intent, int flags, int startId) { ... } @Override public void onDestroy() { ... }}
要启动一个服务,调用startService()
方法:
startService(new Intent(getApplicationContext(), MyService.class));
要停止一个服务,调用StopService()
方法:
stopService(new Intent(getApplicationContext(), MyService.class));
先在中AndroidManifest.xml添加<service android:name=".Service.MyService" />
。
MyService
public class MyService extends Service { @Override public IBinder onBind(Intent arg0) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // We want this service to continue running until it is explicitly // stopped, so return sticky. Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); try { int result = downLoadFile(new URL("http://www.amazon.com/somefile.pdf")); Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes", Toast.LENGTH_LONG).show(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return START_STICKY; } private int downLoadFile(URL url) { try { //---模拟下载--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //---随意返回一个任意值100--- return 100; } @Override public void onDestroy() { super.onDestroy(); Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show(); }}
注意:请注意在点击开启服务的之后,按钮会由1秒多的卡顿(按钮点击特效会有卡顿感)。
在本示例中,服务调用downloadFile()
方法模拟从给定的URL中下载一个文件。该方法返回下载的字节总数(这里硬编码为100)。为了模拟当下载文件时服务所经历的延时,这里使用Thead.Sleep()
方法将服务暂停5秒钟(5000毫秒)。
当启动服务的时候,注意Activity会有5秒钟的停顿。这是从网络下载文件所花的时间。在这段时间中,整个Activity没有任何响应,这也演示了一个非常重要的知识点:服务和Activity运行在相同的线程上。在这种情况下,因为服务停顿5秒钟,所以Activity也同样停顿5秒钟。
也就是说,对于一个长时间运行的服务来说,必须将所有耗时代码放在一个独立的线程中,这样就不会影响调用它的应用。
在服务中创建异步执行任务
MyService
public class MyService extends Service { @Override public IBinder onBind(Intent arg0) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // We want this service to continue running until it is explicitly // stopped, so return sticky. Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); try { new DoBackgroundTask().execute( new URL("http://www.amazon.com/somefiles.pdf"), new URL("http://www.wrox.com/somefiles.pdf"), new URL("http://www.google.com/somefiles.pdf"), new URL("http://www.learn2develop.net/somefiles.pdf")); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return START_STICKY; } private int downloadFile() { try { //---模拟下载停顿--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 随意返回一个下载文件的大小。 return 100; } @Override public void onDestroy() { super.onDestroy(); Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show(); } @SuppressLint("StaticFieldLeak") private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> { @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalBytesDownloaded = 0; for (int i = 0; i < count; i++) { totalBytesDownloaded += downloadFile(); // 在下载中不断更新进度条 publishProgress((int) (((i + 1) / (float) count) * 100)); } return totalBytesDownloaded; } @Override protected void onProgressUpdate(Integer... progress) { Log.d("Downloading files", String.valueOf(progress[0]) + "% downloaded"); Toast.makeText(getBaseContext(), String.valueOf(progress[0]) + "% downloaded", Toast.LENGTH_LONG).show(); } @Override protected void onPostExecute(Long result) { Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes", Toast.LENGTH_LONG).show(); // 当后台线程完成执行之后,需要手动调用stopSelf()方法停止服务。 // 该方法类似于调用stopService()方法停止服务。 stopSelf(); } }}
单击完开启服务按钮之后,Toast类显示信息指示下载的完成进度。你可以看到四条信息:25%、50%、75%、100%。
本示例说明了一个在服务中执行异步任务的方法。该方法通过创建一个继承于AsyncTask类的内部类。AsyncTask方法能够在不需要手动处理线程和执行者的情况下在后台执行操作。
publishProgress()
方法,它会调用下一个方法,onProgressUpdate()
。 onProgressUpdate() 该方法在UI线程中调用,当调用publishProgress()
方法的时候就会调用该方法。它接受一个数组作为参数。使用这个方法可以为用户汇报后台任何的进度。 onPostExecute() 这个方法在UI线程中调用,当doInBackground()
方法结束执行的时候就会调用该方法。在服务中执行重复任务
MyService
public class MyService extends Service { static final int UPDATE_INTERVAL = 1000; int counter = 0; private Timer timer = new Timer(); @Override public IBinder onBind(Intent arg0) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // We want this service to continue running until it is explicitly // stopped, so return sticky. doSomethingRepeatedly(); try { new DoBackgroundTask().execute( new URL("http://www.amazon.com/somefiles.pdf"), new URL("http://www.wrox.com/somefiles.pdf"), new URL("http://www.google.com/somefiles.pdf"), new URL("http://www.learn2develop.net/somefiles.pdf")); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return START_STICKY; } private void doSomethingRepeatedly() { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Log.d("MyService", String.valueOf(++counter)); } }, 0, UPDATE_INTERVAL); } private int downloadFile() { try { //---模拟下载延时--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return 100; } @Override public void onDestroy() { super.onDestroy(); if (timer != null) { timer.cancel(); } Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show(); } @SuppressLint("StaticFieldLeak") private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> { @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalBytesDownloaded = 0; for (int i = 0; i < count; i++) { totalBytesDownloaded += downloadFile(); //---calculate percentage downloaded and // report its progress--- publishProgress((int) (((i + 1) / (float) count) * 100)); } return totalBytesDownloaded; } @Override protected void onProgressUpdate(Integer... progress) { Log.d("Downloading files", String.valueOf(progress[0]) + "% downloaded"); Toast.makeText(getBaseContext(), String.valueOf(progress[0]) + "% downloaded", Toast.LENGTH_LONG).show(); } @Override protected void onPostExecute(Long result) { Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes", Toast.LENGTH_LONG).show(); stopSelf(); } }}
在本例中,创建了一个Timer对象,并在自定义的doSomethingRepeatedly()
方法中调用Timer
对象的scheduleAtFixedRate()
方法:
private void doSomethingRepeatedly() { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Log.d("MyService", String.valueOf(++counter)); } }, 0, UPDATE_INTERVAL);}
向scheduleAtFixedRate()
方法中传入了一个TimerTask
类的实例,从而可以在run()
方法中重复执行一段代码。scheduleAtFixedRate()
方法的第二个参数指定了第一次执行前的等待时间,以毫秒为单位。第三个参数指定了后续执行的时间间隔,以毫秒为单位。
以上示例代码实际上每秒打印计数器的数值(1000毫秒)。服务会重复打印计数器的数值直到服务被终止。
@Overridepublic void onDestroy() { super.onDestroy(); if (timer != null) { timer.cancel(); } Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();}
对于scheduleAtFixedRate()
方法来说,它会固定时间间隔执行任务,不管每次任务会消耗多长时间。
同样需要注意的是,在onStartCommand()
方法中直接调用doSomethingRepeatedly()
,而不需要将它封装在AsyncTask
类的子类中。这是因为TimerTask
类中自己实现了Runnable接口,能够允许它在独立的线程上运行。
使用IntentService在独立的线程中执行异步任务
使用Service的时候,需要时刻注意的就是,当服务结束执行一个任务以后,它应该立即停止从而可以释放宝贵的资源。这就是为什么当一个任务结束以后需要调用stopSelf()
方法停止服务的原因。遗憾的是,当任务完成以后,很多开发者经常忘记终止服务。为了方便地创建一个异步运行任务的服务,并且当任务结束的时候自动终止,可以使用IntentService
类。
作为服务的基类,IntentService
类根据需求处理异步请求。启动它的方法与普通服务相同;但是它会在一个工作线程中执行它的任务并且当任务完成时它会自动终止。
MyIntentService
public class MyIntentService extends IntentService { public MyIntentService() { // Intent Service的名称 super("MyIntentServiceName"); } @Override protected void onHandleIntent(Intent intent) { try { int result = downloadFile(new URL("http://www.amazon.com/somefile.pdf")); Log.d("IntentService", "Downloaded " + result + " bytes"); } catch (MalformedURLException e) { e.printStackTrace(); } } private int downloadFile(URL url) { try { //---模拟下载--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return 100; }}
onHandleIntent()方法就是放置需要在独立线程中执行的代码的位置,比如从服务器上下载文件。当代码结束执行后,线程会被终止而且服务也会自动停止。
在服务和Activity之间通信
MyIntentService
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentServiceName"); } @Override protected void onHandleIntent(Intent intent) { try { int result = downloadFile(new URL("http://www.amazon.com/somefile.pdf")); Log.d("IntentService", "Downloaded " + result + " bytes"); //---send a broadcast to inform the activity // that the file has been downloaded--- Intent broadcastIntent = new Intent(); broadcastIntent.setAction("FILE_DOWNLOADED_ACTION"); getBaseContext().sendBroadcast(broadcastIntent); } catch (MalformedURLException e) { e.printStackTrace(); } } private int downloadFile(URL url) { try { //---模拟下载--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return 100; }}
MainActivity
public class MainActivity extends AppCompatActivity { private BroadcastReceiver intentReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "File downloaded.", Toast.LENGTH_LONG).show(); } }; } public void onStartIntentService(View view) { startService(new Intent(getApplicationContext(), MyIntentService.class)); } public void onStopIntentService(View view) { stopService(new Intent(getApplicationContext(), MyIntentService.class)); } @Override public void onResume() { super.onResume(); //---intent to filter for file downloaded intent--- IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("FILE_DOWNLOADED_ACTION"); //---register the receiver--- registerReceiver(intentReceiver, intentFilter); } @Override public void onPause() { super.onPause(); //---unregister the receiver--- unregisterReceiver(intentReceiver); }}
单击打开Intent服务按钮,大约5秒钟后,Toast类就会弹出一条消息表示文件下载完成。
当服务的执行任务完成之后,想要通知Activity,可以使用sendBroadcast()方法广播一个Intent对象:
@Overrideprotected void onHandleIntent(Intent intent) { try { int result = downloadFile(new URL("http://www.amazon.com/somefile.pdf")); Log.d("IntentService", "Downloaded " + result + " bytes"); //---send a broadcast to inform the activity // that the file has been downloaded--- Intent broadcastIntent = new Intent(); broadcastIntent.setAction("FILE_DOWNLOADED_ACTION"); getBaseContext().sendBroadcast(broadcastIntent); } catch (MalformedURLException e) { e.printStackTrace(); }}
被广播的Intent对象的动作被设置为FILE_DOWNLOADED_ACTION
,这意味着所有监听该Intent的Activity将会被调用。也就是说,在MainActivity文件中,需要使用registerReceiver()
方法监听IntentFilter
类中的Intent
对象。
@Overridepublic void onResume() { super.onResume(); //---intent to filter for file downloaded intent--- IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("FILE_DOWNLOADED_ACTION"); //---register the receiver--- registerReceiver(intentReceiver, intentFilter);}
当接收到Intent
后,它会调用已经定义的BroadcastReceiver
类的实例:
private BroadcastReceiver intentReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "File downloaded.", Toast.LENGTH_LONG).show(); } }; }
将Activity与服务绑定
MainActivity
public class MainActivity extends AppCompatActivity { int notificationID = 1; MyService serviceBinder; Intent i; private BroadcastReceiver intentReceiver; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected( ComponentName className, IBinder service) { //—-called when the connection is made—- serviceBinder = ((MyService.MyBinder) service).getService(); try { //---assign the URLs to the service through the // serviceBinder object--- serviceBinder.urls = new URL[]{ new URL("http://www.amazon.com/somefiles.pdf"), new URL("http://www.wrox.com/somefiles.pdf"), new URL("http://www.google.com/somefiles.pdf"), new URL("http://www.learn2develop.net/somefiles.pdf")}; } catch (MalformedURLException e) { e.printStackTrace(); } startService(i); } @Override public void onServiceDisconnected(ComponentName className) { //---当service为null的时候回调--- serviceBinder = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "File downloaded.", Toast.LENGTH_LONG).show(); } }; } private void displayNotification() { Intent i = new Intent(this, NotificationView.class); i.putExtra("notificationID", notificationID); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, 0); NotificationManager nm = (NotificationManager) getSystemService (NOTIFICATION_SERVICE); NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this, this.getPackageName()) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("会议提醒") .setContentText("内容: 敌军将在五秒钟后到达!") .addAction(R.mipmap.ic_launcher, "Notzuonotdied", pendingIntent); assert nm != null; nm.notify(notificationID, notifBuilder.build()); } public void onClick(View view) { displayNotification(); } public void onContentProvider(View view) { startActivity(new Intent(this, Main2Activity.class)); } public void onMyCP(View view) { startActivity(new Intent(this, Main3Activity.class)); } public void onStartService(View view) {// startService(new Intent(getApplicationContext(), MyService.class)); i = new Intent(MainActivity.this, MyService.class); bindService(i, connection, Context.BIND_AUTO_CREATE); } public void onStopService(View view) { stopService(new Intent(getApplicationContext(), MyService.class)); } public void onStartIntentService(View view) { startService(new Intent(getApplicationContext(), MyIntentService.class)); } public void onStopIntentService(View view) { stopService(new Intent(getApplicationContext(), MyIntentService.class)); } @Override public void onResume() { super.onResume(); //---intent to filter for file downloaded intent--- IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("FILE_DOWNLOADED_ACTION"); //---register the receiver--- registerReceiver(intentReceiver, intentFilter); } @Override public void onPause() { super.onPause(); //---unregister the receiver--- unregisterReceiver(intentReceiver); }}
MyService
public class MyService extends Service { public URL[] urls; private final IBinder binder = new MyBinder(); public class MyBinder extends Binder { public MyService getService() { return MyService.this; } } @Override public IBinder onBind(Intent arg0) { return binder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // We want this service to continue running until it is explicitly // stopped, so return sticky. Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); new DoBackgroundTask().execute(urls); return START_STICKY; } private int downloadFile() { try { //---模拟下载延时--- Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return 100; } @SuppressLint("StaticFieldLeak") private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> { @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalBytesDownloaded = 0; for (int i = 0; i < count; i++) { totalBytesDownloaded += downloadFile(); //---calculate percentage downloaded and // report its progress--- publishProgress((int) (((i + 1) / (float) count) * 100)); } return totalBytesDownloaded; } @Override protected void onProgressUpdate(Integer... progress) { Log.d("Downloading files", String.valueOf(progress[0]) + "% downloaded"); Toast.makeText(getBaseContext(), String.valueOf(progress[0]) + "% downloaded", Toast.LENGTH_LONG).show(); } @Override protected void onPostExecute(Long result) { Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes", Toast.LENGTH_LONG).show(); stopSelf(); } }}
要将Activity与服务绑定,首先必须在服务中创建一个继承于Binder类的内部类:
public class MyBinder extends Binder { public MyService getService() { return MyService.this; }}
在这个内部类中实现getService()
方法,该方法返回一个服务的实例:
private final IBinder binder = new MyBinder();
同时修改onBind()
方法返回MyBind实例:
@Overridepublic IBinder onBind(Intent arg0) { return binder;}
在onStartCommand()
方法中,使用urls数组调用execute()
方法,urls
数组在服务中被声明为一个公共成员:
public class MyService extends Service { public URL[] urls; ...... @Override public int onStartCommand(Intent intent, int flags, int startId) { // We want this service to continue running until it is explicitly // stopped, so return sticky. Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); new DoBackgroundTask().execute(urls); return START_STICKY; } ......}
接着,这个URL
数组必须从Activity中直接赋值。
在MainActivity
文件中,首先声明一个服务的实例和一个Intent对象:
MyService serviceBinder;Intent i;
serviceBinder
对象将被用来作为服务的引用,并在Activity
中可以直接访问。
然后创建一个ServiceConnection
类的实例,从而可以监控服务的状态:
private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected( ComponentName className, IBinder service) { //—-当连接成功的时候回调—- serviceBinder = ((MyService.MyBinder) service).getService(); try { //---初始化URLs数组,并通过Intent传递给Service--- serviceBinder.urls = new URL[]{ new URL("http://www.amazon.com/somefiles.pdf"), new URL("http://www.wrox.com/somefiles.pdf"), new URL("http://www.google.com/somefiles.pdf"), new URL("http://www.learn2develop.net/somefiles.pdf")}; } catch (MalformedURLException e) { e.printStackTrace(); } startService(i); } @Override public void onServiceDisconnected(ComponentName className) { //---当service为null的时候回调--- serviceBinder = null; }};
需要实现两个方法:onServiceConnected
和onServiceDisconnected
。
随后使用startService(i);
启动服务。
在启动服务之前,必须将Activity
与该服务绑定。该内容在启动服务按钮的onClick事件函数onStartService
中:
public void onStartService(View view) { i = new Intent(MainActivity.this, MyService.class); bindService(i, connection, Context.BIND_AUTO_CREATE);}
bindService()
方法使Activity
连接到服务。
附录
《Beginning Android Programming with Android Studio, 4th Edition》