android性能优化实践与总结(包含启动,内存优化)

2023-05-16

应用中性能优化实践与总结(精心总结)

任何优化都需要进行检测,以数据说话,优化前和优化后有了怎样的提升

[TOC]

启动优化

检测启动时间

检测工具任选其一

  1. hugo 插件 ,
  2. 自己定义时间开始和结束手动计算时间.
  3. AOP 工具 AspectJ

adb的am start命令启动Activity测量耗时

从点击应用的启动图标開始创建出一个新的进程直到我们看到了界面的第一帧, 这段时间就是应用的启动时间

adb shell am start -W 包名/.第一个activity

冷启动

Activi
TotalTime: 1074
WaitTime: 1075
Complete

热启动

TotalTime: 115
WaitTime: 117
Complete

优化后冷启动


TotalTime: 773
WaitTime: 774
Complete


热启动

TotalTime: 114
WaitTime: 115
Complete

注意有个点

1、ThisTime:一般和 TotalTime 时间一样。除非在应用启动时开了一个透明的
Activity 预先处理一些事再显示出主 Activity,这样将比 TotalTime 小。

2、TotalTime:应用的启动时间。包含创建进程+Application 初始化+Activity 初
始化到界面显示。

3、WaitTime:一般比 TotalTime 大点,包含系统影响的耗时。

android studio 中利用Displayed 过滤关键字检测

查看优化后数据

Displayed com.sinochem.www.car.owner/.activity.WelcomeActivity: +1s104ms

hugo依赖库测量耗时

使用方法

  1. 项目根目录build.gradle添加hugo插件依赖
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
  1. 主工程或者library的录build.gradle中声明hugo插件。
apply plugin: 'com.jakewharton.hugo'

注意在每个lib里面都要加上 你想在那个lib里面打印就在那个lib里面加上这句
3. 在类或方法上声明@DebugLog注解。


    @Override @DebugLog
    public void onCreate() {
        super.onCreate();
}

项目中使用

在MyApplication 的onCreate 上面加上注解

效果

2021-01-31 15:03:01.871 26943-26943/com.sinochem.www.car.owner V/MyApplication: ⇢ onCreate()
2021-01-31 15:03:02.161 26943-26943/com.sinochem.www.car.owner V/MyApplication: ⇠ onCreate [289ms]

过滤关键字就是在哪个方法上面加上的DebugLog.比如在onCreate方法上加的, 过滤关键字就是onCreate.

在打印的列表搜索方法名称,就可以知道该方法的运行耗时。

TODO注解 打点检测时间

自己写一个时间检测的工具类- 使用自定义注解实现 AOP模式



启动优化方案

1.异步线程优化方案:

在MyApplication 初始化一些第三方库的时候使用线程池异步加载

举例:

 ThreadManager.getDownloadPool().execute(new Runnable() {
            @Override
            public void run() {
                //屏幕适配
                configUnits();
//                mCounDownLatch.countDown();
            }
        });

原来的时间:

2021-01-31 15:03:01.871 26943-26943/com.sinochem.www.car.owner V/MyApplication: ⇢ onCreate()
2021-01-31 15:03:02.161 26943-26943/com.sinochem.www.car.owner V/MyApplication: ⇠ onCreate [289ms]

效果:

2021-01-31 16:46:45.652 26568-26568/com.sinochem.www.car.owner V/MyApplication: ⇢ onCreate()
2021-01-31 16:46:45.882 26568-26568/com.sinochem.www.car.owner V/MyApplication: ⇠ onCreate [229ms]

优化了60多 毫秒.

主线程需要异步线程的结果

可以采用方案 CountDownLauch 同步计数器,同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信

不仅可以用在这里. 也可以用在其他地方.

比如在第一个页面就需要异步的结果,就可以使用 CountDownLauch

countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

使用方法:

  1. 创建
 private CountDownLatch mCounDownLatch = new CountDownLatch(1);

这里的1 是指有几个地方用到了,也就是说异步任务有几个需要主线程确定执行完毕的.

  1. 使用
   ThreadManager.getDownloadPool().execute(new Runnable() {
            @Override
            public void run() {
                //屏幕适配
                configUnits();
                mCounDownLatch.countDown();
            }
        });

