Android开发 - 在后台操作

关键字:Service,AsyncTask,Intent Service,Alarm

 

 

Service的启动、停止和控制是通过其他应用程序组件来实现的,包括Activity、Broadcast Receiver和其他Service。

Android系统提前终止一个Service的唯一可能的情况就是为前台组件提供额外的资源。如果发生了这种情况,当有资源可用的时候,可以通过配置来自动重启Service。

如果Service需要和用户直接进行互动(例如播放音乐),那么有必要把这个Service标识为前台组件,从而提高它的优先级。这样可以保证除了极端情况外,该Service都不会被终止,但是,这会降低运行时管理资源的能力,从而可能会降低整体的用户体验。

 

创建和控制Service

创建Service

public class MyService extends Service {

  @Override
  public void onCreate() {
    // TODO: Actions to perform when service is created.
  }

  @Override
  public IBinder onBind(Intent intent) {
    // TODO: Replace with service binding implementation.
    return null;
  }
}

当创建一个新的Service后,必须将这个Service在应用程序清单中组测,需要在application节点内增加一个service标记。

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

为了确保你的Service只能由自己的应用程序启动和停止,需要在它的Service节点下添加一个premission属性。

<service 
    android:enabled="true" 
    android:name=".MyService"
    android:permission="com.example.MY_SERVICE_PERMISSION"/> 
/>

执行一个Service并控制它的重新启动行为

重写OnStartCommand事件处理程序以执行一个由Service封装的任务(或者启动持续进行的操作)。在这个处理程序中,也可以制定Service的重新启动行为。

当一个Service通过startService启动时,就会调用onStartCommand方法,所以这个方法可能在Service的生命周期内被执行很多次。因此,应该确保Service能够满足这种需求。

public class MyService extends Service {

  @Override
  public void onCreate() {
    // TODO: Actions to perform when service is created.
  }

  @Override
  public IBinder onBind(Intent intent) {
    // TODO: Replace with service binding implementation.
    return null;
  }
  
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    startBackgroundTask(intent, startId);
    return Service.START_STICKY;
  }
}

START_STICKY:描述了标准的重新启动行为。如果返回这个值,那么在运行时终止Service之后,当重新启动Service时,将会调用onStartCommand。注意,当重新启动Service时,传入onStartCommand的Intent参数将是null。

START_NOT_STICKY:这种模式用于启动以特殊处理的操作和命令的Service。通常当命令完成后,这些Service会调用stopSelf来终止自己。

当被运行时终止后,只有当存在未处理的启动调用时,设为这个模式的Service次啊会重新启动。如果在终止Service后没有进行startService调用,那么Service将停止运行,而不会调用onStartCommand。

START_REDELIVER_INTENT:这种模式是前两种模式的组合;如果Service被运行时终止,那么只有当存在未处理的启动调用或者进程在调用stopSelf之前被终止时,才会重新启动Service。在后一种情况中,将会调用onStartCommand,并传入没有正常处理完成的Intent。

注意:在处理完成后,每种模式都要求使用stopSevice和stopSelf显式地终止Service。

在onStartCommand的返回值中制定的重新启动模式将会影响到后面的调用中传入的参数值。起初,会传入一个Intent参数给startService来启动Service。当系统重新启动Service后,Intent在START_STICKY模式中将为null,而在START_REDELIVER_INTENT模式中将为原来的Intent。

启动和停止Service

要启动一个Service,需要调用startService。和Activity一样,既可以在注册合适的Intent Receiver后使用操作来隐式地启动Service,也可以显示的制定Service的类名以启动一个Service。

如果应用程序不具有此Service所要求的权限,那么调用startService将会抛出SecurityException异常。

  private void explicitStart() {
    // Explicitly start My Service
    Intent intent = new Intent(this, MyService.class);
    // TODO Add extras if required.
    startService(intent);
  }

  private void implicitStart() {
    // Implicitly start a music Service
    Intent intent = new Intent(MyMusicService.PLAY_ALBUM);
    intent.putExtra(MyMusicService.ALBUM_NAME_EXTRA, "United"); 
    intent.putExtra(MyMusicService.ARTIST_NAME_EXTRA, "Pheonix"); 
    startService(intent);    
  } 

要停止一个Service,需要使用stopService,并指定用于定义要停止的Service的Intent

  private void stopServices() {

    // Stop a service explicitly.
    stopService(new Intent(this, MyService.class));
        
    // Stop a service implicitly.
    Intent intent = new Intent(MyMusicService.PLAY_ALBUM);
    stopService(intent); 
  }

无论startService被调用了多少次,对stopService的一次调用就会终止它所匹配的运行中的Service。

