Android_ principle chapter _ keeping alive the process Process

Discussion Android6.0 and above system memory resident APP (keep alive) implementation - Part rivalry
(Reproduced please state source: http: //blog.csdn.net/andrexpert/article/details/75045678)

APP keep alive series (up to 7.0 to Android):
(1)  to explore a novel dual keep-alive applications daemon process method
(2)  to explore Android6.0 and above systems APP resident (keep alive) to implement memory - rivalry articles
( 3)  explore the permanent memory (keep alive) to achieve Android6.0 and above systems APP - resurrection articles

       APP permanent memory (keep alive), revisit the issues, study time over a distance of more than six months also. Recently, user feedback, said multi-process daemon program in Huawei Mate8 (7.0) keep alive the effect is not very good, and sometimes still can not receive the message in time, then, and with suspicion, rediscovered the original test code, by the way analysis of the mainstream sports APP keep alive method (micro-channel, hand-Q even if the rich people's children, not representative), but also on system memory APP management rules were further explore the market. This article is the last week to explore, study and summarize one test for future contingencies.
A, APP keep alive the core idea of induction
     for Android6.0 and above systems APP keep alive, I think mainly carried out by these two aspects, namely: reduce omm_adj value, try to ensure that the process will not be killed by the system; the process is killed after the resurrection of the process in other ways. But need to understand is that the face of the mobile phone manufacturers of the depth of customization and Google increasingly stringent resource management mechanism, to combine the two keep-alive is not permanent, can only be relative existence, is not the result of different models the same.

Due to space constraints, to reduce the chance of APP killed, as well as reducing the value of oom_adj oom_adj value analysis under the main means by which this paper is how to do? Next, we need to understand the rules of the Android system in the process of recovery of memory is based on: activity in memory processes when there are five states, namely a foreground process, we can see the process, service process, background processes, empty process, these types of process state from high to low priority, oom_adj values ​​from low to high (in ProcessList defined), then the system will be recovered Android corresponding process based on the current system resources and processes oom_adj value, the foreground process is generally not recovered, the process air most likely to be recovered, this management rule is "legendary" in Low Memory Killer. For a more intuitive understanding of this rule, I drew a table:


Note: Priority 1 is the most advanced, oom_adj ordinary course of> = 0, the system processes oom_adj <0, the system will process in line with the recovery of certain oom_adj values ​​according to the respective memory threshold. In addition, oom_adj value will be occupied with the greater physical memory increases, the system processes the system will not be killed.

Second, the market mainstream sports APP analysis
1. plump (v 7.17.0)
(1) a key clean-up / clean-up sliding

a. When the "plump" is stopped, its process is killed, the notification bar icon to be cleaned, wait a few minutes without automatic restart, when re-entering the "plump", will re-enter from the welcome screen;
b when the "plump" movement in the state, the process of death notification bar icon is cleared, wait a few minutes does not automatically restart, but again. when entering the "plump", which is displayed directly moving interface, but did not enter from the welcome screen, exercise time and other state be cleaned with the same;
c when the "plump" in motion to suspend the state, its normal process alive, the notification area icon display properly. . If cleanup alone, the process of death, the notification bar icons are cleared; but when re-entering the "plump", its direct interface displays movement, but did not enter from the welcome screen, motion and time when the state is the same as with the clean-up;
(2) black / lock screen
a when "plump" is stopped, retreated to the background, lock screen completely black screen, wait 5 minutes, the process of death, the notification bar is cleared;.
. b when the "plump" movement in the state, retreated to the background, lock screen completely black screen, then enter the system, "plump" running interface automatically pop up. Lock screen again, wait 20 minutes, and the process has not been killed, "plump" running automatic pop-up interface, motion remains unchanged;
c when the "plump" in motion a suspended state, retreated to the background, lock screen completely black screen, then. re-enter the system, "plump" running interface automatically pop up. Lock screen again. Wait 20 minutes, and the process has not been killed, "plump" running automatic pop-up interface, motion remains unchanged;
* premise: "Mobile housekeeper -> lock screen application cleanup" closed;
               "Mobile housekeeper ->

                Movement or blinking text interface sports timing
                off net
* Analysis: When the "plump" is at a standstill, a key clean-up and black state will be killed, indicating that in the absence of movement into the interface, its keep-alive mechanism is not activated (ie no so that movement of the interface switch to the background, etc.). When the "plump" is in motion, a key clean-up and black state not killed (except the slide clean up), explained that it had initiated keep alive mechanism: ① "plump" prohibits the return key to ensure that the movement Activity not be destroyed; ② constantly updated notification bar timed to ensure that APP is always in the foreground, prevent recovery system; ③ "plump" after being cleaned automatically restart automatically pop up after the notice is deleted, indicating that there may be another thing (process or Service) listener movement Service (or process) the survival of the state, when the Service is destroyed, immediately pull it up; the ④ "plump" is forced to stop or kill the cleanup, re-enter the sport will be displayed directly interface and can be kept in motion before the killing, explain the possible use of configuration file records related to the state; ⑤ after the lock screen / unlock, "plump" campaign interface will automatically pop up, indicating that the use of the broadcast mechanism to lock screen broadcast listening, pop-up Activity in order to ensure that the process is always in the foreground;
* Conclusion: The resident notification bar, two processes guard, broadcast lock screen, custom lock screen
* Note: The above is Huawei Mate8 (7.0) test Fruit; others such as Samsung C9 (6.0) keep alive is better, especially when a key clean-up, "plump starts automatically, it is estimated that the use of the process of guarding tactics, and Samsung are using the native system, so the results you know; 360F4 (6.0) keep alive the poor, indeed rogue fighter, rogue more ways to get rid of rogue APP;
2. Music power (v7.3.2)
(1) a key clean-up / clean-up slide

      Samsung C9 (6.0): No matter what states, "Music power" process is killed, wait a few minutes, it does not start automatically;
      360F4 (6.0): No matter what kind of state, "Music Power" process is killed, wait a few minutes, it does not start automatically;
      Huawei Mate8 (7.0): No matter what kind of state, "Music Power" process is killed, wait a few minutes , does not start automatically;
(2) lock screen / black screen
 a when "Music power" is stopped, retreated to the background, lock screen, wait 5 minutes, the process of death, the notification bar is cleared;.
 . b when "Music power" in movement suspended, retreated to the background, and then open the lock screen, moving interface is switched to the foreground, and forced eject custom lock screen (overlying system lock screen); lock screen again, wait 20 minutes, and the application process alive ; C
 when "Music power" a state in motion, the background retreated, and then open the lock screen, the interface is moving to the foreground, and the forced eject custom lock screen (overlying lock screen system); lock again screen, wait 20 minutes, and the application process alive;
* premise: "Mobile housekeeper -> lock screen application cleanup" closed;
               "mobile phone housekeeper -> from Kai management" closed;
                motion, disable the back button, the user can only withdraw from the Home key to back
                off net
* analysis: when When "Music Power" is stopped, the black screen state, their systems have been killed in a short time, indicating that the keep-alive mechanism is not enabled; but is in motion or paused state, "Music Power" is not a period of time kill, and when the lock screen, "Music power" movement of the interface will automatically switch to the front, in addition, will be forced to pop up a custom lock screen, which shows "Music power" The keep-alive mechanism is likely to be using the monitor screen broadcast to force the lock switch interface associated to the front, to improve the "Music Power" survival in a black state.
* Conclusion: Permanent notification bar, lock screen broadcast, custom lock screen
3. Wyatt moving coil (v3.1.2.9)
(1) a key clean-up / sliding cleanup

      Samsung C9 (6.0): the same effect with music power;
      360F4 (6.0): the same effect with music power;
      Huawei Mate8 (7.0): the same effect with music power;
(2) lock screen / black
a when "Wyatt dynamic" is stopped, retreated to the background, lock screen, wait three minutes, the process of death, the notification bar is cleared;.
. b when "Wyatt dynamic" in motion suspended, custom lock screen switch interface to the front and plump, music power, as the same effect;
c when "Wyatt dynamic" in the movement state, custom lock screen, switch the interface to the front and plump, as music power, the same effect;.
* Conclusion: Permanent notification bar, lock screen broadcast, custom lock screen
three, APP keep alive program to explore
    through discussion above analysis, "plump", "Music power" and other such APP mainly by listening to the lock screen, broadcast networks and other systems, the process the foreground in order to improve the level of the process, thus preventing the process is not so easy to get rid of the system. In addition, "plump" may also use the clean-up process is related to the resurrection of strategy. Of course, for the resurrection of strategy, we then discuss the next article, this article focuses more APP is to reduce the value by which the process omm_adj way to prevent them from being killed by the system.
    In order to achieve the "plump" and other similar effects APP, we simulate such a scenario: When a user logs test APP, to not open the keep-alive function; when the user starts running, keep-alive function is turned on, then do it on the basis of black run, a key clean-up, forced to stop testing and other functions. In other words, Android project SplashActivity, LoginActivity just with our "acting" And really start APP logic is to keep alive SportsActivity, it staged a "harem rivalry," opera.
     Well, boys, start your show!
1. Open the front desk Service, "to force the monarch host"
    The Service is set to the front desk, to improve the process of oom_adj Service value purposes, in order to reduce their chance of recovery systems. The principle of the program is that by using startForeground () method to the foreground to enhance the current Service Priority Service. Note that, in terms of the API greater than 18 startForeground () method requires a visible notification pops up, if you feel uncomfortable, you can open another Service will remove the notification bar, it still has not changed the value oom_adj. Codes are as follows:

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){
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setContentTitle("KeepAppAlive");
            builder.setContentText("DaemonService is runing...");
            startForeground(NOTICE_ID,builder.build());
            // 如果觉得常驻通知栏体验不好
            // 可以通过启动CancelNoticeService,将通知移除,oom_adj值不变
            Intent intent = new Intent(this,CancelNoticeService.class);
            startService(intent);
        }else{
            startForeground(NOTICE_ID,new Notification());
        }
    }


    @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);
    }
}
Explain:
       here also uses two techniques: First, in return START_STICKY onStartCommand method, the effect is that when Service is kill the process, the system will attempt to re-create the Service, and the Service will hold for the start of the state, but not Intent object remains passed, onStartCommand method will be called again. The second restart itself in onDestory method, that is, as long as the Service come here to destroy onDestory when we restart it.
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=":daemon_service"/>


