承接JobIntentService深入研读1上一篇文章,那么,我们就有了一
个新的需求。查看代码的描述。因此,设计这个类来满足上述需求。
实现原理十分简单,在onStartCommand,保存下来;子类必须通过stopWrap来调用;最后父类决策最后一个startId才执行stopSelf即可。与JobIntentService相反的设计。
虽然简约,但不简单的地方,在于stopSelf和onStartCommand,这个动作,是被Framework的调度来保证了同步性;因此,在我们任意时刻调用stopSelf,都是没有关系的。这与JobIntentService是一样的。
stopSelf,和onStartCommand的时序问题。
因为,即便前面分析的,最后一次stopSelf,万一跟onStartCommand来的冲突呢?
答案是不会相互竞争的。
startService,最后会走到ActiveServices.java里面startServiceLocked是被synchronized(this) 锁在ActivityManagerService.java中的,进而最后调用到了我们的onStartCommand;
而stopSelf,是从我们出发stopServiceToken,也是被synchronized(this) 锁在ActivityManagerService.java中的。
尤其适合应用就是给系统应用做(原厂、有system app、加入了修改framework白名单的)等应用:
github传送门
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
/**
* 与JobIntentService的区别:
* 搞清楚JobIntentService的含义,他代表着顺序的,一个一个排队执行;而且是可以进行耗时操作的。
* 因此一个耗时操作会等待着另外一个操作的完成。
* 因此JobIntentService系列的逻辑是onStartCommand中接收到了workIntent,
* 按照startId按顺序来,一个一个"慢慢"执行;
* 直到最后一个活儿干完了,stopSelf(startId)就是最后一个id。
*
* 但是现在我们的需求是,来一个onStartCommand,我们立刻就要去执行;并不能让他排队。
* 举例:
* 我有个Service,他需要根据startService进来的intent做如下几类活儿:
* 1. Action1 拉起我的FragmentA界面;
* 2. Action2 执行一段网络请求拿到一份xx数据;
* 3. Action3 拉起我的FragmentB界面;
*
* 显然,如果我们使用JobIntentService当有Action2来工作的时候,我们将无法及时响应Action1,3。
* 因为他是排队的。与我们的需求不同:
* 我们要求Action2发起请求就可以结束了,但是Service不能结束。需要等待活干完才能结束。
*
* 你或者会想,我直接JobIntentService里面不在他的onHandleWork卡住,开启子线程跑就好了呀?
* 这样就错误了。因为如果你提前onHandlerWork return掉,就会stopSelf掉就会导致代码游离在Service的生命周期之外。
* 极度容易被oom_obj memKiller杀掉进程。
*
* 因此,设计这个类来满足需求。只有当所有的startId被stopWrap到这里,才能真正stopService。
*/
public abstract class AutoStopService extends Service {
private static final String TAG = "AutoStopService";
protected abstract String getNotifyName();
static final boolean DEBUG = true;
private static final long KEEP_ALIVE_MAX_TIME = 5 * 60 * 1000 + 30 * 1000L; //5分半
private final ArrayList<String> mStartIds = new ArrayList<>(2);
protected AliveHandler aliveHandler;
private class AliveHandler extends Handler {
@Override
public void handleMessage(Message msg) {
//就用what来代表startId。
stopWrap("" + msg.what);
}
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationUtil.startForeground(this, getNotifyName(), getNotifyName(), getNotifyName(), "");
}
aliveHandler = new AliveHandler();
}
@Override
public void onDestroy() {
super.onDestroy();
aliveHandler.removeCallbacksAndMessages(null);
Log.d(TAG, "in onDestroy ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationUtil.stopForegound(this);
}
}
protected final void stopWrap(String startId) {
if(DEBUG) Log.d(TAG, "stop wrap #" + startId);
synchronized (mStartIds) {
//如果startId不在列表中;大于1我们也可以调用删除;并没什么效果
if (mStartIds.size() > 1) {
boolean exist = mStartIds.remove(startId); //注意一定要转为Integer是元素否则错误当成index
if (exist) {
aliveHandler.removeMessages(Integer.parseInt(startId));
}
return;
}
//如果startId在里面;并且就是1个。我们也保护了。判断了最后一个元素不是它我们也不会删除
if (TextUtils.equals(startId, mStartIds.get(0))) {
aliveHandler.removeMessages(Integer.parseInt(startId));
mStartIds.clear();
stopSelf();
}
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(final Intent intent, int flags, final int startIdInt) {
if (DEBUG) Log.d(TAG, "onStart Command #" + startIdInt + ": " + intent);
final String startId = "" + startIdInt;
synchronized (mStartIds) {
mStartIds.add(startId);
}
//每次来一条消息都追加一条delay5分半的事件。并且不移除老的事件。到点后,希望将本startId进行stopWrap
aliveHandler.sendEmptyMessageDelayed(startIdInt, KEEP_ALIVE_MAX_TIME);
onHandleWork(intent, startId);
return START_REDELIVER_INTENT;
}
/**
* 替代你的onStartCommand。
* 不论这里面同步或者异步,最后完成本次工作后,请调用stopWrap(startIdStr)
*/
protected abstract void onHandleWork(Intent intent, String startIdStr);
/**
* keep alive 我们将给予delay保活service的权限
*/
// public static void keepAlive(Context context, Class<? extends AutoStopService> clazz, String tag) {
// keepAlive(context, clazz, tag, KEEP_ALIVE_MAX_TIME);
// }
// public static void keepAlive(Context context, Class<? extends AutoStopService> clazz, String tag, long maxKeepTime) {
// Intent intent = new Intent(context, clazz);
// intent.putExtra(TAG_KEEP_ALIVE_TIME, Math.max(KEEP_ALIVE_MIN_TIME, Math.min(maxKeepTime, KEEP_ALIVE_MAX_TIME)));
// context.startService(intent);
// }
}
//子类分装使用BaseSevice是实现了AutoStopService的。
public static void startSelf(Context context, String pageType) {
Intent intent = new Intent(context, BaseService.class);
intent.putExtra("pageType", pageType);
startServiceWrap(context, intent);
}
public static void startSelf(Context context, String pageType, Bundle bundle) {
Intent intent = new Intent(context, BaseService.class);
intent.putExtra("pageType", pageType);
intent.putExtras(bundle);
startServiceWrap(context, intent);
}
private static void startServiceWrap(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
public final class NotificationUtil {
private static final String CHANNEL_ONE_ID = todo;
private static final int NOTIFY_ID = 0x111;
private static final int FOREGROUND_ID = 0x112;
/**
* 公开使用
*/
public static void sendNotification(Context context, String channelName, String channelDesc, String contentTitle, String contentText) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.notify(NOTIFY_ID, getNotificationO(context, manager, channelName, channelDesc, contentTitle, contentText));
} else {
manager.notify(NOTIFY_ID, getNotification(context, contentTitle, contentText));
}
}
/**
* onStartCommand调用
*/
public static void startForeground(Service service, String channelName, String channelDesc, String contentTitle, String contentText) {
NotificationManager manager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification = getNotificationO(service, manager, channelName, channelDesc, contentTitle, contentText);
} else {
notification = getNotification(service, contentTitle, contentText);
}
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
service.startForeground(FOREGROUND_ID, notification);
}
/**
* onDestory之前调用
*/
public static void stopForegound(Service service) {
service.stopForeground(true);
}
private static Notification getNotificationO(Context context, NotificationManager manager, String name, String desc, String contentTitle, String contentText) {
Notification.Builder builder;
NotificationChannel channel = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
channel = new NotificationChannel(CHANNEL_ONE_ID, name,
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(desc);
manager.createNotificationChannel(channel);
builder = new Notification.Builder(context, CHANNEL_ONE_ID);
builder.setCategory(Notification.CATEGORY_RECOMMENDATION)
.setContentTitle(contentTitle)
.setContentText(contentText)
//.setContentIntent(getPendingIntent(context))
.setSmallIcon(android.R.drawable.ic_notification_overlay); //todo
return builder.build();
}
//channel.enableLights(true);
//channel.setLightColor(color);
//Uri mUri = Settings.System.DEFAULT_NOTIFICATION_URI;
//channel.setSound(mUri, Notification.AUDIO_ATTRIBUTES_DEFAULT);
// Register the channel with system; you can't change the importance
// or other notification behaviors after this
return null; //不可能调用到这里
}
private static Notification getNotification(Context context, String contentTitle, String contentText) {
Notification.Builder builder = new Notification.Builder(context)
.setPriority(Notification.PRIORITY_DEFAULT)
//.setLights(color, 1000, 0)
//.setSound(null, null);
;
builder.setCategory(Notification.CATEGORY_RECOMMENDATION)
.setContentTitle(contentTitle)
.setContentText(contentText)
//.setContentIntent(getPendingIntent(context))
.setSmallIcon(android.R.drawable.ic_notification_overlay); //todo
return builder.build();
}
// private static PendingIntent getPendingIntent(Context context) {
// Intent intent = new Intent(context, MainActivity.class);
// intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
// PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// return pi;
// }
}