1 通过监控电量状态来进行电量管理
1.1 问题描述
提问:拍照和图片的处理,他们可以做一些电量的优化吗?
解答:假如现在没有充电,电量比较低,拍照动作是需要立马执行的,但是图片处理(需要消耗大量的计算—电量的大量消耗)是否可以放在用户手机插上电源之后来处理?
解决:如何立即获取手机当前充电状态,我们可以有针对性地对一些代码做优化。比如:判断只有当前手机为AC充电状态时 才去执行一些非常耗电的操作。
1.2 例子演示
/**
* 获取电池的充电状态
*/
private boolean checkForPower(){
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = this.registerReceiver(null,filter);
//BatteryManager
int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
boolean usb = chargePlug ==BatteryManager.BATTERY_PLUGGED_USB;//usb充电
boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;//交流电
//无线充电---API>=17
boolean wireless = false;
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1) {
wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
return (usb||ac||wireless);
}
/**
* 判断只有当前手机为AC充电状态时 才去执行一些非常耗电的操作。
*/
private void applyFilter() {
//是否在充电
if(!checkForPower()){
mPowerMsg.setText("请充上电,再处理!");
return;
}
mCheyennePic.setImageResource(R.drawable.pink_cheyenne);
mPowerMsg.setText(R.string.photo_filter);
}
2 wake_lock唤醒锁机制——保护关键逻辑的执行过程
2.1 使用唤醒锁原因&如何节省电量
Android 开发人员能够(并有权限)阻止 Android 设备进入睡眠模式。 他们可能希望让 CPU 处于活动状态 — 即使显示器关闭,来执行后台服务。 或者可能他们希望在执行某项活动时阻止显示器自动关闭。 出于此原因,Google 在其 PowerManager API 中增加了唤醒锁。 阻止设备进入睡眠模式的应用可以使用唤醒锁。 只要系统上有活动的唤醒锁,设备便无法进入挂起模式,除非释放唤醒锁。 使用唤醒锁时,一定要了解到当您不需要唤醒锁时,必须使用wakelock.acquice()方法将其正确释放,因为未释放的唤醒锁无法进入默认状态以节能,从而很快便会将设备的电池耗尽。
但是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?什么时候进行重试等等?解决上面的问题,正确的方式可能是使用非精准定时器。通常情况下,我们会设定一个时间进行某个操作,但是动态修改这个时间也许会更好。例如下图所示,如果有另外一个程序需要比你设定的时间晚5分钟唤醒,最好能够等到那个时候,两个任务捆绑一起同时进行,这就是非精确定时器的核心工作原理。我们可以定制计划的任务,可是系统如果检测到一个更好的时间,它可以推迟你的任务,以节省电量消耗。
这正是JobScheduler API所做的事情,在第5章详细描述——对任务调度的优化–Job Scheduler集中处理任务。它会根据当前的情况与任务,组合出理想的唤醒时间,例如等到正在充电或者连接到WiFi的时候,或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。
(有的人可能认为我以前写的后台服务就没掉过链子而且运行得挺好的,1.可能是你的任务时间比较短;2.可能CPU被手机里面很多其他的软件一直在唤醒状态。)。
2.2 合理场景
(1)必要性:真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。
(2)使用场景是:在使用后台服务在屏幕关闭情况下hold住CPU完成关键逻辑的执行。(比如在Acitivity中就没必要用了。)
注意:该 API 突出强调了唤醒锁会显著缩短 Android 设备的电池续航时间,因此如果可以避免应尽量减少使用它们。 如果使用,也应尽快将其释放。
2.3 默认唤醒锁使用&唤醒锁分类
(1)除了,断线重连重新登陆这些关键逻辑的执行过程,还有以下使用:
(2)唤醒锁4种分类
请注意,自 API 等级 17 开始,FULL_WAKE_LOCK 将被弃用。 应用应使用 FLAG_KEEP_SCREEN_ON。
2.4 wake_lock例子演示
(1)就是添加唤醒锁权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
(2)直接使用唤醒锁:
注意:在使用该类的时候,必须保证acquire和release是成对出现的。
public void onCreate(Bundle savedInstanceState) {
...
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
...
mWakeLock = mPowerManager.newWakeLock(mWakeLockState,
"UMSE PowerTest");
if (mWakeLock != null) {
mWakeLock.acquire();
...
}
}
protected void onDestroy() {
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
...
}
2.5 推荐的方式:使用WakefulBroadcastReceiver
2.5.1 概述
推荐的方式是使用WakefulBroadcastReceiver使用广播和Service(典型的IntentService)结合的方式可以让你很好地管理后台服务的生命周期。
2.5.2 详细介绍
WakefulBroadcastReceiver是BroadcastReceiver的一种特例。它会为你的APP创建和管理一个PARTIAL_WAKE_LOCK 类型的WakeLock。WakefulBroadcastReceiver把工作交接给service(通常是IntentService),并保证交接过程中设备不会进入休眠状态。如果不持有WakeLock,设备很容易在任务未执行完前休眠。最终结果是你的应用不知道会在什么时候能把工作完成,相信这不是你想要的。
2.5.3 使用WakefulBroadcastReceiver步骤
(1)在Manifest中注册:
<receiver android:name=".MyWakefulReceiver"></receiver>
(2)使用startWakefulService()方法来启动服务,与startService()相比,在启动服务的同时,并启用了唤醒锁。代码如下:
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Start the service, keeping the device awake while the service is
// launching. This is the Intent to deliver to the service.
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service); //启动服务时保持唤醒锁
}
}
(3)当后台服务的任务完成,要调MyWakefulReceiver.completeWakefulIntent()来释放唤醒锁。
public class MyIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder builder;
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
// Do the work that requires your app to keep the CPU running.
// ...
// Release the wake lock provided by the WakefulBroadcastReceiver.
MyWakefulReceiver.completeWakefulIntent(intent); //释放唤醒锁
}
}
2.6 保持屏幕常亮方法
如果开发人员希望阻止某个应用的显示器在特定使用情况下变暗,可采用 Google 另外提供的一种方法,这种方法无需特别许可,不需要唤醒锁,不用担心无用资源的释放问题,并且能正确管理不同app之间的切换。
(1)最好的方式是在Activity中使用FLAG_KEEP_SCREEN_ON 的Flag:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
(2)另一个方式是在布局文件中使用android:keepScreenOn属性:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</RelativeLayout>
android:keepScreenOn = “true ”的作用和FLAG_KEEP_SCREEN_ON一样。使用代码的好处是你允许你在需要的地方关闭屏幕。
(3)注意:
一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag, windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag, 使用:
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2.7 其他参考资料:
(1)alarmManager在手机休眠时无法唤醒Service的问题?( 为了对付你们这些个“”流氓“”的频繁唤醒的app,各个厂家都开发了心跳对齐。)
https://www.zhihu.com/question/36421849
(2)微信 Android 版 6.2 为什么设置了大量长时间的随机唤醒锁?
https://www.zhihu.com/question/31136645
3 AlarmManager——休眠的情况下唤醒来执行任务
3.1 为什么使用AlarmManager
Android手机有两个处理器,一个叫Application Processor(AP),另一个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。
Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,使得应用程序有权限通过代码阻止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。比如前段时间的某应用,比如现在仍然干着这事的某应用。
那么Wake Lock API有啥用呢?比如心跳包从请求到应答,比如断线重连重新登陆这些关键逻辑的执行过程,就需要Wake Lock来保护。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。
AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。(极光推送就是利用这个来做的。心跳包的机制,其实就是传统的长连接:将数据有服务器端推送到客户端;消息推送也是一种长连接。)
对比:
1、关键逻辑的执行过程,就需要Wake Lock来保护。如断线重连重新登陆。
2、休眠的情况下如何唤醒来执行任务?用AlarmManager。如推送消息的获取。
注意:如果请求网络很差,会要很长的时间,一般谷歌建议一定要设置请求超时时间。
3.2 网上采集的一些问题坑点及解决
1.向服务器轮询的代码不执行——AlarmManager
曾经做一个应用,利用Timer和TimerTask,来设置对服务器进行定时的轮询,但是发现机器在某段时间后,轮询就不再进行了。查了很久才发 现是休眠造成的。后来解决的办法是,利用系统的AlarmService来执行轮询。因为虽然系统让机器休眠,节省电量,但并不是完全的关机,系统有一部 分优先级很高的程序还是在执行的,比如闹钟,利用AlarmService可以定时启动自己的程序,让cpu启动,执行完毕再休眠。
2.后台长连接断开——WAKE_LOCK
最近遇到的问题。利用Socket长连接实现QQ类似的聊天功能,发现在屏幕熄灭一段时间后,Socket就被断开。屏幕开启的时候需进行重连,但 每次看Log的时候又发现网络是链接的,后来才发现是cpu休眠导致链接被断开,当你插上数据线看log的时候,网络cpu恢复,一看网络确实是链接的, 坑。最后使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。
3.调试时是不会休眠的——休眠机制
让我非常郁闷的是,在调试2的时候,就发现,有时Socket会断开,有时不会断开,后来才搞明白,因为我有时是插着数据线进行调试,有时拔掉数据线,这 时Android的休眠状态是不一样的。而且不同的机器也有不同的表现,比如有的机器,插着数据线就会充电,有的不会,有的机器的设置的充电时屏幕不变暗 等等,把自己都搞晕了。其实搞明白这个休眠机制,一切都好说了。
3.3 AlarmManager代码解析
/**
* 注意:在19以上版本,setRepeating中设置的频繁只是建议值(6.0 的源码中最小值是60s),如果要精确一些的用setWindow或者setExact。
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
null, null, null, null, null);
}
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
null, null);
}
private long legacyExactLength() {
return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);//WINDOW_HEURISTIC:探试的,探索的;(意思:有范围浮动的,时间不确定的)
}
3.4 例子演示–采用定时重复的Service开启
(1)代码
/**
* 利用Android自带的定时器AlarmManager实现
*/
Intent intent = new Intent(mContext, ServiceTest.class);
PendingIntent pi = PendingIntent.getService(mContext, 1, intent, 0);
AlarmManager alarm = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
if(alarm != null) {
alarm.cancel(pi);
// 闹钟在系统睡眠状态下会唤醒系统并执行提示功能
alarm.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi);// 确切的时间闹钟
//alarm.setExact(…);
//alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pi);
}
(2)该定时器可以启动Service服务、发送广播、跳转Activity,并且会在系统睡眠状态下唤醒系统。所以该方法不用获取电源锁和释放电源锁。
4 在WiFi连接下进行网络传输
4.1 移动蜂窝网络(Mobile Radio)电量消耗情况
(1)数据中的移动蜂窝网络(Mobile Radio)电量消耗呈现下面的情况,间隔很小,又频繁断断续续的出现,说明电量消耗性能很不好:
(2)经过优化之后,如果呈现下面的图示,说明电量消耗的性能是良好的:
4.2 WiFi连接电量消耗情况
另外WiFi连接下,网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据。表现如下图:
4.3 总结
那么如何才能够把任务缓存起来,做到批量化执行呢?详细请看第5章——“对任务调度的优化–Job Scheduler集中处理任务”。
5 对任务调度的优化–Job Scheduler集中处理任务
5.1 JobSchedule
5.1.1 作用
JobSchedule的宗旨就是把一些不是特别紧急的任务集中选择合适的时间、合适的网络批量处理。这样做有两个好处:
(1)避免频繁的唤醒硬件模块,造成不必要的电量消耗;
(2)避免在不合适的时间(例如:低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
5.1.1 使用场景
如果APP有符合如下情况的任务, 可以尝试使用JobScheduler机制执行任务:
(1)可以推迟的非面向用户的任务(如定期数据库数据更新);
(2)当充电时才希望执行的工作(如备份数据);
(3)需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据);
(4)希望作为一个批次定期运行的许多任务。
5.2 电源报告分析
5.2.1 针对”避免频繁的唤醒硬件模块,造成不必要的电量消耗”
(1)问题
我们通过一个在蜂窝网络下的网络请求例子来说明。Android系统为了尽可能的增加设备的续航,会不断的关闭各种硬件模块来节省电量。当我们的App在设备处于休眠状态下想要执行一次网络请求的时候;首先需要唤醒设备,接着会发送数据请求,然后等待服务端返回的结果,最后再经过一段时间的等待才会慢慢进入休眠状态。整个过程如下图(Google提供):
针对上面这个流程,Google也展示过这样一张图:
通过上面这张电量消耗图我们看到,在唤醒设备、发送数据以及接受数据的瞬间都会造成大量的电量消耗。同时系统为了你的下一次网络请求不用再次唤醒设备,会等待一段时间再让设备进入休眠。如果你的请求是间歇性的,那么这些等待休眠的时间内造成的电量消耗其实也是多余的。
(2)优化方案
针对上述问题,一个很好的优化方案就是将这些间歇性的网络请求任务推迟到某个时间点(根据设备当前状况选择合适的时机)来集中处理。JobSchedule正是用来帮我们做这件事的,具体方案如下图(Google提供):
通过上图我们可以看到,间歇性的网络请求被集中处理了;避免了重复的唤醒设备,同时也减少了设备等待休眠的次数,以此达到省电的目的。
5.2.2 针对”避免在不合适的时间执行过多的任务消耗电量”
在第4章的基础上,我们可以通过JobSchedule来优化请求时机。①比如说用户的设备剩余电量已经不多了,那么对于一些及时性要求不高的任务我们就可以放到电量充足或者是设备充电阶段再执行;②又比如说现在用户设备处于蜂窝网络状况或者若网络环境下,那么我们就可以将这些任务放到WiFi网络或者网络情况良好的时机再处理。
5.3 JobScheduler api
//优化
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
for (int i = 0; i < 500; i++) {
JobInfo jobInfo = new JobInfo.Builder(i,serviceComponent)
.setMinimumLatency(5000)//5秒 最小延时、
.setOverrideDeadline(60000)//maximum最多执行时间
// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免费的网络---wifi 蓝牙 USB
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意网络---
/**
设置重试/退避策略,当一个任务调度失败的时候执行什么样的测量采取重试。
initialBackoffMillis:第一次尝试重试的等待时间间隔ms
*backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。
*/
// .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
.setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
// .setPeriodic (long intervalMillis)//设置执行周期,每隔一段时间间隔任务最多可以执行一次。
// .setPeriodic(long intervalMillis,long flexMillis)//在周期执行的末端有一个flexMiliis长度的窗口期,任务就可以在这个窗口期执行。
//设置设备重启后,这个任务是否还要保留。需要权限:RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x
// .setPersisted(boolean isPersisted);
// .setRequiresCharging(boolean )//是否需要充电
// .setRequiresDeviceIdle(boolean)//是否需要等设备出于空闲状态的时候
// .addTriggerContentUri(uri)//监听uri对应的数据发生改变,就会触发任务的执行。
// .setTriggerContentMaxDelay(long duration)//设置Content发生变化一直到任务被执行中间的最大延迟时间
//设置Content发生变化一直到任务被执行中间的延迟。如果在这个延迟时间内content发生了改变,延迟时间会重写计算。
// .setTriggerContentUpdateDelay(long durationMilimms)
// .BUILD();
jobScheduler.schedule(jobInfo);
}
5.4 例子演示
(1)manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.jobscheduler">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".TestJobService" android:permission="android.permission.BIND_JOB_SERVICE"></service>
</application>
</manifest>
(2)MainActivity.java
public class MainActivity extends AppCompatActivity {
private ComponentName serviceComponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_main);
//............
// Start service and provide it a way to communicate with us.
Intent startServiceIntent = new Intent(this, TestJobService.class);
startServiceIntent.putExtra("messenger", new Messenger(mHandler));
startService(startServiceIntent);
}
//............
/**
* 关键代码
*
* UI onclick listener to schedule a job. What this job is is defined in
* TestJobService#scheduleJob().
*/
@SuppressLint("NewApi")
public void scheduleJob(View v) {
if (!ensureTestService()) {
return;
}
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);
String delay = mDelayEditText.getText().toString();
if (delay != null && !TextUtils.isEmpty(delay)) {
builder.setMinimumLatency(Long.valueOf(delay) * 1000);//5秒 最小延时
}
String deadline = mDeadlineEditText.getText().toString();
if (deadline != null && !TextUtils.isEmpty(deadline)) {
builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);//定时:
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
if (requiresUnmetered) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//WIFI
} else if (requiresAnyConnectivity) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);//所有
}
builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());//设置为空闲状态触发
builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());// 手机是否处于充电状态
mTestService.scheduleJob(builder.build());//开启,Send job to the JobScheduler
}
}
(3)TestJobService.java
package com.example.jobscheduler;
public class TestJobService extends JobService {
private static final String TAG = "SyncService";
private MainActivity mActivity;
private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>();
@Override
public void onCreate() {
super.onCreate();
}
public void setUiCallback(MainActivity activity) {
mActivity = activity;
}
/**
* 关键代码-开启JobScheduler
*/
public void scheduleJob(JobInfo t) {
Log.d(TAG, "Scheduling job");
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.schedule(t);//jobscheduleService
}
/**
* Not currently used, but as an exercise you can hook this
* up to a button in the UI to finish a job that has landed
* in onStartJob().
*/
public boolean callJobFinished() {
JobParameters params = jobParamsMap.poll();
if (params == null) {
return false;
} else {
jobFinished(params, false);
return true;
}
}
/**
* 关键代码-执行耗时操作
*/
@Override
public boolean onStartJob(JobParameters params) {
// We don't do any real 'work' in this sample app. All we'll
// do is track which jobs have landed on our service, and
// update the UI accordingly.
jobParamsMap.add(params);
if (mActivity != null) {
mActivity.onReceivedStartJob(params);
//执行耗时操作:下载文件
}
Log.i(TAG, "on start job: " + params.getJobId());
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
// Stop tracking these job parameters, as we've 'finished' executing.
jobParamsMap.remove(params);
if (mActivity != null) {
mActivity.onReceivedStopJob();
}
Log.i(TAG, "on stop job: " + params.getJobId());
return true;
}
/**
* When the app's MainActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCalback()"
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Messenger callback = intent.getParcelableExtra("messenger");
Message m = Message.obtain();
m.what = MainActivity.MSG_SERVICE_OBJ;
m.obj = this;
try {
callback.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Error passing service object back to activity.");
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}