关于前台后台切换
最近接触到一个地方就是app的前后台,及前后服务,有点生疏,,话不多说进入正题
- service 前台保活
- service 后台换前台广告页面
1
关于保活这里可以说是漫天都是了,很多方案,通知栏保活,屏幕1像素啊,播放无声音乐,两个service相互监听啊。很多,大家可以去查一下,有很多方案
参考 原博 https://blog.csdn.net/andrexpert/article/details/75045678
将Service置为前台,目的时提高进程Service的oom_adj值,以降低其被系统回收的几率。该方案的原理是,通过使用 startForeground()方法将当前Service置于前台来提高Service的优先级。需要注意的是,对API大于18而言 startForeground()方法需要弹出一个可见通知,如果你觉得不爽,可以开启另一个Service将通知栏移除,其oom_adj值还是没变的。实现代码如下:
a) DaemonService.java
/**前台Service,使用startForeground
* 这个Service尽量要轻,不要占用过多的系统资源,否则
* 系统在资源紧张时,照样会将其杀死
*
* Created by jianddongguo on 2017/7/7.
* http://blog.csdn.net/andrexpert
*/
public class DaemonService extends Service {
private static final String TAG = "DaemonService";
public static final int NOTICE_ID = 100;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if(Contants.DEBUG)
Log.d(TAG,"DaemonService---->onCreate被调用,启动前台service");
//如果API大于18,需要弹出一个可见通知
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
boolean isLollipop1 = Build.VERSION.SDK_INT >= 21 ;
int smallIcon1 = getApplicationContext().getResources().getIdentifier(
"notification_small_icon", "drawable",
getApplicationContext().getPackageName());
if(smallIcon1 <= 0 || !isLollipop1) {
smallIcon1 = getApplicationContext().getApplicationInfo().icon;
}
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(smallIcon1);
builder.setContentText("一条通知");
builder.setContentTitle("Title");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId("holoview_im_notification_id");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("holoview_im_notification_id", "holoview_im_notification", NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
startForeground(CommonUtil.NOTIFICATION_ID, builder.build());
}else {
startForeground(CommonUtil.NOTIFICATION_ID, new Notification());
Intent intent = new Intent(getApplicationContext(), DeskService.class);
startService(intent);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 如果Service被终止
// 当资源允许情况下,重启service
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
// 如果Service被杀死,干掉通知
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
mManager.cancel(NOTICE_ID);
}
if(Contants.DEBUG)
Log.d(TAG,"DaemonService---->onDestroy,前台service被杀死");
// 重启自己
Intent intent = new Intent(getApplicationContext(),DaemonService.class);
startService(intent);
}
}
讲解一下:
这里还用到了两个技巧:一是在onStartCommand方法中返回START_STICKY,其作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用。其二在onDestory方法中重新启动自己,也就是说,只要Service在被销毁时走到了onDestory这里我们就重新启动它。
b) CancelNoticeService.java
/** 移除前台Service通知栏标志,这个Service选择性使用
*
* Created by jianddongguo on 2017/7/7.
* http://blog.csdn.net/andrexpert
*/
public class CancelNoticeService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(DaemonService.NOTICE_ID,builder.build());
// 开启一条线程,去移除DaemonService弹出的通知
new Thread(new Runnable() {
@Override
public void run() {
// 延迟1s
SystemClock.sleep(1000);
// 取消CancelNoticeService的前台
stopForeground(true);
// 移除DaemonService弹出的通知
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.cancel(DaemonService.NOTICE_ID);
// 任务完成,终止自己
stopSelf();
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
c) AndroidManifest.xml
<service android:name=".service.DaemonService"
android:enabled="true"
android:exported="true"
android:process=":service"/>
<service android:name=".service.CancelNoticeService"
android:enabled="true"
android:exported="true"
android:process=":service"/>
讲解一下:
总所周知,一个Service没有自己独立的进程,它一般是作为一个线程运行于它所在的应用进程中,且应用进程名称与包名一致。如果希望指定的组件和应用运行在指定的进程中,就需要通过android:process属性来为其创建一个进程,因此android:process=":daemon_service"就是让DaemonService运行在名为“com.jiangdg.keepappalive:daemon_service”进程中;android:enabled属性的作用是Android系统是否实例化应用程序中的组件;android:exported属性的作用是当前组件(Service)是否可以被包含本身以外的应用中的组件启动。
接下来就是启动service了,这个亲身测试没有什么问题。还有一些其他的方案,大家可以去参考原博
2
接下来就是关于后台切前台广告页面,这个其实思路很简单,用到android的原生方法ActivityLifecycleCallbacks去监听前后台切换,通过计算时间超过10秒的话进行广告,否则的话就是直接进入主页,这样一说就明确了很多
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
//前台
Log.i("aaa", "onActivityStarted: "+"在前台");
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
//后台
Log.i("aaa", "onActivityStarted: "+"在后台");
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
当知道这个方法后整个编写就简单多了。就是监听台前操作时监听时间
if ( canShowAdvertisement(activity.getClass())) {
//如果是PhotoActivity时,在开启相机或者打开相册返回时不显示广告
//onActivityResult(int requestCode, int resultCode, Intent data)在onActivityStarted(Activity activity)方法前调用,
//所以不能在onActivityResult(int requestCode, int resultCode, Intent data)中设置enableAdvertisement = true
if (activity instanceof PhotoActivity && !((PhotoActivity) activity).enableAdvertisement){
((PhotoActivity) activity).enableAdvertisement = true;
return;
}
//说明从后台回到了前台
long lastShowAdvertisementTimeStamp = SharePreferencesUtils.getInstance().getLong(Configration.SP_ADVERTISEMENT_LAST_SHOW_TIME, 0L);
long curTime = System.currentTimeMillis();
if ((curTime - lastShowAdvertisementTimeStamp > ADVERTISEMENT_INTERNAL_TIME)) {
SharePreferencesUtils.getInstance().saveLong(Configration.SP_ADVERTISEMENT_LAST_SHOW_TIME, curTime);
activity.startActivity(new Intent(activity, AdvertisementViewActivity.class));
}
}
意思就是说呢,返回到台前时,如果是从相册或是相机的话就没有广告,否则其他的话就有广告,然后通过sp存入切入后台时间,在切换到前台时进行比较,如果在相差时间内就可以出广告,这个时间就可以自定义了。贴入完整代码
public class MyApplication extends Application {
public static final long ADVERTISEMENT_INTERNAL_TIME = 2 * 60 * 60 * 1000;//2小时
private final String TAG = getClass().getSimpleName();
private int mFinalCount;
private ArrayMap<String, String> advertisementFilter = new ArrayMap<>();
private ActivityLifecycleCallbacks activityLifecycleCallbacks = new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
mFinalCount++;
Log.e(TAG, "onActivityStarted: " + activity.getClass().getSimpleName() + ">>>" + mFinalCount);
if (mFinalCount == 1 && canShowAdvertisement(activity.getClass())) {
//如果是PhotoActivity时,在开启相机或者打开相册返回时不显示广告
//onActivityResult(int requestCode, int resultCode, Intent data)在onActivityStarted(Activity activity)方法前调用,
//所以不能在onActivityResult(int requestCode, int resultCode, Intent data)中设置enableAdvertisement = true
if (activity instanceof PhotoActivity && !((PhotoActivity) activity).enableAdvertisement){
((PhotoActivity) activity).enableAdvertisement = true;
return;
}
//说明从后台回到了前台
long lastShowAdvertisementTimeStamp = SharePreferencesUtils.getInstance().getLong(Configration.SP_ADVERTISEMENT_LAST_SHOW_TIME, 0L);
long curTime = System.currentTimeMillis();
if ((curTime - lastShowAdvertisementTimeStamp > ADVERTISEMENT_INTERNAL_TIME)) {
SharePreferencesUtils.getInstance().saveLong(Configration.SP_ADVERTISEMENT_LAST_SHOW_TIME, curTime);
activity.startActivity(new Intent(activity, AdvertisementViewActivity.class));
}
}
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
mFinalCount--;
// Log.e(TAG, "onActivityStopped: " + activity.getClass().getSimpleName() + ">>>" + mFinalCount);
if (mFinalCount == 0) {
//说明从前台回到了后台
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
};
@Override
public void onCreate() {
Log.e(TAG, "onCreate()");
super.onCreate();
InstanceManager.getInstance().init(this);
//初始化SharedPreferences工具
SharePreferencesUtils.getInstance().init(this, "share_data");
//注册activity生命周期监听
if (BuildConfig.enableAdvertisementFeature) {
registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
//启动界面不显示广告页
addToAdvertisementFilter(LaunchActivity.class);
addToAdvertisementFilter(AdvertisementViewActivity.class);
}
}
/**
* Add activity to advertisement filter.
*
* @param clazz clazz
*/
public void addToAdvertisementFilter(Class<? extends Activity> clazz) {
String key = clazz.getName();
String activityName = clazz.getSimpleName();
if (!advertisementFilter.containsKey(key))
advertisementFilter.put(key, activityName);
}
/**
* Remove activity from advertisement filter.
*
* @param clazz clazz
*/
public void removeFromAdvertisementFilter(Class<? extends Activity> clazz) {
String key = clazz.getName();
if (advertisementFilter.containsKey(key))
advertisementFilter.remove(key);
}
private boolean canShowAdvertisement(Class<? extends Activity> clazz){
String key = clazz.getName();
return !advertisementFilter.containsKey(key);
}
}
参考博客https://www.jianshu.com/p/b6b0f3c4efb1
其实这个广告的话拓展性很大,也不是局限这一种方式。希望大家一起努力吧