前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android内存篇(三)----自动重启APP实现内存兜底策略

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

作者头像
Vaccae
发布2022-05-25 09:06:07
9310
发布2022-05-25 09:06:07
举报
文章被收录于专栏:微卡智享

前言

前两篇《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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 实现效果
      • 代码实现
        • 01创建Activity栈堆
        • 02创建BaseActivity的类
        • 03创建Work任务,实现定时检测
        • 04BaseApp中加入重启和开启循环任务
      • 源码地址
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档