自终止Service

由于Service具有高优先级,它们通常不会被运行时终止,因此自终止可以显著地改善应用程序中的资源占用情况。

通过在处理完成后显式地停止Service,可以避免系统仍然为使该Service继续运行而保留资源。

 

将Service绑定到Activity

Service可以和Activity绑定,后者会维持一个对前者实例的引用,此引用允许你像对待其他实例化的类那样,对正在进行的Service进行方法调用。

允许Service和Activity绑定,这样能够获得更加详细的接口。要让一个Service支持绑定,需要实现onBind方法,并返回被绑定的Service的当前实例。

public class MyMusicService extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    return binder;
  }

  public class MyBinder extends Binder {
    MyMusicService getService() {
      return MyMusicService.this;
    }
  }
  private final IBinder binder = new MyBinder();
}

Service和其他组件之间的链接表示为一个ServiceConnection。

要想将一个Service和其他组件进行绑定,需要实现一个新的ServiceConnection,建立了一个连接之后,就可以通过重写onServiceConnected和onServiceDisconnected方法来获得对Service实例的引用。

public class MyActivity extends Activity {
  //Reference to the service
  private MyMusicService serviceRef;
  
  //Handles the connection between the service and activity
  private ServiceConnection mConnection = new ServiceConnection() {
   public void onServiceConnected(ComponentName className, 
                                  IBinder service) {
     // Called when the connection is made.
     serviceRef = ((MyMusicService.MyBinder)service).getService();
   }
  
   public void onServiceDisconnected(ComponentName className) {
     // Received when the service unexpectedly disconnects.
     serviceRef = null;
   }
  };
}

要执行绑定,需要在Activity中调用bindService,并传递给它一个用于选择要绑定的Service的Intent以及一个ServiceConnection实现的实例。

    //Bind to the service
    Intent bindIntent = new Intent(MyActivity.this, MyMusicService.class);
    bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);

 

创建前台Service

Android采用了动态的方法管理资源,所以应用程序组件有可能在很少或者根本没有警告的情况下就被终止了。

当确定哪个应用程序或者应用程序组件可以被终止时,Android给正在运行的Service赋予了第二高的优先级,只有处于激活状态,前台运行的Activity才可以拥有更高的优先级。

可以使用startForeground方法,设置Service在前台运行,提升优先级与前台Activity一样高。必须同时指定一个持续工作的Notification。

  private void startPlayback(String album, String artist) {
    int NOTIFICATION_ID = 1;

    // Create an Intent that will open the main Activity
    // if the notification is clicked.
    Intent intent = new Intent(this, MyActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 1, intent, 0);

    // Set the Notification UI parameters
    Notification notification = new Notification(R.drawable.icon,
      "Starting Playback", System.currentTimeMillis());
    notification.setLatestEventInfo(this, album, artist, pi);

    // Set the Notification as ongoing
    notification.flags = notification.flags |
                         Notification.FLAG_ONGOING_EVENT;

    // Move the Service to the Foreground
    startForeground(NOTIFICATION_ID, notification);
  }

将Service移至后台

  public void pausePlayback() {
    // Move to the background and remove the Notification
    stopForeground(true);
  }

 

使用后台线程

在Android中,对未响应的定义是:Activity对一个输入事件在5秒的时间内没有响应,或者Broadcast Receiver在10秒内没有完成它的onReceive处理程序。

Android为将数据处理移到后台提供了很多的可选技术。可以实现自己的线程并使用Handler类来于GUI线程同步,然后更新UI。另外,AsyncTask类允许定义将在后台执行的操作,并且提供了可以用来监控进度以及在GUI线程上发布结果的方法。

 

使用AsyncTask运行异步任务

当Activity重新启动时,AsyncTask的操作将不会持续进行,也就是说,AsyncTask在设备的方向变化而导致Activity被销毁和重新创建时会被取消。对于生命周期较长的后台操作,如从Internet下载数据,使用Service组件是更好的选择。类似的,Cursor Loader则是使用Content Provider或者数据库结果的最佳选择。

创建新的异步任务,派生自AsyncTask类

public class MyActivity extends Activity {