<service android:name=".service.CancelNoticeService"
            android:enabled="true"
            android:exported="true"
            android:process=":service"/>
Explain:

      总所周知,一个Service没有自己独立的进程,它一般是作为一个线程运行于它所在的应用进程中,且应用进程名称与包名一致。如果希望指定的组件和应用运行在指定的进程中,就需要通过android:process属性来为其创建一个进程,因此android:process=":daemon_service"就是让DaemonService运行在名为“com.jiangdg.keepappalive:daemon_service”进程中;android:enabled属性的作用是Android系统是否实例化应用程序中的组件;android:exported属性的作用是当前组件(Service)是否可以被包含本身以外的应用中的组件启动。

d) 测试结果
接下来,我们观察下KeepAppAlive进程的oom_adj值变化:
  首先,adb查看KeepAppAlive进程的进程号;
  E:\Android\StudioProject\KeepAppAlive>adb shell
  shell@trltechn:/ $ su
  root@trltechn:/ # ps | grep jiangdg

  
  其次,观察KeepAppAlive进程在不同状态下的oom_adj值;
  root@trltechn:/ # cat /proc/15689/oom_adj
  root@trltechn:/ # cat /proc/16033/oom_adj

  
注意:如果执行su命令,提示"/system/bin/sh: su: not found",说明手机设备没有被root。ps命令用于显示静态进程状态,top命令可以对进程进行实时监控,每次启动KeepAppAlive进程号都不一样。

