根据JobIntentService的android8以下版本实现AutoStopService

承接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;
//    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值