Android 的保活的3种解决方案

做Android 保活的背景

由于之前做一个项目的时候需要让进程一直在后台活下去保持不被杀死的状态,因此也是各种百度各种苦苦寻找,本来是想着靠Service来做保活的,因为我的手机是6.0系统的,之前试过的各种依靠Service的方式均以失败告终,因此决定站在另一个角度上来解决问题,–>Android的进程。

方案一:双进程守护

其实诸如类似360杀毒软件之类的产品本身原理是通过一个一个的遍历进程,如果存活就杀死从而达到清理软件的作用的,所以我们是可以拿到自己进程和创建新的进程的。而通过AIDL的接口则可以实现跨进程通信,因此,使用双进程并通过进程间的通信是一种可行的解决方案。因此方案一是通过双进程守护来解决这个Android应用保活的。

首先是一个AIDL接口,两边的Service都要通过继承Service_1.Stub来实现AIDL接口中的方法,这里做一个空实现,目的是为了实现进程通信。接口声明如下:

package com.ph.myservice;

interface Service_1 {
String getName();
}

然后是两个Service,为了保持连接,内部写一个内部类实现ServiceConnection的接口,当外部杀了其中一个进程的时候,会进入onDisConnection中,那么此时要做的就是start和bind另一个进程,因为Service的启动是可以多次的,所以这样是没问题的,代码如下:

public class LocalService extends Service {
private ServiceConnection conn;
private MyService myService;

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


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

}

private void init() {
    if (conn == null) {
        conn = new MyServiceConnection();
    }
    myService = new MyService();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(getApplicationContext(), "本地进程启动", Toast.LENGTH_LONG).show();
    Intent intents = new Intent();
    intents.setClass(this, RemoteService.class);
    bindService(intents, conn, Context.BIND_IMPORTANT);
    return START_STICKY;
}

class MyService extends Service_1.Stub {


    @Override
    public String getName() throws RemoteException {
        return null;
    }
}

class MyServiceConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        System.out.println("获取连接");

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Toast.makeText(LocalService.this, "远程连接被干掉了", Toast.LENGTH_SHORT).show();
        LocalService.this.startService(new Intent(LocalService.this,
                RemoteService.class));
        LocalService.this.bindService(new Intent(LocalService.this,
                RemoteService.class), conn, Context.BIND_IMPORTANT);

    }

}

}

远程服务类如下:

public class RemoteService extends Service {
private MyBinder binder;
private ServiceConnection conn;

@Override
public void onCreate() {
    super.onCreate();
    // System.out.println("远程进程开启");
    init();

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(getApplicationContext(), "远程进程启动", Toast.LENGTH_LONG).show();
    Intent intents = new Intent();
    intents.setClass(this, LocalService.class);
    bindService(intents, conn, Context.BIND_IMPORTANT);
    return START_STICKY;
}

private void init() {
    if (conn == null) {
        conn = new MyConnection();
    }
    binder = new MyBinder();
}

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

static class MyBinder extends Service_1.Stub {


    @Override
    public String getName() throws RemoteException {
        return "远程连接";
    }
}

class MyConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        System.out.println("获取远程连接");
    }

    @Override
    public void onServiceDisconnected(ComponentName nme) {
        Toast.makeText(RemoteService.this, "本地连接被干掉了", Toast.LENGTH_SHORT).show();
        RemoteService.this.startService(new Intent(RemoteService.this,
                LocalService.class));
        RemoteService.this.bindService(new Intent(RemoteService.this,
                LocalService.class), conn, Context.BIND_IMPORTANT);
    }
}

}

布局文件里要加上声明

<service android:name=".LocalService" />
    <service
        android:name=".RemoteService"
        android:process=":remote" />

实际情况我个人测试,在5.0以下的模拟器上是没问题的,不管多次从系统的进程里kill掉,也还是会重新启动tos,但是5.0以上这种方法是无效的,5.0以上Android应该是意识到了这种双进程守护的方式,因此修改了一下源码,让这种双进程保活应用的方式无效。因此,针对5.0以上,我们采用另一种方案。