//在代码的最后 也就是需要确定的地方加上这个
   try {
            mCounDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

这样就形成了 必须等屏幕适配结束 才继续.
可以看到运行时间289ms

2.延迟加载方案

2.1 常规实现

在 MainActivity#OnCreate 执行一个 postDelayed(Runnable r, long delayMillis)

Handler.postDelay()不能确定具体需要 delay 的时间.
如果时间设置过短,那么此时 UI 还没渲染完毕,这势必会阻塞到 UI 的渲染,如果过长,那么又导致延迟时间变长了。

2.2 闲时机制方案

IdleHandler(闲时机制) 处理延迟加载

IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。

源码位置:MessageQueue.IdleHandler

自建一个队列保存任务

//LinkedList 实现了Deque 接口 Deque接口继承了Queue 所以可以这样创建对象
    private Queue<Runnable> delayTasks = new LinkedList<>();

使用

    private MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            LogUtils.d("queueIdle调用");
            if (delayTasks.size() > 0) {
                LogUtils.d("从队列取出");
                Runnable runnable = delayTasks.poll();
                if (runnable != null) {
                    runnable.run();
                }
            }
            return !delayTasks.isEmpty();
            //delayTasks非空时返回ture表示下次继续执行
            //为空时返回false系统会移除该IdleHandler不再执行
        }
    };

2.3 使用启动器

TODO 写一个启动器处理启动任务

AppStartFaster:

AppStartFaster 项目 主要是通过 ClassName 找到相应的 Task,

阿里 alpha项目 :

是通过 taskName 找到相应的 Task,并且需要指定 ITaskCreator。两种方式各有优缺点,具体看使用场景。

3.优化应用启动时的体验白屏问题

对于应用的启动时间,仅仅能是尽量的避免一些耗时的、非必要的操作在主线程 中,这样相对能够缩减一部分启动的耗时,另外一方面在等待第一帧显示的时间
里,能够增加一些配置以增加体验,比方增加 Activity 的 background,这个背 景会在显示第一帧前提前显示在界面上。

单独做个主题 也可以做个透明的样式 都可以我这里做了图片

  1. 替换背景图
 <style name="AppTheme.welcome" parent="@style/Theme.AppCompat.Light.NoActionBar">
       <item name="android:windowBackground">@android:color/transparent</item>
   </style>
  1. 透明的样式
<style name="AppTheme.welcome" parent="@style/Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

然后可以在下一个页面恢复样式

Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme); super.onCreate(savedInstanceState);

4.其他优化方式

  1. 在 Application 的构造器方法、attachBaseContext()、onCreate()方法中不要 进行耗时操作的初始化,一些数据预取放在异步线程中,能够採取 Callable 实现。

  2. 对于 sp 的初始化,由于 sp 的特性在初始化时候会对数据所有读出来存在内 存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于 这个还是须要放在异步线程中处理。

  3. 对于 MainActivity,由于在获取到第一帧前。须要对 contentView 进行測量 布局绘制操作,尽量降低布局的层次。考虑 StubView 的延迟载入策略。当然在 onCreate、onStart、onResume 方法中避免做耗时操作。

  4. 不创建全局静态对象,而是转为单例模式,其中应用仅在第一次访问对象时初始化它们。此外,考虑使用依赖注入框架(如 Dagger),它们会在首次注入时创建对象和依赖项。

5. 其他注意点

  • Application重复启动

多进程任务导致Application重复启动

解决方案:

把下面代码放到MyApplication开头

 //多进程会重复调用
        String curProcessName = getCurrentProcessName(this);
        if (!curProcessName.equals(getPackageName())) {
            return;
        }
/**
     * 获取当前的进程名
     *
     * @param context:上下文
     * @return :返回值
     */
    public String getCurrentProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps == null) {
            return null;
        }
        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }

StrictMode 工具

StrictMode主要检测两大问题:线程策略(TreadPolicy)和VM策略(VmPolicy)。

ThreadPolicy线程策略:

自定义的耗时调用,使用detectCustomSlowCalls()开启;
磁盘读取操作,使用detectDiskReads()开启;
磁盘写入操作,使用detectDiskWrites()开启;
网络操作,使用detectNetwork()开启。

VmPolicy虚拟机策略:
Activity泄漏,使用detectActivityLeaks()开启;
未关闭的Closable对象泄漏,使用detectLeakedClosableObjects()开启;
泄漏的Sqlite对象,使用detectLeakedSqlLiteObjects()开启;
检测实例数量,使用setClassInstanceLimit()开启。