  TextView asyncTextView;
  ProgressBar asyncProgress;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    asyncTextView = (TextView)findViewById(R.id.asyncTextView);
    asyncProgress = (ProgressBar)findViewById(R.id.asyncProgress);
  }
  
    private class MyAsyncTask extends AsyncTask<String, Integer, String> {
    @Override
    protected String doInBackground(String... parameter) {
      // Moved to a background thread.
      String result = "";
      int myProgress = 0;
      
      int inputLength = parameter[0].length();

      // Perform background processing task, update myProgress]
      for (int i = 1; i <= inputLength; i++) {
        myProgress = i;
        result = result + parameter[0].charAt(inputLength-i);
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) { }
        publishProgress(myProgress);
      }

      // Return the value to be passed to onPostExecute
      return result;
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
      // Synchronized to UI thread.
      // Update progress bar, Notification, or other UI elements
      asyncProgress.setProgress(progress[0]);
    }

    @Override
    protected void onPostExecute(String result) {
      // Synchronized to UI thread.
      // Report results via UI update, Dialog, or notifications
      asyncTextView.setText(result);
    }
  }
}

doInBackground 在后台线程执行内容。

onProgressUpdate 进度更新发生变化时。

onPostExecute 当doInBackground完成后,该方法的返回值将会传入到这个时间的处理程序中。在执行时,这个程序将于GUI线程同步,所以可以安全的修改UI元素。

运行异步任务

  private void executeAsync() {
    String input = "redrum ... redrum";
    new MyAsyncTask().execute(input); 
  }

每一个AsyncTask实例只能执行一次。如果试图第二次调用execute,则会抛出一个异常。

 

Intent Service

Intent Service为根据需求执行一组任务的后台Service实现了最佳实践模式,如Internet的循环更新或者数据处理。

应用程序组件想要通过Intent Service完成一个任务,就需要启动Service并传递给它一个包含完成该任务所需的参数的Intent。Intent Service会将收到的所有请求Intent放到队列中,并在一部后台线程中逐个地处理它们。当处理完所有收到的Intent后,Intent Service就会终止它自己。

Intent Service处理所有的复杂工作,如将多个请求放入队列、后台线程的创建、UI线程的同步。

要想把一个Service实现为Intent Service,需要扩展IntentService并重写onHandleIntent方法。

public class MyIntentService extends IntentService {

  public MyIntentService(String name) {
    super(name);
    // TODO 完成任何需要的构造函数任务
  }

  @Override
  public void onCreate() {
    // TODO: 创建Service时要执行的操作
  }
  
  @Override
  protected void onHandleIntent(Intent intent) {
    // 这个处理程序发生在一个后台线程中。
    // 耗时任务应该再次实现。
    // 传入这个IntentService的每个Intent将被逐个处理,当所有传入的Intent被处理完成后,该Service会终止它自己。
  }
}

使用重复Alarm调度网络刷新示例

 

Loader

 

手动创建线程和GUI线程同步

  //这个方法在主GUI线程上调用
  private void backgroundExecution() {
   // 将耗时的操作移到子线程
   Thread thread = new Thread(null, doBackgroundThreadProcessing,
                              "Background");
   thread.start();
  }
  
  //执行后台处理方法的Runnable
  private Runnable doBackgroundThreadProcessing = new Runnable() {
   public void run() {
     backgroundThreadProcessing();
   }
  };
  
  //在后台执行一些处理的方法
  private void backgroundThreadProcessing() {
   // [ ... 耗时操作 ... ]
  }

可以使用runOnUiThread方法,强制一个方法在与Activity UI相同的线程中执行

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 更新一个View或者其他Activity UI元素
    }
});

也可以使用Handler类的Post方法将更新从后台线程发布到用户界面上。

public class MyActivity extends Activity {

  private void backgroundExecution() {
    // 将耗时的操作移到子线程中。
    Thread thread = new Thread(null, doBackgroundThreadProcessing,
                               "Background");
    thread.start();
  }
  
  // 执行后台处理方法的Runnable
  private Runnable doBackgroundThreadProcessing = new Runnable() {
    public void run() {
      backgroundThreadProcessing();
    }
  };
  
  // 在后台执行一些处理的方法
  private void backgroundThreadProcessing() {
    // [ ... 耗时操作 ... ]
    
    // 在主UI线程上使用Handler发布doUpdateGUI Runnable
    handler.post(doUpdateGUI);
  }
  
