Android Component Development (4)--Encapsulation of Process Keep-Alive Components

foreword

In the previous article, we encapsulated the network request component lib_neworkand the image loading component lib_image_loader. Today we will encapsulate a process keep alive componentlib_pull_alive

related articles:

Android component development (1)--Maven private service construction

Android Component Development (2)--Network Request Component Encapsulation

Android Component Development (3)--Picture Loading Component Encapsulation

Component development.png

In ancient times, there were many black technologies. For example MarsDaemon, the use of dual-process guardianship to keep alive was very popular at the time, but unfortunately 8.0it was abandoned when the times came.

Another example is 1像素Activitythe keep-alive method that appeared later. It is not too much to say that he is a rogue. If everyone uses these operations, because the power consumption is greatly increased, it will directly affect the service life of the mobile phone. Therefore, in order to solve the occurrence of this phenomenon, major mobile phone manufacturers have made restrictions on these rogue behaviors at the system level: 后台进程即使你是要黑科技让进程优先级很高,也可能被杀死, so I would rather call it application 求生rather than application 保活.

The original intention of this method is good, reducing the power consumption of the device, reducing the memory, preventing the phone from getting hot, etc., but for some applications that really need to do keep-alive operations, it can be described as miserable.

So new 求生measures have emerged.

How to survive gracefully

After Android 6.0, the system has launched a battery optimization solution, which will kill some high-power-consuming processes strategically.

Then some people may want to ask, how do high-power-consuming applications such as WeChat and QQ are saved?

看下图

WeChat qq whitelist.awebp

It can be seen that the system has put both WeChat and QQ in them. 白名单How is this done?

In fact, this is a negotiation between WeChat and the manufacturer to set their own application to their battery optimization whitelist. The next time a product asks me why they can do it, I'll throw this picture at them. .

Then can we also let the manufacturer add us to the list? Uh uh uh. .

The guy has great ideas.webp

Fortunately, the mobile phone manufacturers did not block the road, leaving us a way back

  • 1. First use the following code to detect whether our process is in the whitelist:
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean isIgnoringBatteryOptimizations() {
    boolean isIgnoring = false;
    PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if (powerManager != null) {
        isIgnoring = powerManager.isIgnoringBatteryOptimizations(getPackageName());
    }
    return isIgnoring;
}
  • 2. If not: call the following code to apply for whitelisting
