双进程守护实现App保活(参考别人写的)

本Demo是根据该大神的博客写的https://blog.csdn.net/andrexpert/article/details/75045678

该文写的主要目的在于巩固下对app保活的使用以及理解,主要用于自己回头查看,不喜勿喷,可以直接看上面链接的内容,以上链接同样附有完整demo,亲测有效。

一、核心思想归纳
App保活主要通过两个方面:1.降低omm_adj值,尽量办证进程不被系统杀死;2.倘若进程被杀死,通过其他方式让进程复活。

二、APP保活方案探讨
1.开启前台service
将service置为前台,降低omm_adj的值,通过使用 startForeground()方法将当前Service置于前台来提高Service的优先级。需要注意的是,对API大于18而言 startForeground()方法需要弹出一个可见通知,可以开启另一个Service将通知栏移除,其oom_adj值还是没变的。实现代码如下:

a) ForegroundService.java

public class ForegroundService extends Service {

    private static final String TAG = ForegroundService.class.getSimpleName();
    public static final int NOTICE_ID = 200;
    public ForegroundService() {
    }

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

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onCreate() {
        super.onCreate();
        if(Constant.DEBUG)
            Log.d(TAG,"ForegroundService---->onCreate被调用,启动前台service");
        //如果API大于18,需要弹出一个可见通知
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
            final String CHANNEL_ID = "channel_id_1";
            final String CHANNEL_NAME = "channel_name_1";
            //获取状态通知栏管理
            NotificationManager mNotificationManager = (NotificationManager)
                    getSystemService(Context.NOTIFICATION_SERVICE);

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                //只在Android O之上需要渠道
                NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
                        CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
                //如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
                //通知才能正常弹出
                mNotificationManager.createNotificationChannel(notificationChannel);
            }

            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setContentTitle("ReadMsg");
            if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
                builder.setChannelId(CHANNEL_ID);
            }
            builder.setContentText("ForegroundService 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());
        }
    }

    /**
     * START_STICKY
     * 作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用
     */
    @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(Constant.DEBUG)
            Log.d(TAG,"ForegroundService---->onDestroy,前台service被杀死");
        // 重启自己
        Intent intent = new Intent(getApplicationContext(),ForegroundService.class);
        startService(intent);
    }
}

讲解一下:
1.onStartCommand方法中返回START_STICKY,其作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用。
2.在onDestory方法中重新启动自己,也就是说,只要Service在被销毁时走到了onDestory这里我们就重新启动它。
3.另外需要注意的是在android 8.0以上,通知需要添加channel。
b) CancelNoticeService.java

public class CancelNoticeService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){

            final String CHANNEL_ID = "channel_id_1";
            final String CHANNEL_NAME = "channel_name_1";
            //获取状态通知栏管理
            NotificationManager mNotificationManager = (NotificationManager)
                    getSystemService(Context.NOTIFICATION_SERVICE);

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                //只在Android O之上需要渠道
                NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
                        CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
                //如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
                //通知才能正常弹出
                mNotificationManager.createNotificationChannel(notificationChannel);
            }

            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
                builder.setChannelId(CHANNEL_ID);
            }
            startForeground(ForegroundService.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(ForegroundService.NOTICE_ID);
                    // 任务完成,终止自己
                    stopSelf();
                }
            }).start();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

c) AndroidManifest.xml

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

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

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(Constant.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(Constant.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(Constant.DEBUG)
            Log.d(TAG,"onCreate--->启动1像素保活");
        Window mWindow = getWindow();
        mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
        WindowManager.LayoutParams attrParams = mWindow.getAttributes();
        attrParams.x = 0;
        attrParams.y = 0;
        attrParams.height = 300;
        attrParams.width = 300;
        mWindow.setAttributes(attrParams);
        // 绑定SinglePixelActivity到ScreenManager
        ScreenManager.getScreenManagerInstance(this).setSingleActivity(this);
    }

    @Override
    protected void onDestroy() {
        if(Constant.DEBUG)
            Log.d(TAG,"onDestroy--->1像素保活被终止");
        if(! SystemUtils.isAPPALive(this,Constant.PACKAGE_NAME)){
            Intent intentAlive = new Intent(this, MainActivity.class);
            intentAlive.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intentAlive);
            Log.i(TAG,"SinglePixelActivity---->APP被干掉了,我要重启它");
        }
        super.onDestroy();
    }
}