  //在主线程上初始化一个Handler
  private Handler handler = new Handler();
// 执行updateGui方法的Runnable private Runnable doUpdateGUI = new Runnable() { public void run() { updateGUI(); } }; // 这个方法必须由UI线程调用 private void updateGUI() { // [ ... 打开一个对话框或者修改GUI元素 ... ] } }

Handler类也允许使用postDelayed方法延迟发布更新,或者使用postAtTime方法在指定时间执行发布。

//一秒后将一个方法发布到UI线程
handler.postDelayed(doUpdateGUI, 1000);

//设备运行5分钟后,将这个方法发布到UI线程
int upTime = 1000 * 60 * 5;
handler.postAtTime(doUpdateGUI, SystemClock.uptimeMillis()+upTimes);

 

使用Alarm

Alarm是一种在预定时间或者时间间隔内激活Intent的方式。和Timer不同,Alarm是在应用程序之外操作的,所以及时应用程序关闭,它们仍然能够用来激活应用程序事件或操作。

Alarm是降低应用程序资源需求的一种极为有效的方式,它允许停止Service和清除定时器,同时仍然可移植性调度的操作。可以使用Alarm实现基于网络查找的定时更新,或者把费时的或者成本受约束的操作安排在“非高峰”时期运行,又或者对失败的操作调度重试。

对于那些只在应用程序的生命周期内发生的定时操作,将Handler类和Timer以及Thread组合起来使用是一种比使用Alarm更好的方法,因为这样做允许Android更好的控制系统资源。通过把调度事件移出应用程序的控制范围,Alarm提供了一种缩短应用程序生命周期的机制。

Android中的Alarm在设备处于休眠状态时依然保持活动状态,可以有选择地设置Alarm来唤醒设备;然而,无论何时重启设备,所有Alarm都将会被取消。

 

创建、设置和取消Alarm

    // 创建一个唤醒Alarm,它在10秒钟后触发
    private void setAlarm() {
    // 获取一个Alarm Manager的引用
    AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
  
    // 如果设备处于休眠状态,设置Alarm来唤醒设备
    int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
  
    // 10秒钟后触发
    long timeOrLengthofWait = 10000;
  
    // 创建能够广播和操作的Pending Intent
    String ALARM_ACTION = "ALARM_ACTION";
    Intent intentToFire = new Intent(ALARM_ACTION);
    PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0, intentToFire, 0);
  
    // 设置Alarm
    alarmManager.set(alarmType, timeOrLengthofWait, alarmIntent);
  }

Alarm的类型:

RTC_WAKEUP 在指定时间唤醒设备,并激活Pending Intent。

RTC 在指定时间激活Pending Intent,但是不会唤醒设备。

ELAPSED_REALTIME 根据设备启动之后经过的时间激活Pending Intent,但是不会唤醒设备。

ELAPSED_REALTIME_WAKEUP 在设备启动并经过指定时间之后唤醒设备和激活Pending Intent。

当触发Alarm时,就会广播指定的Pending Intent。因此,使用相同的Pending Intent设置第二个Alarm会替代已经存在的Alarm。

要取消一个Alarm,需要调用Alarm Manager的cancel方法,并传递给它不想再触发的Pending Intent。

    PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0,
      intentToFire, 0);
  
    // Set the alarm
    alarmManager.set(alarmType, timeOrLengthofWait, alarmIntent);
    
    alarmManager.cancel(alarmIntent);

设置重复Alarm

因为Alarm是在应用程序生命周期之外设置的,所以它们十分适合于调度定时更新或者数据查找,从而避免了在后台持续运行Service。

要向设置重复Alarm,可以使用AlarmManager的setRepeating或者setInexactRepeating方法。

当需要对重复Alarm的精确时间间隔进行细粒度控制时,可以使用setRepeating方法。传入这个方法的时间间隔值可以用于指定Alarm的确切时间间隔,最多可以精确到毫秒。

当按照计划定时唤醒设备来执行更新时会消耗电池的电量,setInexactRepeating方法能够帮助减少这种电量消耗。在运行时,Android会同步多个没有精确指定时间间隔的重复Alarm,并同时触发它们。

使用没有精确指定时间间隔的重复Alarm,可以避免每个应用程序在类似但不重叠的时间段内独立唤醒设备。通过同步这些Alarm,系统可以限制定期重复事件对电池电量的影响。

  private void setInexactRepeatingAlarm() {

    AlarmManager alarmManager = 
    (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    

    int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
    
    //调度Alarm以每半个小时重复一次
    long timeOrLengthofWait = AlarmManager.INTERVAL_HALF_HOUR;

    //创建能够广播和操作的Pending Intent
    String ALARM_ACTION = "ALARM_ACTION";
    Intent intentToFire = new Intent(ALARM_ACTION);
    PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0,
     intentToFire, 0);
    
    //每半小时唤醒设备以激活一个Alarm
    alarmManager.setInexactRepeating(alarmType,
                              timeOrLengthofWait, 
                              timeOrLengthofWait,
                              alarmIntent);
  }

 

使用重复Alarm调度网络刷新示例:

 

posted @ 2016-10-08 19:44  guqiangjs  阅读(1860)  评论(0)    收藏  举报