2. 监听锁屏广播,“制造‘1像素’惨案”
a) ScreenReceiverUtil.java
/** 静态监听锁屏、解锁、开屏广播
 *  a) 当用户锁屏时,将SportsActivity置于前台,同时开启1像素悬浮窗;
 *  b) 当用户解锁时,关闭1像素悬浮窗;
 *
 * Created by jianddongguo on 2017/7/8.
 * http://blog.csdn.net/andrexpert
 */
public class ScreenReceiverUtil {
    private Context mContext;
    // 锁屏广播接收器
    private SreenBroadcastReceiver mScreenReceiver;
    // 屏幕状态改变回调接口
    private SreenStateListener mStateReceiverListener;


    public ScreenReceiverUtil(Context mContext){
        this.mContext = mContext;
    }


    public void setScreenReceiverListener(SreenStateListener mStateReceiverListener){
        this.mStateReceiverListener = mStateReceiverListener;
        // 动态启动广播接收器
        this.mScreenReceiver = new SreenBroadcastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);
        mContext.registerReceiver(mScreenReceiver,filter);
    }


    public void stopScreenReceiverListener(){
        mContext.unregisterReceiver(mScreenReceiver);
    }


    public  class SreenBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d("KeepAppAlive","SreenLockReceiver-->监听到系统广播:"+action);
            if(mStateReceiverListener == null){
                return;
            }
            if(Intent.ACTION_SCREEN_ON.equals(action)){         // 开屏
                mStateReceiverListener.onSreenOn();
            }else if(Intent.ACTION_SCREEN_OFF.equals(action)){  // 锁屏
                mStateReceiverListener.onSreenOff();
            }else if(Intent.ACTION_USER_PRESENT.equals(action)){ // 解锁
                mStateReceiverListener.onUserPresent();
            }
        }
    }


    // 监听sreen状态对外回调接口
    public interface SreenStateListener {
        void onSreenOn();
        void onSreenOff();
        void onUserPresent();
    }
}
讲解一下:
    由于静态注册广播接收器,无法接收到系统的锁屏(Intent.ACTION_SCREEN_OFF)和开屏(Intent.ACTION_SCREEN_ON)广播,因此必须通过动态注册来监听。另外,这里还使用了接口将监听的结果回调给调用者。