注意:我们只需要在 app 的开发版本下使用 StrictMode,线上版本避免使

启动优化结果

经过以上优化结果 adb检测 从1074ms ,优化到773ms.Application的onCreate检测 从288ms 优化到220ms.

内存优化

表现形式:

  1. 内存抖动 : 锯齿状,GC导致卡顿
  2. 内存泄漏 : 可用内存减少,频繁GC
  3. 内存溢出 : OOM, 程序异常

检测工具:

memory profiler

android studio 自带的. 找不到profile 的话 顶部导航栏

  1. 点击下载样式的按钮可以找到当前的内存

这样可以看到当前内存是哪里消耗的最多,还有一些其他的内存信息, 还可以看一个bitmap的预览图

使用ActivityManager

ActivityManager有一个MemoryInfo对象,可以进行进程的内存获取;同时我们需要RunningAppProcessInfo获取到当前系统正在运行的进程的集合,然后去遍历这个集合,根据每个元素的processName(包名)字段去筛选我们需要获取内存信息的进程,然后通过ActivityManager的getProcessMemoryInfo方法去获取到这个进程的内存值,通过换算得到进程的内存占用信息;然后将这个进程的各项信息,比如包名,PID,内存占用等信息

LeakCanary

https://square.github.io/leakcanary/getting_started/

大致原理

监听Activity生命周期->onDestroy以后延迟5秒判断Activity有没有被回收->如果没有回收,调用GC,再此判断是否回收,如果还没回收,则内存泄露了,反之,没有泄露。没有回收的话会dump 内存 然后去分析

引入:

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

arthook方式,epic 库

对bitmap检测不合理

支持ART上的Java方法HOOK. 使用 epic 库

hok 住 imageview 的setImageBitmap 即可

https://github.com/tiann/epic

  1. 引入
implementation 'me.weishu:epic:0.11.0'
  1. hook之后的操作 找到imageview判断和bitmap是否合理设置
public class ImageHook extends XC_MethodHook {

    /**
     * hook方法之后的一个回调
     *
     * @param param
     * @throws Throwable
     */
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        //实现逻辑
        checkBitmap(param);
        
    }

    private void checkBitmap(MethodHookParam param) {
//        DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new XC_MethodHook() {
//            @Override
//            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//                super.afterHookedMethod(param);
        if (param.thisObject instanceof ImageView) {
            final ImageView imageView = (ImageView) param.thisObject;
            if (imageView.getDrawable() instanceof BitmapDrawable) {
                BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
                final Bitmap bitmap = drawable.getBitmap();
                if (bitmap != null) {
                    //bitmap 宽高
                    int bitmapWidth = bitmap.getWidth();
                    int bitmapHeight = bitmap.getHeight();
                    //视图宽高
                    int viewWidth = imageView.getWidth();
                    int viewHeight = imageView.getHeight();
                    if (viewHeight > 0 && viewWidth > 0) {//view 有宽高
                        //当图片宽高都大于视图宽高的2倍时就报出警告
                        if (bitmapWidth >= viewWidth << 2 && bitmapHeight >= viewHeight << 2) {
                            warn(bitmapWidth, bitmapHeight, viewWidth, viewHeight);
                        }
                    } else {
                        imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw() {
                                int bitmapWidth = bitmap.getWidth();
                                int bitmapHeight = bitmap.getHeight();
                                int viewWidth = imageView.getWidth();
                                int viewHeight = imageView.getHeight();
                                if (bitmapWidth >= viewWidth << 2 && bitmapHeight >= viewHeight << 2) {
                                    warn(bitmapWidth, bitmapHeight, viewWidth, viewHeight);
                                }
                                imageView.getViewTreeObserver().removeOnPreDrawListener(this);
                                return true;
                            }
                        });
                    }
                }
            }
        }
//            }
//        });

    }

    private void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
        StringBuffer msg = new StringBuffer();
        msg.append("图片大小不合理:")
                .append("bitmapWidth=").append(bitmapWidth)
                .append(",bitmapHeight=").append(bitmapHeight)
                .append(",viewWidth=").append(viewWidth)
                .append(",viewHeight=").append(viewHeight);
        //不合理
        LogUtil.e((new Throwable(msg.toString())));
    }

}
  1. 开启hook 注入
 //方式1 hook方式 注册到setImageBitmap 里面去
            DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());

