Android内存篇(三)----自动重启APP实现内存兜底策略

2022-05-25 09:06:07 浏览数 (1)

前言

前两篇《Android内存篇(一)---使用JVMTI监控应用》《Android内存篇(二)---JVMTI在Anroid8.1下的使用》主要说的是内存监控,本章做为内存的第三篇,主要介绍的是有效解决问题的方法---内存兜底策略。说起内存兜底策略,用人话讲就是在用户不知情的情况下,自动重启APP,这样可以解决软件在触发系统异常前,选择合适的时间重启,使内存回到正常情况。

执行内存兜底策略的条件?

A

执行内存兜底策略,一般来说要满足下面六个条件:

1)是否在主界面退到后台且位于后台时间超过30分钟。

2)当前时间为早上2点到5点前。

3)不存在前台服务(通知栏、音乐播放栏等情况)。

4)Java heap必须 大于当前进程可分配的85%或者是native内存大于800MB。

5)vmsize超过了4G(32Bit)的85%。

6)非大量的流量消耗(不超过1M/min)并且进程无大量CPU调度情况。

实现效果

上面是手动点击实现的效果,主要是看到开启多个Activity可以正常全部关闭重启,定时任务也做在了里面。

微卡智享

实现App自动重启的思路

上面说了几点App自动重店的思路,在具体的代码实现中呢,也要考虑遇到的问题和使用的什么方式进行处理。

怎么实现凌晨2点到5点间执行重启?

A

采用Work的组件时间,创建一个每15分钟的循环任务检测是否在时间段内,如果在时间段内并且App在闲置状态,实现重启,如果是正在使用的状态则自动跳出等待下一个15分钟检测。

考虑怎么实现当天只重启一次?

A

采用SharedPreferences组件,当App成功后,记录的重启时间为明天的2点,这样每次检测重启时,当前时间小于记录的下次重启时间,也直接跳出。

如何实现App自动重启?

A

通过AlarmManager(闹钟服务)实现App启动,即判断进入重启后,设置一个2秒后的AlarmManager用于开启App,同时执行关闭当前进程的方法,关闭当前进程两个方法:

android.os.Process.killProcess(android.os.Process.myPid())

System.exit(0)

当有多个Activity时,关闭当前进程也只关闭当前的Actvity重启怎么办?

A

如果只单一Activity的话,那直接用上面的关闭进程就可以实现了,但往往App中不会只有一个Activity,所以我们要建一个ActivityStack的类,用于存放活动的Activity的列表,当关闭当前进程时,需要将所有活动的Activity全部关闭后再执行重启。

初步关于App重启所能遇到的问题,上面做了一个解答,接下来就来进行代码实现。

代码实现

新建了一个AppRestart的项目,上图是完成后的整个目录

01创建Activity栈堆

新建一个ActivityStack的类,里面加入activity的集合,和创建,移除,清空等方法。

代码语言:javascript复制
package pers.vaccae.apprestart

import android.app.Activity
import android.util.Log

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:11:34
 * 功能模块说明:
 */
object ActivityStack {
    private const val TAG = "ActivityStack"

    //当前活动的Activity列表
    private val activities: MutableList<Activity> = arrayListOf()

    //添加活动Activity
    @JvmStatic
    fun addActivity(activity: Activity?) {
        try {
            activity?.let {
                if (checkActivity(it)) {
                    removeActivity(it)
                }
                activities.add(it)
            }
        } catch (e: Exception) {
            Log.e(TAG, e.message.toString());
        }
    }

    //删除活动Activity
    @JvmStatic
    fun removeActivity(activity: Activity?) {
        try {
            activity?.let {
                activities.remove(it)
            }
        } catch (e: Exception) {
            Log.e(TAG, e.message.toString());
        }
    }

    //获取当前活动的Activity
    @JvmStatic
    fun currentActivity(): Activity? {
        var activity: Activity? = null
        if (activities.size > 0) {
            activity = activities[activities.size - 1]
        }
        return activity
    }

    //关闭当前活动的Activity
    @JvmStatic
    fun finishCurActivity() {
        val activity = currentActivity()
        activity?.let {
            it.finish()
            activities.remove(it)
        }
    }