@RequiresApi(api = Build.VERSION_CODES.M)
public void requestIgnoreBatteryOptimizations() {
    try {
        Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivity(intent);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

申请时会弹出一个让用户选择的Dialog:

battery optimization options.awebp

窗口中会提示该操作可能是影响电池的使用,如果需要监听用户的按键,可以使用startActivityResultonActivityResult中监听

好了,白名单是加好了,那是不是就是万事大吉了呢?

手机厂商:哪有那么容易,就算你加入了电量优化白名单,你要是不按规矩来,在后台运行的进程还是会被我们杀掉?还有啥招式快快使出来吧

Zoom in. gif eee。。

我们知道进程被杀死,是因为系统的后台管理系统把我们重启的路堵住了,为啥堵我啊?按我说可能系统看你这个进程不顺眼吧,哈哈。。

言归正传:

其实是你不在后台管理的自启动白名单中,自启动白名单就像一张通行证,你的应用需要在系统后台自启动,就要在白名单上,否则哪里来回哪里去吧

那白名单这么好,怎么才能加入TM呢?

要知道市面上手机厂家很多,每个厂家的系统都不一样,一个系统还有很多甚至几十个版本,这让我们怎么加入啊?

而大部分自启动操作可以在厂商的手机管家的设置里面设置:

最理想的做法:我们根据不同手机,甚至是不同的系统版本,给用户呈现一个图文操作步骤,并且提供一个按钮,直接跳转到指定页面进行设置

  • 我们先定义下面两个方法:
/**
 * 跳转到指定应用的首页
 */
private static void showActivity(Context context,@NonNull String packageName) {
	Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
	context.startActivity(intent);
}

/**
 * 跳转到指定应用的指定页面
 */
private void showActivity(Context context,@NonNull String packageName, @NonNull String activityDir) {
	Intent intent = new Intent();
	intent.setComponent(new ComponentName(packageName, activityDir));
	intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
	context.startActivity(intent);
}
  • 手机厂商判断:
华为:
public boolean isHuawei() {
    if (Build.BRAND == null) {
        return false;
    } else {
        return Build.BRAND.toLowerCase().equals("huawei") || Build.BRAND.toLowerCase().equals("honor");
    }
}
小米
public static boolean isXiaomi() {
    return Build.BRAND != null && Build.BRAND.toLowerCase().equals("xiaomi");
}
OPPO
public static boolean isOPPO() {
    return Build.BRAND != null && Build.BRAND.toLowerCase().equals("oppo");
}

VIVO
public static boolean isVIVO() {
    return Build.BRAND != null && Build.BRAND.toLowerCase().equals("vivo");
}
魅族
public static boolean isMeizu() {
    return Build.BRAND != null && Build.BRAND.toLowerCase().equals("meizu");
}
三星
public static boolean isSamsung() {
    return Build.BRAND != null && Build.BRAND.toLowerCase().equals("samsung");
}

  • 手机管家或者自启动界面启动方式:
华为:
private void goHuaweiSetting() {
    try {
        showActivity("com.huawei.systemmanager",
            "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
    } catch (Exception e) {
        showActivity("com.huawei.systemmanager",
            "com.huawei.systemmanager.optimize.bootstart.BootStartActivity");
    }
}
小米:

private void goXiaomiSetting() {
    showActivity("com.miui.securitycenter",
        "com.miui.permcenter.autostart.AutoStartManagementActivity");
}

OPPO:
private void goOPPOSetting() {
    try {
        showActivity("com.coloros.phonemanager");
    } catch (Exception e1) {
        try {
            showActivity("com.oppo.safe");
        } catch (Exception e2) {
            try {
                showActivity("com.coloros.oppoguardelf");
            } catch (Exception e3) {
                showActivity("com.coloros.safecenter");
            }
        }
    }
}

VIVO
private void goVIVOSetting() {
    showActivity("com.iqoo.secure");
}

魅族:
private void goMeizuSetting() {
    showActivity("com.meizu.safe");
}
三星:
private void goSamsungSetting() {
    try {
        showActivity("com.samsung.android.sm_cn");
    } catch (Exception e) {
        showActivity("com.samsung.android.sm");
    }
}

总结下上面我们所讲:

  • 1.为了不被电量优化,我们需要将应用添加进电量优化白名单中
  • 2.为了可以在被杀死后,自己可以启动自己,需要将应用自启动开关开启,可以使

用图文引导的方式:

参考下面这张图:

Autostart.awebp

保活增强:

我们都知道保活操作一般是使用一个前台服务来挂起我们的应用: 还有的保活操作是使用一个JobService来对让系统在某个条件符合下回调一个请求操作。

基于以上分析:

  • 笔者这边封装了一个保活组件-lib_pull_alive

Combining: 前台服务+ JobService+ 电量优化白名单+ 引导用户应用自启动的方式Implemented a 求生scheme, the code is as follows:

keepAliveService.java
package com.anna.lib_keepalive.service;

import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.anna.lib_keepalive.forground.ForgroundNF;
import com.anna.lib_keepalive.utils.Utils;

/**
 * 创建一个JobService用于提高应用优先级
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class KeepAliveService extends JobService {
    private static final String TAG = KeepAliveService.class.getSimpleName();
    private JobScheduler mJobScheduler;
    private static final int JOB_ID = 1;
    private ComponentName JOB_PG;
    private int NOTIFICATION_ID = 10;
    private ForgroundNF mForgroundNF;


    private Handler mJobHandler = new Handler(new Handler.Callback() {

        @Override
        public boolean handleMessage(Message msg) {
            Log.d(TAG, "pull alive.");
            jobFinished((JobParameters) msg.obj, true);
            return true;
        }
    });
    @Override
    public void onCreate() {
        super.onCreate();
        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JOB_PG = new ComponentName(getPackageName(),KeepAliveService.class.getName());
        mForgroundNF = new ForgroundNF(this);
        Utils.requestIgnoreBatteryOptimizations(this);
    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static void start(Context context){
        Intent intent = new Intent(context,KeepAliveService.class);
        context.startService(intent);
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.d(TAG,"onStartJob");
        mJobHandler.sendMessage(Message.obtain(mJobHandler, 1, params));
        return true;
    }

    /**系统回调使用,说明触发了job条件
     * @param params
     * @return
     */
    @Override
    public boolean onStopJob(JobParameters params) {
        Log.d(TAG,"onStopJob");
        mJobHandler.sendEmptyMessage(1);
        return false;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        JobInfo job = initJob();
        mJobScheduler.schedule(job);
        startNotificationForGround();
        return START_STICKY;
    }

    /**
     * 大于18可以使用一个取消Notification的服务
     */
    private void startNotificationForGround(){
        if(Build.VERSION.SDK_INT<18){
            mForgroundNF.startForegroundNotification();
        }else{
            mForgroundNF.startForegroundNotification();
            Intent it = new Intent(this, CancelNotifyervice.class);
            startService(it);
        }
    }

    /**初始化Job任务
     * @return
     */
    private JobInfo initJob() {
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, JOB_PG);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
            builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //执行的最小延迟时间
            builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);  //执行的最长延时时间
            builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS,
                    JobInfo.BACKOFF_POLICY_LINEAR);//线性重试方案
        }else {
            builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
        }
        builder.setPersisted(false);
        builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
        builder.setRequiresCharging(false);
        return builder.build();
    }

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

}