方案二:JobScheduler执行任务调度保活

JobScheduler这个类是21版本google新出来的api,我们看他的文档可以知道大致这个类的作用如下:
这个任务其实是在设备空闲期执行的,而且系统设计的这个api不会很耗电,本意是用来执行一些任务调度的,但是我们设想一下,如果用这个类来执行我们的开启双进程,那么也是一定会在设备空闲期执行的,因此我们写一个类继承JobService,在onstart里声明创建JobScheduler对象,并设置多就执行一次和开机自启动,这样就能确保及时在息屏状态,也能够执行重启进程,所以我们在JobService的onStopJob方法里判断我们的进程是否被回收了,如果被回收了就重启进程,这样子就可以实现5.0以上的进程保活了。具体代码如下:

@SuppressLint("NewApi")
public class JobHandlerService extends JobService {
private JobScheduler mJobScheduler;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    System.out.println("服务被创建");

//        startService(new Intent(this, LocalService.class));
//        startService(new Intent(this, RemoteService.class));

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder builder = new JobInfo.Builder(startId++,
                new ComponentName(getPackageName(), JobHandlerService.class.getName()));

        builder.setPeriodic(5000); //每隔5秒运行一次
        builder.setRequiresCharging(true);
        builder.setPersisted(true);  //设置设备重启后,是否重新执行任务
        builder.setRequiresDeviceIdle(true);

        if (mJobScheduler.schedule(builder.build()) <= 0) {
            //If something goes wrong
            System.out.println("工作失败");
        } else {
            System.out.println("工作成功");
        }
    }
    return START_STICKY;
}


@Override
public boolean onStartJob(JobParameters params) {

    Toast.makeText(this, "服务启动", Toast.LENGTH_SHORT).show();
//        || isServiceRunning(this, "com.ph.myservice.RemoteService") == false
    System.out.println("开始工作");
//        if (!isServiceRunning(getApplicationContext(), "com.ph.myservice") || !isServiceRunning(getApplicationContext(), "com.ph.myservice:remote")) {
//            startService(new Intent(this, LocalService.class));
//            startService(new Intent(this, RemoteService.class));
//        }

   /* boolean serviceRunning = isServiceRunning(getApplicationContext(), "com.ph.myservice");
    System.out.println("进程一" + serviceRunning);

    boolean serviceRunning2 = isServiceRunning(getApplicationContext(), "com.ph.myservice:remote");
    System.out.println("进程二" + serviceRunning2);*/
    return false;
}

@Override
public boolean onStopJob(JobParameters params) {
    if (!isServiceRunning(this, "com.ph.myservice.LocalService") || !isServiceRunning(this, "com.ph.myservice.RemoteService")) {
        startService(new Intent(this, LocalService.class));
        startService(new Intent(this, RemoteService.class));
    }
    return false;
}

// 服务是否运行
public boolean isServiceRunning(Context context, String serviceName) {
    boolean isRunning = false;
    ActivityManager am = (ActivityManager) this
            .getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> lists = am.getRunningAppProcesses();


    for (ActivityManager.RunningAppProcessInfo info : lists) {// 获取运行服务再启动
        System.out.println(info.processName);
        if (info.processName.equals(serviceName)) {
            isRunning = true;
        }
    }
    return isRunning;

}


}


public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        openJobService();
    } else {
        openTwoService();
    }

}

private void openTwoService() {
    startService(new Intent(this, LocalService.class));
    startService(new Intent(this, RemoteService.class));
}

private void openJobService() {

    Intent intent = new Intent();
    intent.setClass(MainActivity.this, JobHandlerService.class);
    startService(intent);

}
}