用的时候minSdkVersion 21

用的时候无法注入.

检测图片的不合理-通过遍历ViewTree的的办法 (我采用的这种方案)

简单原理:
实时检查View中的BitmapDrawable,判断bitmap大小和view图片的大小是否匹配

  1. 注册 Application 的 registerActivityLifecycleCallbacks 方法,传入自己的lifecycleCallbacks
  2. onActivityCreated 方法内 对 ViewTreeObserver 进行addOnGlobalLayoutListener 并实现OnGlobalLayoutListener 可获得宽度或者高度(OnGlobalLayoutListener当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类)
  3. 进行递归遍历直到遍历到ImageView,同时获取Background的bitmap.
  4. 对比bitmap 和ImageView 计算出比例
float scale = Math.max(bitmap.getHeight()*1.0f/view.getHeight(),bitmap.getWidth()*1.0f/view.getWidth());
  1. 如果比例大于1.5倍则给出警告.

优化方案

针对内存泄漏

  1. 集合类
  2. Static关键字修饰的成员变量
  3. 非静态内部类 / 匿名类
  4. 资源对象使用后未关闭

集合类

  • 内存泄露原因 集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏

解决办法: 由于1个集合中有许多元素,故最简单的方法 = 清空集合对象 & 设置为null

Static关键字修饰的成员变量

  • 储备知识
    被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期
  • 泄露原因
    若使被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露

解决办法:

  1. 尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context,若需引用 Context,则尽量使用Applicaiton的Context
  2. 使用 弱引用(WeakReference) 代替 强引用 持有实例)

非静态内部类 / 匿名类

  • 静态成员变量有个非常典型的例子 = 单例模式
  • 储备知识 单例模式 由于其静态特性,其生命周期的长度 = 应用程序的生命周期(泄露原因 若1个对象已不需再使用 而单例对象还持有该对象的引用,那么该对象将不能被正常回收 从而 导致内存泄漏)

解决方案 单例模式引用的对象的生命周期 = 应用的生命周期

  • 储备知识 非静态内部类 / 匿名类 默认持有 外部类的引用;而静态内部类则不会
    常见情况 3种,分别是:非静态内部类的实例 = 静态、多线程、消息传递机制(Handler)

解决方案:

将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)

该内部类抽取出来封装成一个单例

尽量 避免 非静态内部类所创建的实例 = 静态

Handler 内存泄露

使用leakcannry检测到内存泄漏后分析.一般造成内存泄漏的原因有以下几种

储备知识

  • 主线程的Looper对象的生命周期 = 该应用程序的生命周期
  • 在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用

举例:

 //1. 实例化自定义的Handler类对象-
                //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
             private Handler handler; = new Handler();

在Handler消息队列 还有未处理的消息 / 正在处理消息时,消息队列中的Message持有Handler实例的引用


1、资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap
等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

2、注册对象未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

3、类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

4、单例造成的内存泄漏
优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封
装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

5、非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源
不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如
果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置
空让GC可以回收,否则还是会内存泄漏。

6、Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引
用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,
则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息,
当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message
持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回
收,引发内存泄漏。解决方案如下所示:

1、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这样在回收时,也可以回收Handler持有的对象。

2、在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。需要注意的是,AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。对于类似AsyncTask或是线程造成的内存泄漏,我们也可以将AsyncTask和Runnable类独立出来或者使用静态内部类。

7、容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

8、WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为
WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业
务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

9、使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView。

针对内存抖动

查看: 主要查看memoryProfiler里面的内存是否总是频繁回收

解决办法:
主要查看是否有循环创建清空对象的地方.

  • 尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
  • 注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
  • 当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用。
  • 对于能够复用的对象,同理可以使用对象池将它们缓存起来。

针对bitmap过大或不合理

https://github.com/smallnew/BitmapCanary

使用bBitmapCanary检测后,对图片进行处理

  1. 使用低色彩的解析模式,如 RGB565,减少单个像素的字节大小
  2. 资源文件合理放置,高分辨率图片可以放到高分辨率目录下
  3. 图片缩小,减少尺寸

针对加载gif图

https://juejin.cn/post/6854573219425288199

OOM