ForgroundNF.java
package com.anna.lib_keepalive.forground;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.os.Build;

import androidx.core.app.NotificationCompat;

import com.anna.lib_keepalive.R;

public class ForgroundNF {
    private static final int START_ID = 101;
    private static final String CHANNEL_ID = "app_foreground_service";
    private static final String CHANNEL_NAME = "前台保活服务";

    private Service service;
    private NotificationManager notificationManager;
    private NotificationCompat.Builder mNotificationCompatBuilder;
    public ForgroundNF(Service service){
        this.service = service;
        initNotificationManager();
        initCompatBuilder();
    }


    /**
     * 初始化NotificationCompat.Builder
     */
    private void initCompatBuilder() {
        mNotificationCompatBuilder = new NotificationCompat.Builder(service,CHANNEL_ID);
        //标题
        mNotificationCompatBuilder.setContentTitle("test keep alive");
        //通知内容
        mNotificationCompatBuilder.setContentText("test alive");
        mNotificationCompatBuilder.setSmallIcon(R.mipmap.ic_launcher_round);
    }

    /**
     * 初始化notificationManager并创建NotificationChannel
     */
    private void initNotificationManager(){
        notificationManager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
        //针对8.0+系统
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel  = new NotificationChannel(CHANNEL_ID,CHANNEL_NAME,NotificationManager.IMPORTANCE_LOW);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            channel.setShowBadge(false);
            notificationManager.createNotificationChannel(channel);
        }
    }

    public void startForegroundNotification(){
        service.startForeground(START_ID,mNotificationCompatBuilder.build());
    }

    public void stopForegroundNotification(){
        notificationManager.cancelAll();
        service.stopForeground(true);
    }

}

The complete code can be viewed in the Demo on github: github.com/ByteYuhb/an…

Summarize:

This article is the fourth part of component-based development, and the package of the third functional component has been uploaded to Github, and the packages of other components will be recommended later.

Component development.png

Guess you like

Origin juejin.im/post/7121643256495996936