b) ScreenManager.java
/**1像素管理类
 *
 * Created by jianddongguo on 2017/7/8.
 * http://blog.csdn.net/andrexpert
 */


public class ScreenManager {
    private static final String TAG = "ScreenManager";
    private Context mContext;
    private static ScreenManager mSreenManager;
    // 使用弱引用,防止内存泄漏
    private WeakReference<Activity> mActivityRef;


    private ScreenManager(Context mContext){
        this.mContext = mContext;
    }


    // 单例模式
    public static ScreenManager getScreenManagerInstance(Context context){
        if(mSreenManager == null){
            mSreenManager = new ScreenManager(context);
        }
        return mSreenManager;
    }


    // 获得SinglePixelActivity的引用
    public void setSingleActivity(Activity mActivity){
        mActivityRef = new WeakReference<>(mActivity);
    }


    // 启动SinglePixelActivity
    public void startActivity(){
        if(Contants.DEBUG)
            Log.d(TAG,"准备启动SinglePixelActivity...");
        Intent intent = new Intent(mContext,SinglePixelActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
    }


    // 结束SinglePixelActivity
    public void finishActivity(){
        if(Contants.DEBUG)
            Log.d(TAG,"准备结束SinglePixelActivity...");
        if(mActivityRef != null){
            Activity mActivity = mActivityRef.get();
            if(mActivity != null){
                mActivity.finish();
            }
        }
    }
}
讲解一下:
    Java中为对象的引用分了四个级别:强引用、软引用、弱引用、虚引用。这里,我们使用了弱引用WeakReference来防止内存泄漏,为了解释这个问题,我们举这么一个例子:有两个类class A和class B,分别实例化这两个类得到a,b,其中a又作为实例化B时传入的构造参数,代码如下:
A a = new A();
B b = new B(a);
从这两行代码来看,a是对象A的引用,b是对象B的引用,对象B同时依赖于对象A,对象A和对象B之间形成了强引用。当a=null时,a不在指向对象A,通常情况下,对象A在不被其他对象引用时会被GC回收,但是由于B还依赖于对象A,对象A不会被GC回收,从而造成内存泄漏(除非b=null,对象A和对象B才会被GC同时回收)。如果使用弱引用的话,对象A只会被WeakReference所依赖,当a=null时,GC会回收它,从而避免了内存泄漏。
c) SinglePixelActivity.java

/**1像素Activity
 *
 * Created by jianddongguo on 2017/7/8.
 */


public class SinglePixelActivity extends AppCompatActivity {
    private static final String TAG = "SinglePixelActivity";


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(Contants.DEBUG)
            Log.d(TAG,"onCreate--->启动1像素保活");
        // 获得activity的Window对象,设置其属性
        Window mWindow = getWindow();
        mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
        WindowManager.LayoutParams attrParams = mWindow.getAttributes();
        attrParams.x = 0;
        attrParams.y = 0;
        attrParams.height = 1;
        attrParams.width = 1;
        mWindow.setAttributes(attrParams);
        // 绑定SinglePixelActivity到ScreenManager
        ScreenManager.getScreenManagerInstance(this).setSingleActivity(this);
    }


    @Override
    protected void onDestroy() {
        if(Contants.DEBUG)
            Log.d(TAG,"onDestroy--->1像素保活被终止");
        if(! SystemUtils.isAppAlive(this,Contants.PACKAGE_NAME)){
            Intent intentAlive = new Intent(this, SportsActivity.class);
            intentAlive.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intentAlive);
            Log.i(TAG,"SinglePixelActivity---->APP被干掉了,我要重启它");
        }
        super.onDestroy();
    }
}
讲解一下:
    在UI界面架构中,每个Activity都包含一个Window对象,在Android中Window对象通常由PhoneWindow来实现,PhoneWindow将一个DecorView设置为整个应用窗口的根View,它作为窗口界面的顶层视图,封装了很多通用操作窗口的方法...好了,不扯远了,既然我们已经知道Window对象在一个Activity中的位置,这里我们通过getWindow方法来获得SinglePixelActivity 的Window对象,然后为其设置相关属性,比如窗体的大小、位置、坐标等,来达到所需的"1像素"界面效果。