onTrimMemory 与 onLowMemory Android 系统的每个进程都有一个最大内存限制,如果申请的内存资源超过这个 限制,系统就会抛出 OOM 错误。 onTrimMemory 所以在实际开发过程中我们要尽可能避免内存泄漏与内存抖动之外,还要格外注 意内存使用情况。根据《Manage Your App’s Memory》,
我们可以对内存的状 态进行监听,我们的 Application、Acivity、Service、ContentProvider 与 Fragment 都实现了 ComponentCallbacks2 接口。所以能够重写 onTrimMemory 与 onLowMemory 函数。

onLowMemory 这个函数看名字就是低内存。这个函数的回调意味着后台进程已经被干掉了。这 个回调可以作为 4.0 兼容 onTrimMemory 的 TRIM_MEMORY_COMPLETE 来使用 如果希望在其他组件中也能接收到这些回调可以使用上下文的 registerComponentCallbacks 注册接收, unRegisterComponentCallbacks 反注册

在Application的onTrimMemory(level)

内存紧张时停止一些非必要功能,如关闭推送进程,关闭后台service等


    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        switch (level) {
            case TRIM_MEMORY_COMPLETE:
                //内存不足,并且该进程在后台进程列表最后一个,马上就要被清理
                LogUtil.d("内存不足,并且该进程在后台进程列表最后一个,马上就要被清理");
                break;
            case TRIM_MEMORY_MODERATE:
                //内存不足,并且该进程在后台进程列表的中部
                LogUtil.d("内存不足,并且该进程在后台进程列表的中部");
                break;
            case TRIM_MEMORY_BACKGROUND:
                //内存不足,并且该进程是后台进程
                LogUtil.d("内存不足,并且该进程是后台进程");
                break;
            case TRIM_MEMORY_UI_HIDDEN:
                //内存不足,并且该进程的UI已经不可见了。
                LogUtil.d("内存不足,并且该进程的UI已经不可见了");
                break;
            case TRIM_MEMORY_RUNNING_CRITICAL:
                //内存不足(后台进程不足3个),并且该进程优先级比较高,需要清理内存
                LogUtil.d("内存不足(后台进程不足3个),并且该进程优先级比较高,需要清理内存");
                break;
            case TRIM_MEMORY_RUNNING_LOW:
                //内存不足(后台进程不足5个),并且该进程优先级比较高,需要清理内存
                LogUtil.d("内存不足(后台进程不足5个),并且该进程优先级比较高,需要清理内存");
                break;
            case TRIM_MEMORY_RUNNING_MODERATE:
                //内存不足(后台进程超过5个),并且该进程优先级比较高,需要清理内存.
                LogUtil.d("内存不足,(后台进程超过5个),并且该进程优先级比较高,需要清理内存");
                break;
        }
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        //被回调时,已经没有后台进程
        //通过一键清理后,OnLowMemory不会被触发,而OnTrimMemory会被触发一次。
        LogUtils.d("内存不够了");
    }

上述系统提供的API,还可以自己实现ComponentCallbacks,通过API注册,这样也能得到OnLowMemory回调。例如:

public static class MyCallback implements ComponentCallbacks { 
        @Override
        public void onConfigurationChanged(Configuration arg) { 
        }

        @Override
        public void onLowMemory() {
            //do release operation
        }
    }

然后,通过

Context.registerComponentCallbacks()

在合适的时候注册回调就可以了。通过这种自定义的方法,可以在很多地方注册回调,而不需要局限于系统提供的组件。

  • 如果内存紧急了,直接清空 UIL/Fresco/glide 的内存缓存救急

线上监控

后台监控中采用garylog ,客户端就直接用post请求发送一些异常日志.

  1. 目前有做的是Crash后记录异常信息保存本地后上传服务器.(实现Thread.UncaughtExceptionHandler 接口)
  2. 一些自己做的 try catch 的异常信息
    • 未来要做的内存泄漏,内存消耗过大监控

这里择机发送日志需要注意几个方面:

  1. 需要有兜底的策略,不能一次性全部发过去,最多如果发送失败三次.则下次启动再发送 可以采用队列方式
  2. 不能影响正常业务.需要把异常上传信息独立出来

其他优化方案

  1. webview 依赖的activity销毁后需要调用mWebview.destroy();才能释放资源
  2. webview 启用独立进程 - (治标不治本,后续处理事情多,需在有时间时完成)
  • web进程 -> app主进程 通过aidl实现
  • app主进程 -> 回调到web进程 也是通过aidl接口实现
  • 广播本身跨进程的
  • 为了让组件中供web调用的 JsInterface 与业务分离,增加了一个H5Bridge.getInstance().register(JsInterfaceImpl.class)
  1. 关闭activiy后遍历view,清空所有ImageView中的图片的内存