经过我6.0系统的华为真机测试是没有问题的,就算处于息屏状态进程也还是活着的,不管过多久打开屏幕还是会tos,并且关机了开机也会tos。以上就是我的Android保活的两种方案啦,其实还有更深入的几种方案,但是涉及到ndk层和Linux层我目前也没做,就不在这里说啦。

方案三:1像素Activity进程保活

Android8.0马上就要发布,Google对于安卓市场上各种应用占据内存空间的问题不断进行内存完善,我们的应用进程很难再保持不死之身,以前的服务唤醒在5.0已经失效,现在我们尽可能的做到保活方式就是提高进程的优先级,本文介绍一种1像素Activity保活进程的实现(据说QQ也在用)。

其整个逻辑就是在手机屏幕黑屏时,我们启动一个1像素的Activity,其占用内存很小毕竟只有1像素嘛,无形中减小了内存的回收几率,在屏幕亮的时候就关闭该页面。

1、首先需要在MainActivity中注册一个监听手机屏幕状态的广播监听:
//注册监听屏幕的广播
mOnepxReceiver = new OnePixelReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.SCREEN_OFF");
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.USER_PRESENT");
registerReceiver(mOnepxReceiver, intentFilter);
2、监听到屏幕状态后的处理:
public class OnePixelReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {    //屏幕关闭启动1像素Activity
        Intent it = new Intent(context, OnePiexlActivity.class);
        it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(it);
    } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {   //屏幕打开 结束1像素
        context.sendBroadcast(new Intent("finish"));
        Intent main = new Intent(Intent.ACTION_MAIN);
        main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        main.addCategory(Intent.CATEGORY_HOME);
        context.startActivity(main);
    }
}
}
3、创建我们的1像素的Activity:
public class OnePiexlActivity extends Activity {

private BroadcastReceiver endReceiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //设置1像素
    Window window = getWindow();
    window.setGravity(Gravity.LEFT | Gravity.TOP);
    WindowManager.LayoutParams params = window.getAttributes();
    params.x = 0;
    params.y = 0;
    params.height = 1;
    params.width = 1;
    window.setAttributes(params);

    //结束该页面的广播
    endReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            finish();
        }
    };
    registerReceiver(endReceiver, new IntentFilter("finish"));
    //检查屏幕状态
    checkScreen();
}

@Override
protected void onResume() {
    super.onResume();
    checkScreen();
}

/**
 * 检查屏幕状态  isScreenOn为true  屏幕“亮”结束该Activity 
 */
private void checkScreen() {

    PowerManager pm = (PowerManager) OnePiexlActivity.this.getSystemService(Context.POWER_SERVICE);
    boolean isScreenOn = pm.isScreenOn();
    if (isScreenOn) {
        finish();
    }
}
}
4、我们还需要设置1像素activity为透明,不然可能会影响体验,但是影响不大,1像素毕竟是挑战视力的存在。
<style name="OnePixelActivity" parent="android:Theme.Holo.Light.NoActionBar">//无标题
    <item name="android:windowIsTranslucent">true</item>//透明
</style>
5、AndroidManifest配置
<activity
    android:name=".OnePiexlActivity"
    android:screenOrientation="portrait"
    android:theme="@style/OnePixelActivity"/>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • 版权声明:本文为LooperJing原创文章,转载请注明出处! 自己曾经也在这个问题上伤过脑经,前几日刚好有一个北...
    LooperJing阅读 29,499评论 98 481
  • Java中的String类可以被继承么? 答:不能,因为它是一个final类,同样的还有Integer,Float...
    gyymz1993阅读 3,884评论 2 104
  • 前一段时间一个新的APP上线审核被拒三次,可谓是困难重重哈。。 第一次:是因为提交的预览图(屏幕快照)顺序问题,i...
    BLMamba阅读 427评论 0 0
  • Chapter 1 遇见 文/ 郁菲 你我未谋面之前,只是两个独立的个体。当两条平行线柔软的偏离轨...
    郁菲阅读 670评论 0 0