讲解一下:
既然我们已经知道Window对象在一个Activity中的位置,这里我们通过getWindow方法来获得SinglePixelActivity 的Window对象,然后为其设置相关属性,比如窗体的大小、位置、坐标等,来达到所需的”1像素”界面效果
d)MainActivity.java

public class MainActivity extends BaseActivity implements SendMsgView{

    private static final String TAG = "MainActivity";
//    读取新短信的广播action
    private static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
//    申请权限code码
    private static final int CODE_READ_SMS  = 102;
//    广播
    // 动态注册锁屏等广播
    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() {
            // 解锁,暂不用,保留
        }
    };
    private JobSchedulerManager mJobManager;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注册EventBus
        EventBus.getDefault().register(this);


        // 2. 启动系统任务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mJobManager = JobSchedulerManager.getJobSchedulerInstance(this);
            mJobManager.startJobScheduler();
        }
//        开启前台服务
        startForegroundService();
//        开启音乐服务
        startPlayMusicService();

        // 1. 注册锁屏广播监听器
        mScreenListener = new ScreenReceiverUtil(this);
        mScreenManager = ScreenManager.getScreenManagerInstance(this);
        mScreenListener.setScreenReceiverListener(mScreenListenerer);

        //具体操作代码

    }




    private void startForegroundService() {
        Intent intent = new Intent(this, ForegroundService.class);
        startService(intent);
    }

    private void stopForegroundService() {
        Intent intent = new Intent(this, ForegroundService.class);
        stopService(intent);
    }


    private void startPlayMusicService() {
        Intent intent = new Intent(this,PlayerMusicService.class);
        startService(intent);
    }

    private void stopPlayMusicService() {
        Intent intent = new Intent(this, PlayerMusicService.class);
        stopService(intent);
    }




    /**
     * 销毁EventBus
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(Constant.DEBUG)
            Log.d(TAG,"--->onDestroy");

        EventBus.getDefault().unregister(this);

        mScreenListener.stopScreenReceiverListener();


    }
}
<activity android:name=".activity.MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".activity.Main2Activity" />
<activity
    android:name=".activity.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

<!--1像素Activity风格 @android:color/transparent-->
<style name="SingleActivityStyle" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@null</item>
    <item name="android:windowFrame">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsFloating">true</item>
    <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>

3 .循环播放一段无声音频

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(Constant.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(Constant.DEBUG)
                Log.d(TAG,"启动后台播放音乐");
            mMediaPlayer.start();
        }
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopPlayMusic();
        if(Constant.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" />

复活方案:请继续看大神blog: https://blog.csdn.net/andrexpert/article/details/75174586

源码地址:https://download.csdn.net/download/silence_sep/10562709

Uniapp 是基于 Vue.js 的一款跨平台的开发框架,可以一次编代码,同时在多个平台上运行,如微信小程序、H5 页面、Android 和 iOS 应用。如果要实现 Uniapp 项目的 App 保活,需要考虑以下几个方面: 1. 使用前台服务:在 Android 平台上,可以通过启动一个前台服务来提高应用的优先级,从而让操作系统不会轻易地将应用杀死。可以在 `mainfest.xml` 文件中定义一个 Service,并在 `onStartCommand` 方法中启动前台服务。 2. 使用 JobScheduler:在 Android 5.0 及以上版本,可以使用 JobScheduler API 来实现应用保活。可以通过创建一个 JobService,并在 `onStartJob` 方法中执行需要保活的任务,然后通过 JobScheduler.schedule() 方法将任务添加到系统中。 3. 使用 WorkManager:在 Android 8.0 及以上版本,可以使用 WorkManager API 来实现应用保活。WorkManager 可以让应用在后台执行任务,同时保证任务可靠地完成。可以通过创建一个 Worker,并在 `doWork` 方法中执行需要保活的任务,然后通过 WorkManager.enqueue() 方法将任务添加到系统中。 4. 使用推送服务:可以通过使用推送服务来让应用保持在线状态。当应用在后台时,可以通过推送服务向应用发送消息,从而唤醒应用。同时,可以在应用中集成推送服务的 SDK,从而让应用在后台时也可以接收到推送消息。 总之,在实现 Uniapp 项目的 App 保活时,需要根据具体的需求和场景选择合适的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值