内存优化结果

Chrash现象: 之前出现的兼容性问题主要出现在内存消耗峰值过高,以及存在内存泄漏情况.

检测方案: 采用技术工具检测到内存消耗过大的原因有几个方面.

  1. 大图片加载 和实际控件宽高不匹配
  2. gif图加载
  3. 3.代码存在内存泄漏的写法.

解决办法: 针对上面问题解决办法如下

  1. 采用加载图片的款号和实际ImageView控件的宽高做比对压缩,
  2. 采用底层C++库加载gif图片 .
  3. 修复代码存在内存泄漏的写法.

优化结果: 从1.1.0版本 标准兼容性测试开始通过率72%左右. 到现在通过率95%以上. 内存峰值消耗由原来300M+ 到现在稳定220M左右.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android性能优化实践与总结(包含启动,内存优化) 的相关文章

  • Socket网络通信C++编程总结

    概述 Socket编程有三种 xff0c 流式套接字 xff08 SOCK STREAM xff09 数据报套接字 SOCK DGRAM 原始套接字 SOCK RAW 前两者较常用 xff0c 这里简单总结前两种编程步骤以及一些参考资料 编
  • 【STM32+cubemx】0029 HAL库开发:HMC5883L磁力计的应用(电子指南针)

    今天我们来学习电子磁力计HMC5883L的使用 先介绍磁力计的基础知识 xff0c 再给一个获取磁力计数据的例子 xff0c 最后讲解HMC5883L磁力计的校准 xff0c 以及一些使用中的经验 1 xff09 HMC5883L磁力计的基
  • stm32F103R6之BKP(备份寄存器)

    目录 概述 侵入检测 RTC校准 概述 Stm32F103有42个16位的备份寄存器 他们处在备份域里 xff0c 当VDD电源被切断 xff0c 他们仍然由VBAT维持供电 当系统在待机模式下被唤醒 xff0c 或系统复位或电源复位时 x
  • Stm32F103R6之控制器局域网

    目录 概述 双CAN bxCAN工作模式 CAN协议特点 xff1a ISO11898标准CAN物理特性 CAN协议帧 概述 bxCAN是基本扩展CAN Basic Extended CAN 的缩写 xff0c 它支持CAN协议2 0A和2
  • 如何输出一个数的二进制数

    实现思想 xff1a 二进制数是以bit为操作数 xff0c 所以要想将一个数转换为二进制数 xff0c 我们需要先要将输入的数字转化为二进制数 xff0c 然后从高位到低位判断每一位是 1 还是 0 xff0c 最后把对应的 1 和 0
  • 使用libcurl库编写HTTP客户端(包括GET/POST/HTTPS)

    最近在写一个应用程序 需要与HTTP服务器进行数据交互 于是乎自己写了一个类似wget的功能的客户端 实现很简单 但是功能不给力 只可基本功能 于是又在网上找了找 发现使用libcurl库很方便 很强大 比起wget之类的 强大不是一点点
  • 修改系统默认shell为bash

    xfeff xfeff 从 ubuntu 6 10 开始 xff0c ubuntu 就将先前默认的bash shell 更换成了dash shell xff1b 其表现为 bin sh 链接倒了 bin dash而不是传统的 bin bas
  • 如何解决Reporting Services目录数据库文件存在的问题

    出处 xff1a http blog sina com cn s blog 6bace3cc0101jlxv html 错误提示 xff1a 自检时提示 Reporting Services目录数据库文件存在 失败 xff0c Report
  • 无线射频专题《射频合规,2.4GHz WIFI测试指标详解》

    目录 引言 Transmitter Power 发送功率 Transmit Spectrum Mask 发送信号频谱模版 Frequency Error 频率误差 EVM 矢量误差幅度 Band Edges and harmonics 频带
  • 获取当前访问的路径

    String returl 61 request getRequestURL 43 request getQueryString 61 61 null 34 34 34 34 43 request getQueryString
  • 抓取百度关键词排名

    最近在做百度关键词排名的功能 xff0c 发现网上资源比较少 xff0c 于是自己琢磨了一下 xff0c 写一下笔记 xff1b 本文重点在于提供思路 xff0c 请不要过分依赖 xff0c 本文主要靠抓取页面标签来完成 xff0c 如果百
  • 抓取百度关键词排名、标题、连接、描述

    抓取百度关键词排名 标题 连接 描述 转载请标明出处 最近在做百度关键词排名的功能 xff0c 发现网上资源比较这里写代码片少 xff0c 于是自己琢磨了一下 xff0c 写一下笔记 xff1b 本文重点在于提供思路 xff0c 请不要过分
  • Windows Server 2008远程桌面端口的修改

    Windows Server 2008远程桌面端口系统默认的是3389端口 xff0c 但出于安全考虑 xff08 谁都不希望任何都可以远程连接到自己的服务器吧 xff0c 哈哈 xff09 xff0c 经常我们把系统默认的3389端口更改
  • jsp 登陆成功后,显示登录的用户名

    首先在登陆界面将用户名保存起来 xff0c 我这里是将用户名提交到Servlet然后再进行保存 xff0c 从登录界面取用户参数 String uname 61 request getParameter 34 userName 34 req
  • 背景图片随网页的变化而变化(指大小)

    lt DOCTYPE HTML PUBLIC 34 W3C DTD HTML 4 01 Transitional EN 34 gt lt html xmlns 61 34 http www w3 org 1999 xhtml 34 gt l
  • 为什么vscode用久了电脑速度变慢?

    1 vscode 插件占用的内存 1 已经安装的插件 2 插件占用内存 我可以看到 xff0c vs code 这个程序下面有多个进程在跑 xff0c 插件占用的内存比我电脑剩下所有占用的内存还要高 xff0c 但是vs code 性能还是
  • ROS基础学习(一)---创建工作空间

    工作空间 xff08 workspace xff1a 是一个存放工程开发相关文件的文件夹 src xff1a 代码空间 xff08 Source Space build 编译空间 xff08 Build Space devel 开发空间 x
  • 一个简单的爬虫程序,爬取网站的图片

    最简单的爬虫是分析网页 xff0c 如果要爬取图片 xff0c 就要将图片在网页中的格式进行分析 xff0c 取到图片的连接 xff0c 接着下载图片 xff1b 由于网页中还会链接到其他的网页 xff0c 所以需要将其中的所有网页取出 x
  • python文件操作及引申的代码行数统计

    文件操作一般包括文件的读写 xff0c 文件夹的创建 xff0c 文件夹的删除等 第一部分 xff1a python文件读写的基本操作 python打开文件一般使用 open函数 xff1a open file mode 61 39 r 3
  • 超声波测距仪

    基本原理 超声波是利用反射的原理测量距离的 xff0c 被测距离一端为超声波传感器 xff0c 另一端必须有能反射超声波的物体 测量距离时 xff0c 将超声波传感器对准反射物发射超声波 xff0c 并开始计时 xff0c 超声波在空气中传

