前两篇《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的项目,上图是完成后的整个目录
新建一个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
}
}
新建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)
}
}
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()
}
}
上面是重启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
)
}
}
https://github.com/Vaccae/AppRestartDemo.git
完