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

学更好的别人,

做更好的自己。

——《微卡智享》

8610357fec5e7e2811f29988f268b1df.png

本文长度为4832,预计阅读8分钟

前言

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

a4328ba830d6eed24621aaa2b5bf1c1c.png

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

A

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

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

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

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

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

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

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

实现效果

f68e05ed5e90d7a074e80605930a5adf.gif

上面是手动点击实现的效果,主要是看到开启多个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重启所能遇到的问题,上面做了一个解答,接下来就来进行代码实现。

代码实现

1c415e5dc6626a313eab987bd9e6e78d.png

微卡智享

cd27b7e50a5356a8116816c5ff981b03.png

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

01

创建Activity栈堆

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

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列表中加入和移除。

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)
    }
}

aa5e2346b30a861d48226a9d8b10cc05.png

4eb69f8ac2cdf3cbb407e4f11c2cbd33.png

03

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

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 类

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()
    }
}

04

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

e4b7f9a1810a725ae62e03d8e04daa57.png

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

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
        )
    }


}

1e7417257e60a9194ba39f1e281d8f54.png

微卡智享

源码地址

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

点击原文链接可以看到“码云”的源码地址

f6d183ddfd0054c28389a794a5bcfa8c.png

b11107a6cc61a5c386087df558bff687.png

往期精彩回顾

 

a394be39fc736e467090233c83bd125f.png

Android内存篇(二)---JVMTI在Anroid8.1下的使用

 

 

052217d451b45dcfbc0acf60b69eaf03.png

Android内存篇(一)---使用JVMTI监控应用

 

 

dfdaefcbd751bd0fd201e9428e3c6ec4.png

Android中关于OOM的捕获的方法

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值