随机推荐

  • Authorization 值中Bearer空格加token值在python接口请求中如何实现

    在项目中每个接口请求都需要Authorization 值 xff0c 而Authorization他的值必须 Bearer 加token值 xff0c 刚开始自己忘记添加Bearer 接口请求一直拒绝访问 xff0c 后来用fiddler抓
  • printf格式化字符串漏洞原理解析

    读任意地址 printf 34 x 34 只给格式化字符串 xff0c 而不给参数 xff0c 会导致内存泄漏从而读到内存中其他地址的数据 N x参数可以以16进制方式打印第N个参数的内容 xff0c 通过修改N xff0c 我们可以遍历栈
  • 教程丨利用微软官方工具制作U盘安装Win10系统

    一 制作Win10安装U盘 1 登录网站 https www microsoft com zh cn software download windows10 下载 MediaCreationTool 工具 xff0c 这里我们直接点击 立即
  • Linux常用命令大全

    发布jar包 nohup java jar xxxx jar gt dev null 2 gt amp 1 amp 修改nginx conf 后刷新配置 usr local nginx sbin nginx t 测试配置文件修改是否正常 u
  • Zab协议详解

    什么是Zab协议 xff1f Zab协议 的全称是 Zookeeper Atomic Broadcast xff08 Zookeeper原子广播 xff09 Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性 Zab协议是
  • 谷歌浏览器安装json格式化插件

    实际开发工作中经常用到json数据 xff0c 那么就会有这样一个需求 xff1a 在谷歌浏览器中访问URL地址返回的json数据能否按照json格式展现出来 比如 xff0c 在谷歌浏览器中访问 xff1a http jsonview c
  • Seata详解(一)

    分布式事务 事务是数据库的概念 xff0c 数据库事务 xff08 ACID xff1a 原子性 一致性 隔离性和持久性 xff09 xff1b 分布式事务的产生 xff0c 是由于数据库的拆分和分布式架构 微服务 带来的 xff0c 在常
  • RocketMQ的事务消息和改造

    什么是 rmq分布式事务消息 xff1f Apache RocketMQ在4 3 0版中已经支持分布式事务消息 xff0c 这里RocketMQ采用了2PC的思想来实现了提交事务消息 xff0c 同时增加一个补偿逻辑来处理二阶段超时或者失败
  • session和token区别

    一 session的状态保持及弊端 xff08 1 xff09 当用户第1次通过浏览器使用用户名和密码访问服务器时 xff0c 服务器对用户名和密码进行验证 xff08 2 xff09 验证成功后 xff0c 在服务器端生成并保存sessi
  • 关于阿里云对RocketMQ的topic,tag,consumer关系的理解

    什么是订阅关系一致 消息队列RocketMQ版 阿里云帮助中心 RocketMQ 中Topic Tag GroupName基本概念介绍 Young丶的博客 CSDN博客 rocketmq topic和group的区别 转载
  • MySql Workbench 8.0汉化插件分享

    打开workbench的安装数据目录 xff0c 路径是 xff1a C Program Files MySQL MySQL Workbench 8 0 CE data 打开以后 xff0c 可以看到下面有一堆的xml结尾的文件 xff0c
  • Java-Jdk12版本下载后没有Jre的解决方案以及配置环境变量

    新本Jdk12版本下载后不像以前的版本会自动生成Jre文件夹 xff0c 通过dos命令行可以解决 1 下载jdk12 网址 xff1a https www oracle com technetwork java javase downlo
  • android--性能优化1--首屏优化&启动速度与执行效率检测

    文章目录 实战篇traceview 工具使用操作步骤操作步骤2 systrace 工具使用操作步骤 AOP 工具 hugo 的使用使用方法效果 BlockCanary 使用AOP 工具使用 AspectJ操作步骤 优化方案 异步线程优化 针
  • error Failed connect to github.com 443原因 Github更换认证方式

    Github更换认证方式 github近期调整了认证方式 xff0c 不再允许第三方工具基于账号密码来访问和管理项目了 除非使用github DeskTop 自带的客户端 好坑啊 我一直再用android studio 自带的提交工具很方便
  • AppCompatRatingBar备份用 下次拷贝直接用自定义图片背景

    控件 lt androidx appcompat widget AppCompatRatingBar android id 61 34 64 43 id rating bar 34 style 61 34 64 style myRating
  • 备份Edittext编辑框字数限制ui以及逻辑

    ui布局 etContent addTextChangedListener new TextWatcher 记录输入的字数 private CharSequence enterWords private int selectionStart
  • android—性能优化2—内存优化

    文章目录 性能优化 工具 memory profilerLeakCanaryarthookepic 库 java内存管理机制java 内存回收机制Android内存管理机制Dalvik与 Art区别Low Memory Killer 内存抖
  • glide4.11.0封装gfilib优化gif图片加载

    文章目录 具体步骤 下载giflib 和 framesequence导入并集成 giflib 和 framesequenceglide的配置gif使用giflib集成开始使用加载思路创建自定义GifDrawable 需求 目前项目中加载进度
  • java筑基.泛型,反射,注解-利用注解加反射练习

    文章目录 泛型 泛型类泛型方法泛型接口子类明确泛型类的类型参数变量子类不明确泛型类的类型参数变量 限定类型变量通配符泛型 注解元注解注解的应用场景 反射 注解 43 反射练习 泛型 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特
  • android性能优化实践与总结(包含启动,内存优化)

    应用中性能优化实践与总结 精心总结 任何优化都需要进行检测 以数据说话 优化前和优化后有了怎样的提升 TOC 启动优化 检测启动时间 检测工具任选其一 hugo 插件 自己定义时间开始和结束手动计算时间 AOP 工具 AspectJ adb