    //关闭所有的Activity
    @JvmStatic
    fun finishAllActivity() {
        for (i in activities.size - 1 downTo 0) {
            val activity = activities[i]
            activity.finish()
            activities.removeAt(i)
        }
    }

    //检查Activity是否在列表中
    @JvmStatic
    private fun checkActivity(activity: Activity?): Boolean {
        var res = false
        activity?.let {
            res = activities.contains(activity)
        }
        return res
    }

}

02创建BaseActivity的类

新建BaseActivity的类,以后创建的Activity都继承自BaseActivity,在创建和释放时自动在活动的Activity列表中加入和移除。

代码语言:javascript复制
package pers.vaccae.apprestart

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:12:52
 * 功能模块说明:
 */
open class BaseCompatActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityStack.addActivity(this)
    }


    override fun onDestroy() {
        super.onDestroy()
        ActivityStack.removeActivity(this)
    }
}

03创建Work任务,实现定时检测

代码语言:javascript复制
package pers.vaccae.apprestart

import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import java.text.SimpleDateFormat
import java.util.*

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:09:25
 * 功能模块说明:
 */
class AppRestartWork(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    private var TAG = "restart"
    private var KeyStr = "ReStartTime"

    override fun doWork(): Result {
        //检测是否可以重启
        if (isInReStartTime()) {
            if (isInTimeScope(5, 0, 8, 0)) {
                Log.i(TAG, "Success")
                val ReStartTime = setNextReStartTime(1)
                SpHelper.putString(BaseApp.mContext!!, KeyStr, ReStartTime)
                BaseApp.reStartApp()
            }
            else {
                val ReStartTime = setNextReStartTime()
                SpHelper.putString(BaseApp.mContext!!, KeyStr, ReStartTime)
            }
        }
        return Result.success()
    }

    /**指定下次启动时间
     *
     * stype 类型:1-明天的2点开始, 0-15分钟后重新开启  2-当天的2点开始
     */
    fun setNextReStartTime(stype: Int = 0): String {
        val today = Date()
        val cal = Calendar.getInstance()
        cal.time = today;
        when (stype) {
            0 -> {
                cal.add(Calendar.MINUTE, 15)
            }
            1 -> {
                cal.set(Calendar.HOUR_OF_DAY, 2)
                cal.set(Calendar.MINUTE, 0)
                cal.add(Calendar.DAY_OF_MONTH, 1)
            }
            else -> {
                cal.set(Calendar.HOUR_OF_DAY, 2)
                cal.set(Calendar.MINUTE, 0)
            }
        }
        val f = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        val res = f.format(cal.time)
        Log.i(TAG, "Calendar : ${f.format(cal.time)}")
        return res
    }


    //判断是否大于本次重启时间
    fun isInReStartTime(): Boolean {
        var res = false
        val f = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        //获取上次重启时间
        val ReStartTimeStr =
            SpHelper.getString(BaseApp.mContext!!, KeyStr, "")
        Log.i(TAG, "ReStartTime:$ReStartTimeStr")
        if (ReStartTimeStr!!.equals("")) {
            val setReStartTime = setNextReStartTime(2)
            SpHelper.putString(BaseApp.mContext!!, KeyStr, setReStartTime)
            return false
        }

        val ReStartTime = f.parse(ReStartTimeStr).time
        Log.i(TAG, "ReStartTime:$ReStartTimeStr")
        //获取当前时间
        val nowTime = System.currentTimeMillis()
        Log.i(TAG, "nowTime:${f.format(nowTime)}")
        return nowTime > ReStartTime
    }


    /**
     * 判断当前系统时间是否在指定时间的范围内
     *
     * sHour 开始小时,例如22
     * sMin  开始小时的分钟数,例如30
     * eHour   结束小时,例如 8
     * eMin    结束小时的分钟数,例如0
     * true表示在范围内, 否则false
     */
    fun isInTimeScope(sHour: Int, sMin: Int, eHour: Int, eMin: Int): Boolean {
        var res = false

        val cal = Calendar.getInstance()
        val nowHour = cal.get(Calendar.HOUR_OF_DAY)
        val nowMin = cal.get(Calendar.MINUTE)
        //获取当前时间
        val nowTime = nowHour * 60   nowMin
        Log.i(TAG, "nowTime:$nowTime")
        //起始时间
        val sTime = sHour * 60   sMin
        Log.i(TAG, "sTime:$sTime")
        //结束时间
        val eTime = eHour * 60   eMin
        Log.i(TAG, "eTime:$eTime")

        res = nowTime in sTime..eTime
        return res;
    }
}