d) SportsActivity.java
/** 运动界面,启动监听锁屏广播,判断是否开关1像素界面
 *
 * Created by jianddongguo on 2017/7/7.
 * http://blog.csdn.net/andrexpert
 */

public class SportsActivity extends AppCompatActivity {
    // 动态注册锁屏等广播
    private ScreenReceiverUtil mScreenListener;
    // 1像素Activity管理类
    private ScreenManager mScreenManager;
    // 代码省略...


    private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() {
        @Override
        public void onSreenOn() {
            // 移除"1像素"
            mScreenManager.finishActivity();
        }


        @Override
        public void onSreenOff() {
            // 接到锁屏广播,将SportsActivity切换到可见模式
            // "咕咚"、"乐动力"、"悦动圈"就是这么做滴
//            Intent intent = new Intent(SportsActivity.this,SportsActivity.class);
//            startActivity(intent);
            // 如果你觉得,直接跳出SportActivity很不爽
            // 那么,我们就制造个"1像素"惨案
            mScreenManager.startActivity();
        }


        @Override
        public void onUserPresent() {
            // 解锁,暂不用,保留
        }
    };


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sports);
        if(Contants.DEBUG)
            Log.d(TAG,"--->onCreate");
        // 1. 注册锁屏广播监听器
        mScreenListener = new ScreenReceiverUtil(this);
        mScreenManager = ScreenManager.getScreenManagerInstance(this);
        mScreenListener.setScreenReceiverListener(mScreenListenerer);
    }
    // 代码省略...
}
e) AndroidManifest.xml
<activity android:name=".SportsActivity"
            android:launchMode="singleTask"/>
<activity android:name=".SinglePixelActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
            android:excludeFromRecents="true"
            android:finishOnTaskLaunch="false"
            android:launchMode="singleInstance"
            android:theme="@style/SingleActivityStyle"/>

讲解一下:
     android:launchMode属性用于指定activity的启动模式,总共分为四种,即standar模式,每次启动activity都会创建其实例,并加入到任务栈的栈顶;singleTop模式,每次启动activity如果栈顶时该activity则无需创建,其余情况都要创建该activity的实例;singleTask模式,如果被启动的activity的实例存在栈中,则不需要创建,只需要把此activity加入到栈顶,并把该activity以上的activity实例全部pop;singleInstance模式,将创建的activity实例放入单独的栈中,该栈只能存储这个实例,且是作为共享实例存在;
     android:configChanges属性用于捕获手机状态的改变,即当手机状态(如切换横竖屏、屏幕大小)改变时会保存当前活动状态重启Activity,由于SinglePixelActivity肩负着保活的特殊使命,这里使用android:configChanges属性防止Activity重启,它只是调用了onConfigurationChanged(Configuration newConfig)来通知手机状态的改变;
     android:excludeFromRecents属性用于控制SinglePixelActivity不在最近任务列表中显示;
     android:finishOnTaskLaunch属性用于标记当用户再起启动应用(TASK)时是否关闭已经存在的Activity的实例,false表示不关闭;
     android:theme属性用于指定Activity显示主题,这里我们自定义主题SingleActivityStyle
         <style name="SingleActivityStyle" parent="horizontal_slide">
         <!-- 窗体背景颜色为透明 -->
	<item name="android:windowBackground">@android:color/transparent</item>
         <!-- 窗体没有边框 -->
        <item name="android:windowFrame">@null</item>
        <!-- 窗体不包含标题栏 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 窗体悬浮 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 自定义TitleBar时去掉多余的阴影-->
        <item name="android:windowContentOverlay">@null</item>
        <!-- 不允许窗体背景变暗-->
        <item name="android:backgroundDimEnabled">false</item>
        <!-- 窗体切换无动画-->
        <item name="android:windowAnimationStyle">@null</item>
        <!-- 禁用窗口的预览动画-->
        <item name="android:windowDisablePreview">true</item>
        <item name="android:windowNoDisplay">false</item>
    </style>