加入的时间判断,在时间范围内重启,不在修改下一次的重启时间,当重启成功后,改为明天的2点。

使用SharedPreferences 存放数据,封装了个SpHelper 类

代码语言:javascript复制
package pers.vaccae.apprestart

import android.content.Context
import android.content.SharedPreferences

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:09:32
 * 功能模块说明:
 */
object SpHelper {
    private val spFileName = "app"

    fun getString(
        context: Context, strKey: String?,
        strDefault: String? = ""
    ): String? {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getString(strKey, strDefault)
    }

    fun putString(context: Context, strKey: String?, strData: String?) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putString(strKey, strData)
        editor.apply()
        editor.commit()
    }


    fun getBoolean(
        context: Context, strKey: String?,
        strDefault: Boolean? = false
    ): Boolean {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getBoolean(strKey, strDefault!!)
    }

    fun putBoolean(
        context: Context, strKey: String?,
        strData: Boolean?
    ) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putBoolean(strKey, strData!!)
        editor.apply()
        editor.commit()
    }


    fun getInt(context: Context, strKey: String?, strDefault: Int = -1): Int {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getInt(strKey, strDefault)
    }

    fun putInt(context: Context, strKey: String?, strData: Int) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putInt(strKey, strData)
        editor.apply()
        editor.commit()
    }


    fun getLong(context: Context, strKey: String?, strDefault: Long = -1): Long {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getLong(strKey, strDefault)
    }

    fun putLong(context: Context, strKey: String?, strData: Long) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putLong(strKey, strData)
        editor.apply()
        editor.commit()
    }

    fun getFloat(context: Context, strKey: String?, strDefault: Float = -1f): Float {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getFloat(strKey, strDefault)
    }

    fun putFloat(context: Context, strKey: String?, strData: Float) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putFloat(strKey, strData)
        editor.apply()
        editor.commit()
    }

    fun getStringSet(context: Context,strKey: String?): MutableSet<String>? {
        val setPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        return setPreferences.getStringSet(strKey, LinkedHashSet<String>())
    }

    fun putStringSet(context: Context, strKey: String?, strData: LinkedHashSet<String>?) {
        val activityPreferences: SharedPreferences = context.getSharedPreferences(
            spFileName, Context.MODE_PRIVATE
        )
        val editor = activityPreferences.edit()
        editor.putStringSet(strKey, strData)
        editor.apply()
        editor.commit()
    }
}

04BaseApp中加入重启和开启循环任务

上面是重启App的方法,完整BaseApp的代码如下:

代码语言:javascript复制
package pers.vaccae.apprestart

import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlarmManager
import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.util.Log
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:10:10
 * 功能模块说明:
 */
class BaseApp : Application() {

    companion object {
        @JvmField
        @SuppressLint("StaticFieldLeak")
        var mContext: Context? = null

        //重启APP
        @JvmStatic
        fun reStartApp(second: Int = 1) {
            mContext?.let { context->
                val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
                intent?.let {
                    //关闭所有Activity
                    ActivityStack.finishAllActivity()

                    it.putExtra("REBOOT", "reboot")
                    val restartintent = PendingIntent.getActivity(
                        context, 0, it, PendingIntent.FLAG_ONE_SHOT
                    )
                    val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
                    mgr.set(AlarmManager.RTC, System.currentTimeMillis()   second * 1000, restartintent)
                    //android.os.Process.killProcess(android.os.Process.myPid())
                    System.exit(0)
                }
            }
        }
    }

    override fun onCreate() {
        super.onCreate()

        mContext = this

        //创建WorkManager任务
        val periodicwork =
            PeriodicWorkRequestBuilder<AppRestartWork>(5000, TimeUnit.MILLISECONDS).build()
        WorkManager.getInstance(this).enqueueUniquePeriodicWork(
            "AppReStart", ExistingPeriodicWorkPolicy.REPLACE,
            periodicwork
        )
    }

}

源码地址

https://github.com/Vaccae/AppRestartDemo.git

0 人点赞