f) 测试结果

   监听锁屏广播,锁屏时将SportActivity置于前台(可见) 

    

   监听锁屏广播,锁屏时开启SinglePixelActivity(1像素)

   

3 .循环播放一段无声音频,"打造金刚不坏之身"
    对于三星C9、Note4和华为4X来说,结合前台Service和悬浮界面(1像素)的保活方式,在用户不主动清理或强杀的情况下,测试APP的保活效果还是非常不错的。但是,对于华为Mate8来说,效果还是差强人意,尤其是当使用一键清理内存时,测试APP基本无法幸存。然后,"咕咚"却奇妙的活了下来,一键清理怎么也清不掉,正当自己百思不得其"姐"时,一个"恶心"的界面出现在我面前。尼玛!看到下面的红框框没,"咕咚"居然在后台循环播放一个无声音乐,难怪生命力这么旺盛,但是耗电也是杠杠的。好吧,不纠结这么多,这里只是从学技术的角度出发而研究,毕竟用户对耗电量还是很敏感的,不到万不得已还是收敛点,不要这么"风骚",用户体验很重要,一不小心就"泻"了你。

a) PlayerMusicService.java

/**循环播放一段无声音频,以提升进程优先级
 *
 * Created by jianddongguo on 2017/7/11.
 * http://blog.csdn.net/andrexpert
 */
public class PlayerMusicService extends Service {
    private final static String TAG = "PlayerMusicService";
    private MediaPlayer mMediaPlayer;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        if(Contants.DEBUG)
            Log.d(TAG,TAG+"---->onCreate,启动服务");
        mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
        mMediaPlayer.setLooping(true);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                startPlayMusic();
            }
        }).start();
        return START_STICKY;
    }


    private void startPlayMusic(){
        if(mMediaPlayer != null){
            if(Contants.DEBUG)
                Log.d(TAG,"启动后台播放音乐");
            mMediaPlayer.start();
        }
    }


    private void stopPlayMusic(){
        if(mMediaPlayer != null){
            if(Contants.DEBUG)
                Log.d(TAG,"关闭后台播放音乐");
            mMediaPlayer.stop();
        }
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        stopPlayMusic();
        if(Contants.DEBUG)
            Log.d(TAG,TAG+"---->onCreate,停止服务");
        // 重启
        Intent intent = new Intent(getApplicationContext(),PlayerMusicService.class);
        startService(intent);
    }
}
b) AndroidManifest.xml
<service android:name=".service.PlayerMusicService"
          android:enabled="true"
          android:exported="true"
          android:process=":music_service"/>
4. 测试结果
    这里在cmd窗口使用"ps | grep jiangdg"命令,如果进程在内存中存在,则打印进程信息;如果不存在,则没有信息。各机型测试情况如下:
    (1)华为Mate8(7.0):将测试APP置于后台,前台Service在黑屏状态下1分钟之内被干掉,"1像素"悬浮Activity在黑屏状态下测试2小时依然存活,效果还可以。但是,当用户一键清理最近应用时,会被杀死,当在后台开启Serive循环播放一段无声音频时,一键清理依然存活,在置于后台的黑屏模式下存活12小时以上;

    (2)三星C9(6.0):开启前台Service和1像素,KeepAppAlive在黑屏后台模式下存活9个小时以上,看样子原生系统还是温柔些;开启后台播放音频服务,用户一键清理最近应用成功保活;
    (3)华为4X(6.0):效果同C9;
    (4) 三星Note4(5.0):效果同C9;
注:Mate8循环播放一段无声音频,当用户点击一键清理最近应用时,KeepAppAlive不会被干掉,但是如果用户只选择清理KeepAppAlive时,也会被杀死,这与"咕咚"保活效果一致。


三星C9(6.0):运行Demo,后台黑屏保活效果


华为Mate8(7.0):运行Demo,黑屏和一键清理保活效果


Github项目地址: https://github.com/jiangdongguo/KeepingAppAlive




发布了19 篇原创文章 · 获赞 19 · 访问量 3万+

Guess you like

Origin blog.csdn.net/baidu_30084597/article/details/79255616