精华内容
下载资源
问答
  • Android-APP内存优化

    千次阅读 2018-04-09 14:27:10
    为什么要进行内存优化 APP运行内存限制,OOM导致APP崩溃 APP性能:流畅性、响应速度和用户体验

    为什么要进行内存优化

    1. APP运行内存限制,OOM导致APP崩溃
    2. APP性能:流畅性、响应速度和用户体验

    查看APP内存的方法和工具

    我们先获取系统服务,我们通过getSystemService传入什么参数呢?下面就先说下各个参数代表的是什么吧:

    参数名获取了什么系统服务
    WINDOW_SERVICE窗口管理器
    LAYOUT_INFLATER_SERVICE布局管理
    ACTIVITY_SERVICE活动管理
    POWER_SERVICE电源管理
    ALARM_SERVICE闹钟管理
    NOTIFICATION_SERVICE通知栏管理
    KEYGUARD_SERVICE屏幕保护管理
    LOCATION_SERVICE位置管理
    SEARCH_SERVICE搜索管理
    VIBRATOR_SERVICE手机震动
    CONNECTIVITY_SERVICE网络连接管理
    WIFI_SERVICEwifi管理
    WIFI_P2P_SERVICE音频管理
    MEDIA_ROUTER_SERVICE大屏幕播放管理
    TELEPHONY_SERVICE电话管理
    SubscriptionManager.from(this);消息驱动
    INPUT_METHOD_SERVICE软键盘的输入控制
    UI_MODE_SERVICE模式管理
    DOWNLOAD_SERVICE下载管理
    BATTERY_SERVICE电池管理
    JOB_SCHEDULER_SERVICE后台服务
    NETWORK_STATS_SERVICE应用流量统计

    详细的可以看我这篇博客:传送门
    所以我们就要传入ACTIVITY_SERVICE这个参数,这样我们就可以通过ActivityManager中的getMemoryClass方法获取到该APP在该部手机上能获取到的最大的内存空间,单位是M,或者可以通过getLargeMemoryClass方法获取到设置了最大堆的应用的能获得到的最大的内存空间。但是这两个获取到的值一般都是相同的,因为一般的应用都不支持最大堆的申明,而且也不这么去做。

    private void calculate() {
        StringBuilder str = new StringBuilder();
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        int memClass = activityManager.getMemoryClass();//以m为单位
        int LargememClass = activityManager.getLargeMemoryClass();//以m为单位
        str.append("memClass:"+memClass+"\n");
        str.append("LargememClass:"+LargememClass+"\n");
        mTv.setText(str.toString());
    }

    这么声明最大堆呢?
    在AndroidManifest中application标签下定义下面的属性:

    android:largeHeap="true"

    Android的内存管理方式

    Android系统内存分配与回收方式

    我们先运行我们之前写的程序,然后再来查看一下虚拟机中运行的进程吧,我们要用adb shell这个命令进入Android底层Linux系统,然后再用ps命令查看系统里面的进程,当然有可能有人输入adb shell会返回不是内部或外部命令,也不是可运行程序和批处理文件,这就是你的环境没有配好,我有一篇博客是讲怎么解决的,这里就不啰嗦了(传送门:戳这里)。正常的我们就会获得下面这些数据,我们从中找到属于程序包名的一行数据。
    ADB
    然后我们来看下这个应用的进程相关信息,输入下面这个命令

    dumpsys meminfo com.gin.xjh.testnc

    这里写图片描述
    说了怎么看系统的内存分配了,我们就来说下系统的回收方式:
    GC(垃圾回收器)只有在Heap(堆)空间不够的情况下才会发出垃圾回收的命令。之后再释放空间,所以这就照成了一个很不好的情况,因为他释放空间是会让所有的线程暂停的,如果你的垃圾比较多的话,在GC触发的时候,所有的线程都会被暂停,这样就会让应用卡顿等。

    APP内存限制机制

    每个APP分配的最大内存限制,随不同设备而不同。使用内存最大的是图片。那为什么要堆内存进行限制呢?因为Android手机是多任务系统,有很多APP在运行,所以肯定要对内存进行限制。

    切换应用时后台APP清理机制

    APP在切换的时候是使用了LRU算法的(LRU算法:最近使用的排在最前面,最少可能被清理掉)
    当真正要开始清理的时候,系统会发出onTrimMemort()回调,但是这个回调并不是在清理的时候回调,而是在系统内存发生变化的时候,系统会发出onTrimMemort给各个应用,然后你的APP收到了这个以后,如果系统的内存已经很少了,你就要开始把你APP中不用到的一些占用内存的东西进行释放,这样的话你的APP占用的内存就会相对的小一点,系统在查看后台APP的时候把你的APP清除的可能性就会小一点。

    监控APP内存的方法

    1、代码显示
    这里我们就可以利用上面我们获取最大内存的代码来实现内存的查看,用代码来看呢就要在你需要看内存信息的时候调用这段代码。后面这种方法获取到的最大内存大小应该是和之前那种方法获取到的应该是一样的。

    private void calculate() {
        StringBuilder str = new StringBuilder();
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        int memClass = activityManager.getMemoryClass();//以m为单位
        int LargememClass = activityManager.getLargeMemoryClass();//以m为单位
        str.append("memClass:"+memClass+"\n");
        str.append("LargememClass:"+LargememClass+"\n");
        Float maxMemory = Runtime.getRuntime().maxMemory()*1.0f/(1024*1024);//以字节为单位
        Float totalMemory = Runtime.getRuntime().totalMemory()*1.0f/(1024*1024);//以字节为单位
        Float freeMemory = Runtime.getRuntime().freeMemory()*1.0f/(1024*1024);//以字节为单位
        str.append("maxMemory:"+maxMemory+"\n"+"totalMemory:"+totalMemory+"\n"+"freeMemory:"+freeMemory+"\n");
        mTv.setText(str.toString());
    }

    这里写图片描述
    当然我们也可以通过Android Studio的Android Profiler来动态的查看我们APP的内存使用情况:
    这里写图片描述
    当然我们也可以用DDMS来查看内存信息
    DDMS打开方式:Tools->Android->Android Device Monitor。
    打开DDMS以后我们在Devices中找到我们现在程序运行的模拟器或者真机,找到我们程序对应的包名,然后点击heap,再点Cause GC就可以查看详细信息了,我们查看程序是否内存泄漏主要是看data object以及class object两个的值,假如这两个值随着程序的运行数值趋于平稳就说明你的程序应该没有发生内存泄漏,如果一直在进行增长的话就是可能会发生内存泄漏。
    DDMS

    内存优化方法

    1、数据结构优化

    • 频繁使用字符串拼接用StringBuilder(字符串之间通过+的方式进行字符串拼接会产生中间字符串,这样就造成了内存浪费,并且字符串进行拼接也是比较耗时的)。
    • ArrayMap、SparseArray替换HashMap(内存使用更少,并且在数据量大的时候效率会比较高)。
    • 内存抖动(就是在短时间之内申请了很多空间,但是使用一下子就不用了,之后过了一会以后又申请很多的空间,类似的反复下去,这样就会当空间不足的时候,不断的让GC被调用,导致APP卡顿)。

    我们先来人工造成内存抖动来看一下现象,按照上面的定义我们要先申请很多空间,然后弃之不用,然后再申请,这个实现应该很简单的。

    private void doChurn() {
        Log.d("xjhLog","doChurn start:");
        for(int i=0;i<rowlength;i++){
            String[] strMatrix = new String[length];
            for(int j=0;j<length;j++){
                strMatrix[j] = String.valueOf(ran.nextDouble());
            }
            Log.d("xjhLog","doChurn rowStr:"+i);
        }
        Log.d("xjhLog","doChurn end.");
    }

    通过Android Studio的Android Profiler,我们可以看到内存的使用情况。
    这里写图片描述
    2、对象复用

    • 复用系统中自带的资源
    • ListView/GridView的ConvertView复用
    • 避免在onDraw方法里面执行对象的创建(因为onDraw在界面,图像或者View一有变化的化就会重新调用,如果在里面执行对象的创建的话,就会影响绘制的时间)

    3、避免内存泄漏
    内存泄漏:由于代码的瑕疵,导致这块内存虽然是停止不用的,但是依然还是被其他东西引用着,使得GC没法对它进行回收。

    • 内存泄漏会导致剩余可以使用的Heap(堆)越来越少,以至于频繁的触发GC。
    • 尤其是Activity泄漏。
    • 上下文对象最好是使用Application Context而不是Activity Context,因为context会经常被调用,假如我们使用了Activity Context,就导致了GC认为Activity的引用还是在的,所以不会进行回收,但是其实那时候Activity已经销毁了。
    • 注意Cursor(指针)对象是否及时关闭

    OOM问题

    OOM:内存溢出
    OOM的必然性:Android手机对于每个APP可用的内存是有一定限制的。
    OOM的可解决性:Android手机的生产厂家对于手机内存的设置是有过考量的,一般来说只要是有对内存的优化是不会出现OOM问题的。
    OOM问题绝大部分发生在图片上。
    (1)强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
    如下:

    Object o=new Object();   //  强引用

    当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

    o=null;     // 帮助垃圾收集器回收此对象

    显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
    举例:

    public void test(){
        Object o=new Object();
        // 省略其他操作
    }

    在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。
    但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
    强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

    private transient Object[] elementData;
    public void clear() {
            modCount++;
            // Let gc do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
            size = 0;
    }

    在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
    (2)软引用(SoftReference)
    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

    String str=new String("abc");                                     // 强引用
    SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

    当内存不足时,等价于:

    If(JVM.内存不足()) {
       str = null;  // 转换为软引用
       System.gc(); // 垃圾回收器进行回收
    }

    软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
    (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
    这时候就可以使用软引用

    Browser prev = new Browser();               // 获取页面进行浏览
    SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用    
    if(sr.get()!=null){ 
        rev = (Browser) sr.get();           // 还没有被回收器回收,直接获取
    }
    else{
        prev = new Browser();               // 由于内存吃紧,所以对软引用的对象回收了
        sr = new SoftReference(prev);       // 重新构建
    }

    这样就很好的解决了实际的问题。
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
    其实对应的还有弱引用和虚引用,这个对于内存的优化没有什么用处就不在这里说了。

    优化OOM问题

    • 注意临时Bitmap对象的及时回收
    • 避免Bitmap的浪费
    • Try catch某些大内存的分配的操作
    • 加载Bitmap:缩放比例、解码格式、局部加载

    我们其实可以发现上面这几点大部分和Bitmap图片操作有关系,接下来我们来说下怎么进行图片压缩,主要有三种方法一是把图片缩小,二是把图片的每个像素压缩(一个像素由4个字节压缩到2个字节),最后一种就是显示部分完整图片。
    缩小图片

    private void changePicOpti() {
        if (file == null) {//file图片文件
            return;
        }
        try {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;//只获得文件的宽和高
            BitmapFactory.decodeStream(new FileInputStream(file), null, o);
            int width_tmp = o.outWidth;
            int height_tmp = o.outHeight;
            int scale = 2;
            while (true) {
                if (width_tmp / scale < SCREEN_WIDTH && height_tmp / scale < SCREEN_HEIGHT) {//SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度
                    break;
                }
                scale += 2;
            }
            scale /= 2;//inSampleSize=1会将原始图片放大2倍
            o.inJustDecodeBounds = false;
            o.inSampleSize = scale;
            FileInputStream fin = new FileInputStream(file);
            Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
            img.setImageBitmap(bitmap);
        } catch (IOException e) {
            e.printStackTrace();
        }

    压缩像素

    private void changeRGB() {
        if (file == null) {//file图片文件
            return;
        }
        try {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inPreferredConfig = Bitmap.Config.RGB_565;
            FileInputStream fin = new FileInputStream(file);
            Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
            img.setImageBitmap(bitmap);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    显示部分高清图片

    private void partLoad() {
        if (file == null) {//file图片文件
            return;
        }
        try {
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;//只获得文件的宽和高
            FileInputStream fin = new FileInputStream(file);
            BitmapFactory.decodeStream(fin, null, o);
            int width_tmp = o.outWidth;
            int height_tmp = o.outHeight;
            fin = new FileInputStream(file);
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(fin, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            //SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度,nextPx垂直方向变动的,nextPy水平方向的变动,默认中心点
            int x = width_tmp / 2 - SCREEN_WIDTH / 2 + nextPx;
            int y = height_tmp / 2 - SCREEN_HEIGHT / 2 + nextPy;
            //保证在图片范围以内
            if (x < 0) {
                x = 0;
            } else if (x > width_tmp - SCREEN_WIDTH) {
                x = width_tmp - SCREEN_WIDTH;
            }
            if (y < 0) {
                y = 0;
            } else if (y > height_tmp - SCREEN_HEIGHT) {
                y = height_tmp - SCREEN_HEIGHT;
            }
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(x, y, x + SCREEN_WIDTH, y + SCREEN_HEIGHT), options);
            img.setImageBitmap(bitmap);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    展开全文
  • App性能优化:内存优化

    千次阅读 2020-01-03 14:22:41
    App性能优化:内存优化 本篇主要探讨app性能优化相关的知识点,预计20年2月底前完成 内存优化工具 内存管理机制 内存都懂解决 内存泄漏解决 MAT详解 小结 ...

    App性能优化:内存优化

    目录:

    1. 内存优化工具
    2. 内存管理机制
    3. 内存抖动解决
    4. 内存泄漏解决
    5. MAT详解
    6. 小结
    背景介绍
    • 内存是大问题但缺乏关注
    • 压死骆驼的最后一根稻草
    表现形式
    • 内存抖动:锯齿状,GC导致卡顿
    • 内存泄漏:可用内存减少、频繁GC
    • 内存溢出:OOM、程序异常

    1、工具选择

    • Memory Profiler
    • Memory Analyzer
    • LeakCanary
    Memory Profiler
    1. 实时图标展示内存使用量
    2. 识别内存泄漏、抖动
    3. 提供捕获堆转储、强制GC以及跟踪内存分配的能力
    Memory Profiler的使用
    1. 非常直观
    2. 线下平时使用
    Memory Analyzer(MAT)
    1. 强大的Java heap 分析工具,帮助查找内存泄漏和内存占用
    2. 生成整体报告,分析问题
    3. 线下深入使用
    LeakCanary
    1. 自动内存泄漏检测
    2. 线下集成

    2、android内存管理机制

    Java内存管理机制

    在这里插入图片描述

    Java内存回收算法-标记清除算法
    • 标记出所有需要回收的对象
    • 统一回收所有被标记的对象
      在这里插入图片描述
    标记回收算法存在的问题
    • 标记和清除的效率不高
    • 产生大量的内存碎片
    Java内存回收算法之 复制算法
    • 将内存分成两份,只将数据存储在其中一块上。
    • 当需要垃圾回收时,也是标记出废弃的数据,然后将有用数据复制到另一块内存上,最后将第一块内存全部清除
      在这里插入图片描述
    复制算法的问题
    • 实现简单,运行高效
    • 浪费一半空间,代价大
    Java内存回收算法之 标记整理算法
    1. 标记过程与“标记-清除算法”一样
    2. 存活对象往一端移动
    3. 清理其余内存
      在这里插入图片描述
    标记整理算法的特点
    1. 避免标记-清理导致的内存碎片
    2. 避免复制算法的空间浪费
    Java内存回收算法之 分代收集算法
    1. 结合多种收集算法优势
    2. 新生带对象存活率低:复制算法
    3. 老年代对象存活率高:标记-整理算法
    Android内存管理机制
    • 内存弹性分配,分配值和最大值受具体设备影响
    • OOM场景:内存真正不足,可用内存不足
    Dalvik 与Art区别
    • Dalvik仅固定一种回收算法
    • Art回收算法可运行期选择
    • Art具备内存整理能力,减少内存空洞
    Low Memory killer
    • 进程分类:前端、可见、服务、后台、空进程,优先级依次降低
    • 回收收益

    内存抖动案例

    ######制造内存抖动

        companion object {
            var datas: ArrayList<TestClass>? = null
            val handler = object : Handler() {
                override fun handleMessage(msg: Message) {
                    //创造内存抖动
                    for (i in 0..100) {
                        datas = ArrayList<TestClass>(100000)
                    }
                    sendEmptyMessageDelayed(0, 1000)
                }
            }
        }
    

    在这里插入图片描述

    内存抖动解决技巧
    1. 找循环或者频繁调用的地方
    2. 通过Profiler定位,锯齿状或者频繁的GC

    内存泄漏

    • 定义:内存中存在没有用的对象,又无法回收
    • 表现:内存抖动、可用内存逐渐减少

    内存泄漏案例

    1. 静态变量持有Activity引用导致Activity无法被回收
    //定义一个接口
    public interface Callback {
    
        void doOperation();
    }
    //定义持有Activity引用的类
    public class CallbackMananger {
        public static ArrayList<Callback> sCallbacks = new ArrayList();
    
        public static void addCallback(Callback callback) {
            sCallbacks.add(callback);
        }
    
        public static void removeCallBack(Callback callback) {
            sCallbacks.remove(callback);
        }
    }
    
    
    1. 在生成LeakCanaryActivity,显示一张图片
    
    class MemoryLeakActivity : AppCompatActivity(), Callback {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_memory_leak)
            val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_leaktest)
            iv.setImageBitmap(bitmap)
            //保存Activity引用
            CallbackMananger.addCallback(this)
        }
    
        override fun doOperation() {
    
        }
    }
    
    
    1. 重复打开关闭 页面,即可在Profiler中看到内存成阶梯状上升,如下图所示:
      在这里插入图片描述
      显然:这里每次GC的的时候并没有导致MemoryLeakActivity被回收,内存图形呈现阶梯状显示。

    2. 使用MAT分析内存泄漏
      1. 进入官网下载MAT工具:MAT下载地址
      2. 下载对应系统版本
      在这里插入图片描述

    3. 安装MAT工具,双击zip文件解压后得到mat.app
      在这里插入图片描述

    4. 获取堆文件 Heap Dump
      在这里插入图片描述

    5. 将文件存到本地
      在这里插入图片描述
      在这里插入图片描述

    6. 注意这里是Android的hprof文件,需要使用android 的platform-tool里面的hprof-conv工具对文件进行一次格式转化,转化命令如下:
      ./hprof-conv /Users/hudingpeng/Desktop/memory-20200221T114049.hprof 2.hprof
      在这里插入图片描述

    7. 这样在platform-tools目录下会生成一个2.hprof文件,如下图所示:
      在这里插入图片描述

    8. 打开MAT工具:

      1. 直接点击mat.app会报错,按照以下步骤运行
      2. 右键mat.app => 显示包内容 =>进入 mat.app/Contents/MacOS ,此目录下就有工具MemoryAnalyzer =>双击直接打开工具就行了
        在这里插入图片描述
    9. 在这里插入图片描述
      10.点击open a Heap Dump选择我们的转化后的文件2.hprof 文件即可,下面分析MAT工具的一些常用用法

    MAT工具分析内存泄漏

    1. 打开hprot文件,选择Histogram
      在这里插入图片描述

    2. 搜索我们的泄漏的MemoryLeakActivity,可以看到有4个MemoryLeakActivity对象,表示已经泄漏了
      在这里插入图片描述

    3. 右键选择查看with incoming references
      在这里插入图片描述

    4. 选择一个,点击查看到GC Root的路径,看到具体引用的地方
      在这里插入图片描述
      在这里插入图片描述
      这样我们就看到了具体泄漏的地方就是,CallbackManager类里面的静态变量sCallbacks

    5. 点击Group by package 可以按包名排序
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      这样也可以很方便看到我们的包名下的类对象生成的情况,这里也可以看到MemoryLeakActivity在内存里有4个对象

    6. 第三个菜单项:dominator_tree 视图,显示对象占用内存的百分比
      在这里插入图片描述
      可以看到对象占用内存的百分比,给我们的内存优化提供一定的参考

    7. 第四个菜单项:OQL,相当于一种数据查询语言,我们直接搜索我们的类,点击感叹号查询,如下图所示
      在这里插入图片描述

    8. Top Consumer条目:通过图表的形式显示占用内存最多的对象
      在这里插入图片描述
      在这里插入图片描述
      这对我们的内存占用优化也是一个不错的参考

    关于BitMap的优化

    Btimap的内存模型
    1. 获取Bitmap占用的内存

      1. getByteCount()方法
      2. 宽 x 高 x 一像素占用的内存
    2. 常规方式:
      背景:图片对内存大小至关重要、图片宽高大于控件宽高

    3. 通过Epic ARTHook优雅检测不合理的图片
      Epic:Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 4.0 ~ 10.0)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。
      Epic 被 VirtualXposed 以及 太极 使用,用来实现非 Root 场景下的 Xposed 功能,已经经过了相当广泛的验证。
      非常厉害的AOP框架,参考连接:Epic AOP框架
      这里我们Hook setImageBitmap方法:

    import android.graphics.Bitmap;
    import android.graphics.drawable.BitmapDrawable;
    import android.graphics.drawable.Drawable;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.ImageView;
    
    import com.optimize.performance.utils.LogUtils;
    import com.taobao.android.dexposed.XC_MethodHook;
    
    public class ImageHook extends XC_MethodHook {
    
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            // 实现我们的逻辑
            ImageView imageView = (ImageView) param.thisObject;
            checkBitmap(imageView,((ImageView) param.thisObject).getDrawable());
        }
    
    
        private static void checkBitmap(Object thiz, Drawable drawable) {
            if (drawable instanceof BitmapDrawable && thiz instanceof View) {
                final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
                if (bitmap != null) {
                    final View view = (View) thiz;
                    int width = view.getWidth();
                    int height = view.getHeight();
                    if (width > 0 && height > 0) {
                        // 图标宽高都大于view带下的2倍以上,则警告
                        if (bitmap.getWidth() >= (width << 1)
                                && bitmap.getHeight() >= (height << 1)) {
                            warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
                        }
                    } else {
                        final Throwable stackTrace = new RuntimeException();
                        view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw() {
                                int w = view.getWidth();
                                int h = view.getHeight();
                                if (w > 0 && h > 0) {
                                    if (bitmap.getWidth() >= (w << 1)
                                            && bitmap.getHeight() >= (h << 1)) {
                                        warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
                                    }
                                    view.getViewTreeObserver().removeOnPreDrawListener(this);
                                }
                                return true;
                            }
                        });
                    }
                }
            }
        }
    
    
        private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
            String warnInfo = new StringBuilder("Bitmap size too large: ")
                    .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
                    .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
                    .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
                    .toString();
    
            LogUtils.i(warnInfo);
        }
    
    }
    
    

    在App onCreate方法里注册要Hook的方法,传入ImageHook

     DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());
                }
            });
    

    more:LeakCanary 监控内存泄漏

    内存优化总结

    优化大方向
    1. 内存泄漏
    2. 内存抖动
    3. Btimap
    优化细节
    1. LargeHeap属性 (建议开启,大概率能申请到更多内存)
    2. onTrimMemory (系统块干掉app了,可以手动清理界面,跳转到主界面)
    3. 使用系统优化过的集合:SparseArray
    4. 谨慎使用SharedPreferences
    5. 谨慎使用外部库
    6. 业务架构设计合理
    展开全文
  • iOS内存优化,app内存优化

    千次阅读 2016-09-08 17:37:15
    当我们开发iOS应用时,好的性能对我们的App来说是很重要的。你的用户也希望如此,但是如果你的app表现的反应迟钝或者很慢也会伤害到你的审核。   然而,由于IOS设备的限制有时很难工作得很正确。我们开发时有很...

    当我们开发iOS应用时,好的性能对我们的App来说是很重要的。你的用户也希望如此,但是如果你的app表现的反应迟钝或者很慢也会伤害到你的审核。 

         然而,由于IOS设备的限制有时很难工作得很正确。我们开发时有很多需要我们记住这些容易忘记的决定对性能的影响。 

         这是为什么我写这篇文章的原因。这篇文章用备忘录的形式集合了25个技巧和诀窍可以用来提高你的app性能。所以保持阅读来给你未来的App一个很不错的提高。 

          Note:在优化代码之前,必须保证有个需要解决的问题!不要陷入"pre-optimizing(预优化)"你的代码。勤 用Instruments分析你的代码,发现任何一个需要提高的地方。Matt Galloway写了一个使用Instruments优化代码的的教程

        

        以下这些技巧分为三个不同那个的级别---基础,中级,高级。 

       基础

       这些技巧你要总是想着实现在你开发的App中。 

       1. 用ARC去管理内存(Use ARC to Manage Memory)

       2.适当的地方使用reuseIdentifier(Use a reuseIdentifier Where Appropriate)

       3.尽可能设置视图为不透明(Set View as Opaque When Possible)

       4.避免臃肿的XIBs文件(Avoid Fat XiBs)

       5.不要阻塞主进程(Don't Block the Main Thread)

       6.调整图像视图中的图像尺寸(Size Images to Image Views)

       7.选择正确集合(Choose the Correct Collection)

       8.启用Gzip压缩(Enable GZIP Compression)

       

       中级

       这些技巧是当你遇到更复杂的情况的时候使用。

        9. 重用和延迟加载视图(Reuse and Lazy Load Views)

       10.缓存,缓存,缓存(Cache,Cache,Cache)

       11.考虑绘图(Consider Drawing)

       12.处理内存警告(Handle Memory Warnings)

       13.重用大开销对象(Reuse Expensive Objects)

       14.使用精灵表(Use Sprite Sheets )

       15.避免重复处理数据(Avoid Re-Processing Data)

       16.选择正确的数据格式(Choose the Right Data Format)

       17.适当的设置背景图片(Set  Background Images Appropriately)

       18.减少你的网络占用(Reduce Your Web Footprint)  

       19.设置阴影路径(Set the Shadow Path )

       20.你的表格视图Optimize Your Table Views)

       21.选择正确的数据存储方式(Choose Correct Data Storage Option)

       

       高级

       这些技巧你应该只在你很积极认为它们能解决这个问题,而且你觉得用它们很舒适的时候使用。

       22.加速启动时间(Speed up Launch Time )

       23.使用自动释放池(Use AutoRelease Pool)

       24.缓存图像(Cache Images-Or not 

       25.尽可能避免日期格式化器(Avoid Date Formatters Where Possible)  

       没有其他的,一起去看看这些技巧吧!

      

     基础的性能提升

    1)用ARC去管理内存

       ARC是伴随IOS5 一起发布的,它用来消除常见的的内存泄漏。

       ARC是"Automatic Reference Counting"的缩写。它自动管理你代码中的retain/release循环,这样你就不必手动做这事儿了。

       下面这段代码展示了创建一个view的常用代码

    ?
    1
    2
    3
    4
    UIView *view =[[UIView alloc] init];
    //...
    [self.view addSubview:view];
    [view release];

       这里极其容易忘记在代码结束的地方调用release,ARC将会自动的,底层的为你做这些工作。

       除了帮助你你避免内存泄漏,ARC还能保证对象不再使用时立马被回收来提高你的性能。你应该在你的工程里多用ARC。

       这里是一些学习更多关于ARC的非常棒的资源

       值得注意的是ARC不能消除所有的内存泄漏。你依然有可能内存泄漏,这主要可能是由于blocks(块),引用循环,CoreFoundation对象管理不善(通常是C结构体,或者是确实很糟糕的代码)。


    2)适当的地方使用reuseIdentifier   

         在app开发中的一个常见的为UITableViewCellsUICollectionViewCellsUITableViewHeaderFooterViews设置一个正确的reuseIdentifier(重用标识)

         为了最大化性能,一个tableView的数据源一般应该重用UITableViewCell对象,当它在tableView:cellForRowAtIndexPath:中分配数据给cells的时候。一个表视图维护了一个UITableViewCell对象的队列或者列表,这些对象已被数据源标记为重用。

          如果你不用reuseIdentifier 会怎么样呢?

         如果你用,你的tableview每显示一行将会配置一个全新的cell。这是非常费事的操作而且绝对会影响你app滚动的性能。

         自从引进了iOS6,你应该为header and footer 视图设置reuseIdentifiers,就像在 UICollectionView’s cells 和 supplementary views(补充视图)一样。 

         使用reuseIdentifiers,当你的数据源要求提供一个新的cell给tableview的时候调用这个方 

    ?
    1
    2
    3
    NSString *CellIdentifier = @ "Cell" ;
     
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];


    3)可能的时候设置视图为不透明

         如果你有不透明视图(opaque views)--也就是说,没有透明度定义的视图,你应该设置他们的opaque属性为YES。 

         为什么? 这会允许系统以最优的方式绘制你的views。这是一个简单的属性可以在Interface Builder 和代码中设置。 

         苹果的文档 Apple documentation中有对这个属性的描述 

         这个属性提供了一个提示给图系统如何对待这个视图。如果设置为YES,绘制系统将会把这个视图视为完全不透明。这样允许系统优化一些绘制操作和提高性能。如果设置为NO,绘图系统会复合这个视图和其他的内容,这个属性的默认值是YES

        在相对静态的屏幕上,设置opaque属性不会有什么大问题。尽管如此,如果你的视图是嵌入在一个scrollView,或者是一个复杂的动画的一部分,不设置这个属性绝对会影响你的程序的性能。 

        你也可以使用Debug\Color olor Blended Layers选项 在你的模拟器中形象化的看见没有设置为不透明(opaque)的视图.你的目标应该是尽可能多的设置视图为透明。 


    4)  避免臃肿的XIB文件

         故事板,由iOS5引进,很快的替代XIBs。尽管如此,XIBs在一下情况下依然是很有用的。如果你需要在IOS5之前版本的设备上运行或者你想自定义重用的视图,那么你确实不能避免使用它们。 

         如果你专注使用XIBs,那么让它们尽量的简单。尝试为一个试图控制器创建一个XIB,如果可能的话,把一个视图控制器的视图分层管理在单独的XIBs中。 

        注意当你加载一个XIB到内存的时候,它所有的内容都会载入内存,包括所有的图片。如果你有视图但不是要立即使用,那你就浪费了珍贵的内存。值得注意的是这不会发生在故事板中,因为故事版只会在需要的时候实例化一个视图控制器。 

        当你载入一个xib,所有的图像文件会被缓存,如果是开发OSX,那么音频文件也会被缓存。 

        Apple’s documentation 如是说: 

        当你载入一个包含了图和声音资源引用的nib文件时,nib加载代码读取实际的图片文件和音频文件到内存中并缓存它。在OS X中,图片和音频资源被存储在已命名的缓存 中这样你可以在之后需要的时候访问它们。在iOS中,只有图片资源被缓存,访问图片,你使用NSImage或者UIImage的imageNamed:方法来访问,具体使用取决于你 的平台。

        显然这也发生在使用故事板的时候。尽管如此,我还不能找到这种说法的证据。如果你知道,请给我留言。 

        想学习更多关于故事板的更多内容吗?看看Matthijs Hollemans的 Beginning Storyboards in iOS 5 Part 1and Part 2


    5)不要阻塞主进程

        你永远不应该在主线程中做任何繁重的工作。这是因为UIKIt的所有工作都在主线程中进行,比如绘画,管理触摸,和响应输出。 

    你的app的所有工作都在主线程上进行就会有阻塞主线程的风险,你的app会表现的反应迟钝。这是在App Store里获一星评论的快速途径!(作者卖萌..) 

        阻塞主线程最多的情况就是发生在你的app进行I/O操作,包括牵扯到任何需要读写外部资源的任务,比如读取磁盘或者网络 

        你可以异步的执行网络任务使用NSURLConnection中的这个方法: 

    ?
    1
    + ( void )sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:( void (^)(NSURLResponse*, NSData*, NSError*))handler

         或者使用第三方框架比如 AFNetworking

        如果你在做任何大开销的操作(比如执行一个耗时的计算,或者读写磁盘)使用Grand Central Dispatch(GCD)或者 NSOperations 和 NSOperationQueues. 

        使用GCD的模板如下代码所示: 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     
         // switch to a background thread and perform your expensive operation
     
      
     
         dispatch_async(dispatch_get_main_queue(), ^{
     
             // switch back to the main thread to update your UI
     
      
     
         });
     
    });

          这里为什么dispatch_async 嵌套在第一个的里面?这是因为任何UIKit相关的代码都必须在主线程上执行。 

          对NSOperation和GCD的详情感兴趣?看看Ray Wenderlich’s Multithreading and Grand Central Dispatch on iOS for Beginners 教程,和 Soheil Azarpour’s How To Use NSOperations and NSOperationQueues 教程。 



    6)调整图像视图中的图像尺寸

        如果你用UIImageView呈现app束中的图片时,确认图片和UIImageView的尺寸相同。缩放图片会非常的耗时,特别是当你的UIImageView被嵌入UIScrollView。 

         如果图片是从远程服务器上下载的,有时你没法控制图片尺寸,或者你不能在服务器上在下载之前缩放它。在这些情况下你可以在图片下载完成后手动缩放一次,最好是在后台进程中。然在UIImageView中使用调整尺寸之后的图片。 


    7)选择正确集合

         学着怎么在手头工作中使用最合适的类或对象是写出高效代码的基本。当时用集合是(collections),这个说法特别对。 

         可喜的是在苹果开发者文档( Collections Programming Topics)中有详细解释可用类之间的关系,还有解释各个类的适用情况。这个文档是每个使用集合的人的必读文档。 

        这是一个最常见的集合类型的快速简介: 

    • Arrays:有序的值的列表,用index快速查找,通过值查找慢,insert/delete操作慢。 
    • Dictionaries:存储键/值对.用index快速查找。 
    • Sets: 无序的值列表。通过值快速查找,insert/delete快。 


    8)启用Gzip压缩

         大量和持续增长的app依赖从远端服务器或者外部APIs获取的外部数据。某些时候你可能会开发一些需要下载XML,JSON,HTML或者其他文本格式的应用。 

         问题是移动设备不能保证网络环境,用户可能一分钟在边缘网络,下一分钟又是3G网络,无论什么情况下,你不想你的用户一直等待。 

         一个减少文件大小并加速下载的网络资源的方法是同时在你的服务器和客户端上使用GZIP压缩,对于文本数据这种有高比率压缩的数据来说非常有用。 

         好消息是iOS早已默认支持GZIP压缩,如果你是使用NSURLConnection或者建立在这之上的框架比如AFNetworking。更好的消息是一切云服务提供商像 Google App Engine早已发送压缩之后的响应数据。 

         这里有一篇文章great article about GZIP compression 介绍如何在你的Apache或IIS服务器上启用GZIP。 


    中级性能提升

        好的,当谈到优化你的代码时,你应该很自信你已经初级的方法已经完全掌握了。但有时候有的问题的解决方法并不是那么显而易见,它由你app的结构和代码决定,尽管如此,在正确的上下文中,它们可能是没有价值的。 

    9)重用和延迟加载视图
       

         越多的视图就有越多的绘图操作,最终意味着更多的CPU和内存开销。这说得特别对如果你的app嵌入很多视图在UIScrollView时。

         管理这个的技巧是去模拟UITableView 和 UICollectionView的行为:不要一次创建所有的子视图,而是在需要的时候创建,然后把他们假如重用队列中。 

         这样,你只需要在视图浮动时配置你的视图,避免昂贵的资源分配开销。 

         视图创建的时机问题也同样适用于你app的其他地方。试想当你点击一个button时呈现一个视图的情景。至少有两种方法: 

         1.屏幕第一次载入时创建视图并隐藏它。当你需要的时候,显示出来。 

         2.需要呈现的时候一次创建视图并显示它。 

         每种方法都有各自的优缺点 

         使用第一种方法,你消耗了更多内存因为从创建开始到它释放前你都保持了它的内存,然而,当你点击button的时候,你的app会表现得响应快速因为它只需要更改视图的可视化属性。 

         使用第二种方法会有相反的效果,在需要的时候创建视图,消耗更少的内存,但当button被点击时应用会表现得不那么响应快速。 


    10)缓存,缓存,缓存


          在开发应用时的一个伟大的经验是"Cache what matters"--也就是说那些不大会改变但会平凡被访问的东西。 

         你能缓存些什么呢?缓存的候选项有远程服务器的响应,图片,已计算过的值(比如UITableView的行高)。 

          NSURLConnection 根据处理的Http头缓存资源到磁盘或者内存中,你甚至可以手动创建一个NSURLRequest值加载缓存过的值。 

          这里有一段很棒的代码,用在任何时候你需要针对一个不大会改变的图片创建一个NSURLRequest。 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    + (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
     
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
     
     
        request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;  // this will make sure the request always returns the cached image
     
         request.HTTPShouldHandleCookies = NO;
     
         request.HTTPShouldUsePipelining = YES;
     
         [request addValue:@ "image/*" forHTTPHeaderField:@ "Accept" ];
     
         return request;
     
    }

          如果想知道更多关于Http caching,NSURLCache,NSURLConnection等内容,请阅读the NSURLCache entry

          注意,你可以通过NSURLConnection获取取一个URL请求,AFNetworking也可以。有了这个技巧这样你不用改变任何你的网络代码。 

          如果要缓存不牵扯到HTTP请求的其他东西,NSCache是很好的选择。

          NSCache像NSDictionary,但是当系统需要回收内存的时候会自动的移除内容。 

          对HTTP Cache感兴趣并想学更多的内容?推荐阅读这篇文章best-practices document on HTTP caching


    11)考虑绘图

        在IOS中有很多方法可以制作拥有很棒外观的buttons,你可以是由全尺寸的图像,也可以使用调整尺寸之后的图像,或者你用CALayer,CoreGraphics,甚至OpenGL手动的它们。 

        当然,每种途径都有不同的复杂度级别和不同的性能,这篇文章非常值得一读post about iOS graphics performance here,这是Apple UIKit团队成员Andy Matuschak发表的文章,里面对各种方法有一些非常棒的见解和对性能的权衡。 

         使用预渲染图片更快,因为iOS不用创建一张图像和绘制图形到屏幕上(图像已经处理好了)。问题是你需要全部把这些图片放进应用束里,增加它的尺寸。那就是为什么使用可调整尺寸的图片是那么好:你通过移除”浪费了的“图片空间来节约空间。你也不需要为不同的元素生成不同的图片。(例如 buttons) 

        尽管如此,用图片你会失去代码调整你图片的能力,需要一次又一次的生成它们然后把它们加入到应用中。这是个缓慢的过程。另外一点如果你有动画或者很多张稍微变化的图片(例如 颜色叠加),你需要加很多的图片增加了应用束的大小。 

         总结一下,你需要想对你来说最重要的是什么:绘图性能还是app的大笑.通常两个都很重要,所以你会在一个工程里使用这两种方法。 


    12)处理内存警告

          当系统内存低的时候iOS会通知所有的正在运行的app,关于低内存警告的处理苹果官方文档 official Apple documentation描述: 

          如果你的应用收到这个警告,它必须尽可能多的释放内存。最好的方法是移除对缓存,图像对象,和其他稍后要创建的对象的强引用。 

          幸运的是,UIKit提供了一些方法去接收低内存警告: 

    • 实现App代理中的applicationDidReceiveMemoryWarning:方法。 
    • 重载你自定义UIViewController子类中的didReceiveMemoryWarning方法。 
    • 注册接收UIApplicationDidReceiveMemoryWarningNotification的通知 

          一旦收到这些警告,你的处理方法必须立刻响应并释放不必要的内存。 

          举例,如果视图当前不可见,UIViewController的默认行为是清除这些视图;子类可以通过清除额外的数据结构来补充父类的默认行为。一个应用程序维护一个图片的缓存,没有在屏幕上的图片都会被释放。 

         一旦收到内存警告,释放可能的全部内存是很重要的,否则你就有让你的app被系统杀死的的风险。 

         尽管如此,开始扑杀对象释放内存的时候要小心,因为你需要保证它们会在之后重新创建。当你开发app的时候,用你的模拟器上的模拟内存警告功能测试这种情况。 


    13)重用大开销对象

       有的对象的初始化非常慢--NSDateFormatter 和 NSCalendar是两个例子,但是你不能避免使用它们,当你从 JSON/XML响应中解析日期时。 

       避免使用这些对象时的性能瓶颈,试着尽可能的重用这些对象。你可以加入你的类中成为一个属性,也可以创建为静态变量。 

       注意如果你选择了第二种方式,这个对象在app运行的时候会一直保持在内存里,像单例一样。 

      下面这段代码演示了NSDateFomatter作为一个属性的lazy加载,第一次被调用然后创建它,之后就使用已创建在的实例 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // in your .h or inside a class extension
     
    @property (nonatomic, strong) NSDateFormatter *formatter;
     
      
    // inside the implementation (.m)
     
    // When you need, just use self.formatter
     
    - (NSDateFormatter *)formatter {
     
         if (! _formatter) {
     
             _formatter = [[NSDateFormatter alloc] init];
     
             _formatter.dateFormat = @ "EEE MMM dd HH:mm:ss Z yyyy" // twitter date format
     
         }
     
         return _formatter;
     
    }

           同样要记住设置一个NSDateFormatter的日期格式几乎跟创建一个新的一样慢。因此,如果在你的应用中你频繁需要处理多个日期格式,你的代码应该获利于初始化创建,重用,多个NSDateFormatter对象。 

    14) 使用精灵表

        你是一个游戏开发者吗?精灵表是你的好朋友之一.精灵表让绘制比标准屏幕绘制方法更快速,消耗更少的内存。 

        这里有两个很棒的精灵表使用的教程 

    1. How To Use Animations and Sprite Sheets in Cocos2D
    2. How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

        第二个教程详细覆盖了像素格式,它可以对游戏性能有一个可衡量的影响。 

        如果对精灵表还不是很熟悉,一个很好的介绍 SpriteSheets – The Movie, Part 1and Part 2. 这些视频的作者是Andreas Löw,一个最流行的创建精灵表的工具Texture Packer的创建者。 

         除了使用精灵表之外,之前已经说到的内容也可以用在游戏上.举个例子,如果你的游戏有很多精灵,比如在标准的敌人或炮弹射击游戏,你可以重用精灵表额如是每次重新创建它们。 


    15)避免重复处理数据

         很多app调用函数获取远程服务器上的数据.这些数据通常是通过JSON 或者 XML格式来传输。非常重要的是在请求和接收数据的时候努力在两端使用相同的数据结构。 

         理由?在内存中操纵数据以合适你的数据结构是非常昂贵的。 

         比如,如果你需要在表格视图中显示数据,最好请求和接收数据是数组的格式,以避免任何中间操纵数据,使其适合你在app中使用的数据结构 

         相似的,如果你的应用程序依赖于访问特定值的键,那么你可能会想要请求和接收一个键/值对的字典 

         通过第一次就获取正确格式的数据,在自己的应用程序中你就会避免很多的重复处理工作,使数据符合你的选择的结构。  


    16)选择正确的数据格式

        你可以有很多方法从web 服务中传递数据到你的app中    

        JSON 是一种通常比XML小且解析更快的格式,它的传输的内容也比较小。自iOS5起,内置的JSON解析很好用 built-in JSON deserialization

        尽管如此,XML的一个优势当你使用SAXparsing方法时,你可以传输过程中读取它,在面的非常大的数据时,你不必像JSON一样在数据下载完之后才开始读取。 


    17)适当的设置背景图片

        像iOS编码的其他工作一样,至少有两种不同方式去替换你视图的背景图片。 

    1. 你可以设置你的视图的背景颜色为UIColor的colorWithPatternImage创建的颜色。 
    2. 你可以添加一个UIImageView子试图给View 

        如果你有全尺寸的背景图片,你绝对要用UIImageView,因为UIColor的colorWithPatternImage是重复的创建小的模式图片,在这种情况下用UIImageView方式会节约很多内存。 

    ?
    1
    2
    3
    4
    5
    // You could also achieve the same result in Interface Builder
     
      UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@ "background" ]];
     
    [self.view addSubview:backgroundView];

         尽管如此,如果你计划用模式图片背景,你应该是用UIColor的colorWithPatternImage。它更快一些,而且这种情况不会使用很多内存。 

    ?
    1
    self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@ "background" ]];


    18)减少你的网络占用

        UIWebView 是非常游泳的.它非常容易用来显示web内容,甚至创建你app的视窗。这些都是标准UIKit 空间很难做到的。

        尽管如此,你可能注意你可以用在你的app中的UIWebView组件并没有Apple的Safari app快。这是Webkit’s的Nitro引擎的限制使用。JIT compilation

         所以为了获得最佳的性能,你需要调整你的HTML。第一件事是尽可能多的避免Javascript,包括避免大的框架比如jQuery。有时使用vanilla Javascript取代依赖的框架会快很多。 

         随时随地遵循异步加载Javascript文件的实践。特别当它们不直接影响到页面表现的时候,比如分析脚本。 

        最后,总是要意识到你在用的图片,保持图片的正确尺寸。正如这个教程前面所提到的,利用精灵表的优势来节约内存和提高速度。 

         想要获取更多的信息,看看WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS


    19) 设置阴影路径

         你需要给视图或者layer添加一个阴影,你应该怎么做? 

         大多数开发者是添加 QuartzCore框架到工程中,然后写如下代码: 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #import <QuartzCore/QuartzCore.h>
     
      // Somewhere later ...
     
    UIView *view = [[UIView alloc] init];
     
      // Setup the shadow ...
     
    view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
     
    view.layer.shadowRadius = 5.0f;
     
    view.layer.shadowOpacity = 0.6;

          看起来非常简单,是吧? 

          不好的是这个方法有一个问题。核心动画必须要先做一幕动画确定视图具体形状之后才渲染阴影,这是非常费事的操作。 

          这里有个替代方法让系统更好的渲染,设置阴影路径: 

    ?
    1
    view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];


         如果你想知道这个内容的更多技巧,Mark Pospesel 写过一篇
    post about shadowPath.       

         设置阴影路径,iOS不需要总是计算如何绘制阴影。而是用已经计算好的的路径。坏消息是它依赖与你的视图格式,你是视图可能很难计算这个路径。另一个问题是你需要在每次视图的框架改变时更新阴影路径。 


    20) 优化你的表格视图

        表格视图需要快速的滚动,如果不能,用户能确切注意到很滞后。 

        为了让你的表格视图流畅的滚动,保证你实现了下列的建议。 

    • 通过正确的reuseIdentifier重用cells 
    • 尽量多的设置views 为不透明,包括cell本身。 
    • 避免渐变,图像缩放,屏幕以外的绘制。 
    • 如果行高不总是一样,缓存它们。 
    • 如果cell显示的内容来自网络,确保异步和缓存。 
    • 使用shadowPath来建立阴影。 
    • 减少子视图的数目。 
    • cellForRowAtIndexPath:中做尽量少的工作,如果需要做相同的工作,那么只做一次并缓存结果。 
    • 使用适当的数据结构存储你要的信息,不同的结构有对于不同的操作有不同的代价。 
    • 使用rowHeight,sectionFooterHeight,sectionHeaderHeight为常数,而不是询问代理。 


    21) 选择正确的数据存储方式

       当要存储和读取大数据的时候你的选择是什么? 

       你有一些选项,包括: 

    • 使用 NSUserDefaults存储它们。 
    • 存储在结构化文件中,XML,JSON,Plist格式中。 
    • 是用NSCoding打包? 
    • 存储在本地数据库,如SQLite 
    • 使用NSData 

         NSUserDefaults有什么问题呢?虽然说NSUserDefaults是好而且简单,它确实很好只有当你有很少的数据要存(像你的等级,或者音量是开还是关)。一旦你接触大数据,会有更好的其他选择。 

         保存在结构化文件中也可能有问题。一般的,在解析之前,你需要加载整个文件到内存中,这是非常耗时的操作。你可以使用SAX去处理XML文件,但是那是一个复杂的作法。同时你加载了全部的对象进内存,其中有你想要的也有不想要的。 

         那么NSCoding怎么样呢?不幸的是,它也同样要读写文件,跟上面说的方法有同样的问题。 

         你最好的解决方法是使用SQLite或者 Core Data. 通过这些技术,你可以执行特定的查询只加载需要的对象,避免强力搜索方法来检索数据。性能方面,SQLite和Core Data 非常接近。 

        SQLite 和 Core Data最大的不同就是它们的使用方法。Core Data呈现为一个对象图模型,但是SQLite是一个传统的DBMS(数据库管理系统).通常Apple建议你用Core Data,但是除非你有特殊的原因不让你你会想避开它,使用更低级的SQLite。 

        如果在你的app中使用SQLite,一个方便的库 FMDB 允许你使用SQLite而不用专研SQLite的C API。 


    高级性能技巧

       寻找一些精英的方式去成为十足的代码忍者?这些高级性能技巧可以合适的时候使用让你的app运行得尽可能的高效。 

    22)加速启动时间

       App的启动时间非常重要,特别是第一次启动的时候。第一影响意味着太多了! 

       最大的事情是保证你的App开始尽量的快,尽量的多的执行异步任务,不如网络请求,数据库访问,或者数据解析。 

       尽量避免臃肿的XIBs,因为你在主线程中加载。但是在故事板中不会有这个问题,所以尽量用它们。 

       Note: 监察人不会运行你的app在Xcode调试中, 所以确保测试启动性能时断开与Xcode的连接。


    23)使用自动释放池

         NSAutoreleasePool负责释放在代码块中的自动释放对象。通常,它是被UIKit自动调用的。但是也有一些场景我们需要手动创建NSAutoreleasePools。

         举个例子,如果你创建太多的临时对象在你的代码中,你会注意到你的内存用量会增加直到对象被释放掉。问题是内存只有在UIKit排空(drains)自动释放池的时候才能被释放,这意味着内存被占用的时间超过了需要。                

         好消息是你可以在你的@autoreleasepool段中创建临时对象来避免上述情况。代码如下所示。

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    NSArray *urls = <# An array of file URLs #>;
    for (NSURL *url in urls) {
     
         @autoreleasepool {
     
             NSError *error;
     
             NSString *fileContents = [NSString stringWithContentsOfURL:url
     
                                              encoding:NSUTF8StringEncoding error:&error];
     
             /* Process the string, creating and autoreleasing more objects. */
     
         }
     
    }
        在每次迭代之后会自动释放所有的对象。 

        

        你可以阅读更多关于NSAutoreleasePool的内容Apple’s official documentation.


    24)缓存图像

         这里有两种方法去加载app束中的Image,第一个常见的方式是用imageNamed. 第二个是使用imageWithContentsOfFile 

         为什么会有两种方法,它们有效率吗? 


         imageNamed 在载入时有缓存的优势。文档 documentation for imageNamed是这样解释的: 

         这个方法看起来在系统缓存一个图像对象并指定名字,如果存在则返回对象,如果匹配图像的对象不在缓存中,这个方法会从指定的文件中加载数据,并缓存它,然后返回结果对象。 

        作为替代,imageWithContendsOfFile 简单的载入图像并不会缓存。 

        这两个方法的的演示片段如下: 

    ?
    1
    2
    3
    4
    UIImage *img = [UIImage imageNamed:@ "myImage" ];  // caching
     
    // or
    UIImage *img = [UIImage imageWithContentsOfFile:@ "myImage" ];  // no caching


         如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。什么时候使用哪一种呢? 

         然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。 


    25) 尽可能避免日期格式化器


         如果你要用NSDateFormatter来解析日期数据,你就得小心对待了。之前提到过,尽量的重用NSDateFormatters总是一个好的想法。 

         然而,如果你需要更快的速度,你可以使用C代替NSDateFormatter来解析日期。 Sam Soffes写了一篇 blog post about this topic来说明如何用代码来解析 ISO-8601日期串。尽管如此,你可以很容易的修改他的代码例子来适应你的特殊需求。

         噢,听起来很棒,但是你相信有更好的办法吗? 

         如果你能控制你所处理日期的格式,尽可能的选择使用 Unix timestamps。Unix时间戳是简单的整数代表从某个起始时间点开始到现在的秒数。这个起始点通常是1970年1月1日 UTC 00:00:00。 

        你可以容易的把时间戳转换为NSDate,如下面所示: 

    ?
    1
    2
    3
    4
    5
    - (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
     
       return [NSDate dateWithTimeIntervalSince1970:timestamp];
     
    }

         这甚至比C函数更快

         注意,很多WEB APIs返回时间戳是毫秒,因为这对于javascript最终来使用和处理数据是非常常见的。只要记住将这个时间戳除以1000再传递给dateFromUnixTimestamp方法即可。

    展开全文
  • 网上有很多大拿分享的关于Android性能优化的文章,主要是通过各种工具分析,使用合理的技巧优化APP的体验,提升APP的流畅度,但关于内存优化的文章很少有看到。下面是我在实践过程中使用的一些方法,很多都是不太...
  • Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性、复杂度也随之增长,这样使得UI布局的优化,显得至关重要,UI布局不慎,就会引起过度...

    Android APP性能优化(最新总结)

    在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性、复杂度也随之增长,这样使得UI布局的优化,显得至关重要,UI布局不慎,就会引起过度绘制,从而造成UI卡顿的情况,本篇博客,主要总结了下目前所了解的优化。

    主要对一下几点进行说明
    布局优化 ,线程优化 ,App瘦身优化 , 页面切换优化 , App启动优化 , 内存优化(内存泄漏,内存溢出OOM)

    布局优化

    减少嵌套,避免过度加载。
    其实说白了就是减少层级,越简单越好,减少overdraw,就能更好的突出其性能:。

    1. **如果能使用RelativeLayout的,尽量不用linearlayout **
      在RelativeLayout和LinearLayout同时都能够满足需求时,尽量使用RelativeLayout,这一点可以从我们MainActivity引用默认布局就可以看出,默认是RelativeLayout,因为可以通过扁平的RelativeLayout降低LinearLayout嵌套所产生布局树的层级。可以自己用LinearLayout和RelativeLayout写个布局看看他们之间布局的不同。
      看下这两二布局的分级关系 首先LinearLayout
      在这里插入图片描述

    RelativeLayout在这里插入图片描述

    1. 布局中使用抽象布局标签include、merge、ViewStub
      在Android种系统对View进行测量、布局和绘制时,都是通过对View数的遍历来进行操作的。如果一个View数的高度太高就会严重影响测量、布局和绘制的速度。Google也在其API文档中建议View高度不宜哦过10层。现在版本种Google使用RelativeLayout替代LineraLayout作为默认根布局,目的就是降低LineraLayout嵌套产生布局树的高度,从而提高UI渲染的效率。
      布局复用,使用<www.taohuayuan178.com include>标签重用layout;
      提高显示速度,使用延迟View加载;
      减少层级,使用标签替换父级布局;
      注意使用wrap_content,会增加measure计算成本;
      删除控件中无用属性;

    include
    开发Android布局时,我们常将一些通用的视图提取到一个单独的layout文件中,然后使用标签在需要使用的其他layout布局文件中加载进来,比如我们自己App导航栏等。这样,便于对相同视图内容进行统一的控制管理,提高布局重用性。

    viewstub
    它是view的子类。他是一个轻量级View, 隐藏的,没有尺寸的View。他可以用来在程序运行时简单的填充布局文件
    merge
    merge标签存在的意义是帮助include标签排除多余的一层ViewGroup容器,减少view hierarchy的结构,提升UI渲染的性能。include标签存在着一个不好的地方,可能会导致产生多余的布局嵌套。

    1. Android最新的布局方式ConstaintLayout拖拽

    ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局。它与RelativeLayout非常相似,所有的view都依赖于兄弟控件和父控件的相对关系。但是,ConstraintLayout比RelativeLayout更加灵活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。那么怎么使用呢?
     首先是安装Constaintlayout了。Android SDK -> SDK Tools -> Support Repository中的ConstrainLayout for Android和Solver for ConstaintLayout。
     
    在这里插入图片描述
     然后build.gradle中添加:

    compile ‘com.android.support.constraint:constraint-layout:1.0.0-beta4’

    然后同步下就可以正常使用ConstaintLayout了。

    1. 利用Android Lint工具寻求可能优化布局的层次
       一些Lint规则如下:
        1、使用组合控件: 包含了一个ImageView以及一个TextView控件的LinearLayout如果能够作为一个组合控件将会被更有效的处理。
        2、合并作为根节点的帧布局(Framelayout) :如果一个帧布局时布局文件中的根节点,而且它没有背景图片或者padding等,更有效的方式是使用merge标签替换该Framelayout标签 。
        3、无用的叶子节点:通常来说如果一个布局控件没有子视图或者背景图片,那么该布局控件时可以被移除(由于它处于 invisible状态)。
        4、无用的父节点 :如果一个父视图即有子视图,但没有兄弟视图节点,该视图不是ScrollView控件或者根节点,并且它没有背景图片,也是可以被移除的,移除之后,该父视图的所有子视图都直接迁移至之前父视图的布局层次。同样能够使解析布局以及布局层次更有效。
        5、过深的布局层次:内嵌过多的布局总是低效率地。考虑使用一些扁平的布局控件,例如 RelativeLayout、GridLayout ,来改善布局过程。默认最大的布局深度为10 。

    线程优化

    1.不能通过非UI线程对View进行操作。因为Android的UI不是安全的,如果View能被不同的线程所访问或修改,那么就可能在程序的执行期间,产生不可预期的行为或者并发错误。
    2.使用线程时,避免在循坏中使用同步,因为获取和释放锁的操作代价很大。会引起CPU资源的损耗。
    3.处理多线程以及线程间通信时,使用HandlerThread来操作,它内部包装了Looper,记得不用的时候退出/释放资源哦。
    4.当工作线程与UI线程之间通信的时候,推荐使用AsyncTask(Android 7.0后内部任务变成串行处理,不再会出现以前并行时超过任务数执行饱和策略的情况)
    5.Loader可以用来代替AsyncTask的某些情况,因为Loader的生命周期是独立的(与Application Context有关),当Activity/Fragment销毁重建时,它仍然在,而且它特别使用异步操作,比如AsyncTaskLoader代替AsyncTask也可以实现后者的功能,但是生命周期完全独立于Activity。切记Loader使用完记得销毁。

    6.当你的Service不需要交互时,请使用可以自动停止的IntentService。
    7.当你希望延长BroadcastReceiver的生命周期时,例如启动一个后台线程IntentService。在onReceiver中调用BroadcastReceiver.goAsync(),它会返回一个PendingResult对象,这时,广播接收器的生命周期会延长持续到PendingResult.finish()方法调用。
    8.线程池最好用构造方法手动创建,而不要用Executors来直接调用工厂方法,这样利于明白线程池的运行规则,避免用了错误的线程池导致资源耗尽。
    9.给线程一个好听的名字,调试时候用。
    10.线程池设置线程的存活时间,以保证空闲线程准确释放。

    App瘦身优化

    App瘦身优化包括
    使用软件
    1.1 软件名字:Pngyu
    1.2 软件下载地址: windows版本下载地址mac版下载地址。具体使用方法网上可以搜一下哦。
    2. 删除冗余文件
     在开发的过程中,随着需求的变更,项目中会有一些冗余的资源文件例如图片,音频,plist文件,不用的第三方的文件等。需要我们去定期的清理。这个需要责任到人,某一个模块用到的资源进行分类,每个人对自己负责的模块要有owner精神。在需求发生变化的时候检查自己负责的模块是否有冗余的资源文件删除。
    3. 图片资源压缩
    一般情况UI给开发人员发的切图都是未经压缩的,所以我们在给app瘦身的时候需要对工程中的切图进行压缩操作。可以使用Pngyu进行压缩。
    4.合适的资源文件存取策略
    虽然我们采取了一些策略,但是随着app功能的增加,源代码文件,切图等资源文件还是在增加。所以我们就需要对资源文件的位置进行处理。对于使用频率较高的切图等资源文件,我们放进工程中,而对于app内容展示到,使用频率较低的我们可以考虑将这一类资源文件放台后台服务器上通过接口来返回,图片资源提供图片的url。再通过合适的缓存策略减少网络请求的次数,提升用户的体验。

    可能优化的点在哪?
    1、减少切片,大图,及背景图

    2、图片压缩,采用.9图片。

    3、采用压缩率更高的webp图片格式,代替当前的png格式

    4、清理不需要的布局文件,XML格式资源文件,图片资源

    5、删除一些用户量极少,“无意义”的功能

    6、检查第三方包,把不需要的组件、图片之类的删除或替换

    7、降低so库的体积

    8、把部分页面做成H5,客户端删除这部分功能

    9、自查代码,尤其是逻辑相似的部分,抽出去来,删除冗余代码

    具体实现
    1、lint使用

    采用lint工具,删除了大量无用的资源。有一定作用。Android studio集成了lint工具,检测“unused resoure“及unused declaration等。这里我们使用lint检测了无用的资源文件。
    另我的量一篇文章有介绍

    2、其他团队so库的体积减少,作用明显著,尤其类似于百度地图,直播软件

    3、代码冗余部分:效果非常有限,你再减少也少不了几kb

    4、使用图片压缩工具,有一定作用
    Google推荐图片压缩工具:https://developers.google.com/speed/docs/insights/OptimizeImages,
    市面上有许多工具可用来对JPEG和PNG文件执行进一步的无损压缩,且不会对图片质量造成任何影响。对于JPEG文件,我们建议您使用jpegtran或jpegoptim(仅适用于Linux;使用–strip-all选项运行)。对于PNG文件,我们建议使用OptiPNG或PNGOUT。

    5、H5页面,作用明显,而且H5适配非常简单啊

    6、支持插件so,插件支持网络加载so及更新原则,作用明显

    7、代码混淆,jar包资源混淆、
    8、TinyPng压缩图片  试一下测试 https://tinypng.com/

    TinyPng能够在视觉上几乎不
    影响图片的情况下显著压缩图片体积。
    从图中可以看到,压缩3次以后基本上体积就很难再减少了,压缩5次以后体积基本上就不变了,值得高兴的是,即使不停的压缩,图片依然不会太失真(但这是有损压缩,有时候alpha通道会给你压缩没了)。如下,压缩5次后的对比图:

    TinyPng好用的地方:

    1、图片压缩后对视觉影响不大,但体积显著减小

    2、可以批量压缩

    3、压缩后文件名与原文件名相同,可以直接替换,尤其是批量压缩的时候

    4、可以反复压缩5-6次,而不用担心太失真,肯定会失真啦,不影响视觉效果

    页面切换优化

    所谓界面和流畅度优化,就是尽可能多地消除用户可直接感知的、影响用户操作体验的bug

    1、人为在UI线程中做轻微耗时操作,导致UI线程卡顿

    人为避免一切耗时操作
    开启 StrictMode(严苛模式)

    在Activity里加入如下代码

    public void onCreate() {
         if (DEVELOPER_MODE) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectDiskReads()
                     .detectDiskWrites()
                     .detectNetwork()   // or .detectAll() for all detectable problems
                     .penaltyLog()
                     .build());
             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                     .detectLeakedSqlLiteObjects()
                     .detectLeakedClosableObjects()
                     .penaltyLog()
                     .penaltyDeath()
                     .build());
         }
         super.onCreate();
     }
    
    

    注意:StrictMode只适用于测试版本,千万不要在上线版本使用 StrictMode

    2、布局Layout过于复杂,无法在16ms内完成渲染
    利用drawableXXX属性来做有图文的控件,特别是类似设置页面中文字在左右边有剪头的View
    多用tools的属性例如tools:text tools:listitem

    3、同一时间动画执行的次数过多,导致CPU或GPU负载过重
    人为避免同一时间执行过多动画

    4、View过度绘制,导致某些像素在同一帧时间内被绘制多次
    简化布局嵌套
    减少不必要的背景颜色填充(用纯色图片代替颜色)

    在设置-> 开发者选项->调试GPU过度绘制中打开调试
    定位绘制区域
    利用Android提供的工具进行位置确认以及修改(HierarchyView,Tracer for OpenGL ES)
    定位到具体的视图(xml文件或者View)
    通过代码和xml文件分析过度绘制原因
    结合具体情况进行优化

    5、View频繁触发onMeasure,onLayout,导致onMeasure,onLayout累计耗时过多及整个View频繁的重新渲染
    使用系统性能分析工具systrace分析measure, layout耗时

    6、内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作
    使用内存分配跟踪工具Allocation Tracker工具跟踪对象的分配

    7、冗余资源及逻辑等导致加载和执行缓慢
    lint检查删除冗余
    去除重复库无用库,使用更小库
    去除无用的语言资源
    图片压缩webp
    开启ProGuard
    开启shrinkResources

    buildTypes{
    	release{
    		miniyEnabled true
    		shrinkResources true
    		proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    	}
    } 
    
    

    8、代码效率低
    考虑使用ArrayMap代替传统数据结构HashMap
    避免使用enums,推荐使用static
    避免在非Constants类中使用static

    其他详见《大话Java性能优化》

    9、其他
    对于不需要使用硬件加速的activity(没有动画效果、视频播放以及各种多媒体文件),不要在AndroidManifest.xml文件声明activity时添加“android:hardwareAccelerated=“true””关掉硬件加速可节省应用内存

    对于需要横竖屏转换的应用,又不想在横竖屏切换的时候重新跑onCreate方法,可以在AndroidManifest.xml文件声明Activity时添加“android:configChanges=“screenSize|orientation””

    为了减轻应用程序主进程的内存压力,对于耗内存比较多的界面(多媒体),可以在AndroidManifest.xml文件声明Activity时下添加“android:process=".processname"”单开一个进程,退出在退出这个界面的时候一定要在该界面的onDestory方法中调用System的kill方法来杀掉该进程;

    可以通过为application、activity自定义主题的方式来关掉多点触摸功能,只需要在自定义的主题下添加这两个标签:

      <item name="android:windowEnableSplitTouch">false</item>
      <item name="android:splitMotionEvents">false</item>
    
    

    App启动优化

    一、App启动分类
    1.冷启动 Cold start

    在启动应用前,系统还没有App的任何进程。比如设备开机后应用的第一次启动,系统杀掉应用进程 (如:系统内存吃紧引发的 kill 和 用户主动产生的 kill) 后 的再次启动等。那么自然这种方式下,应用的启动时间最长。

    2.热启动 Warm start
    当应用中的 Activities 被销毁,但在内存中常驻时,应用的启动方式就会变为暖启动。相比冷启动,暖启动过程减少了对象初始化、UI的布局和渲染。启动时间更短。但启动时,系统依然会展示一个空白背景,直到第一个 Activity 的内容呈现为止。

    3.温启动 Lukewarm start
    用户退出您的应用,但随后重新启动。该过程可能已继续运行,但应用程序必须通过调用onCreate()从头开始重新创建活动。系统从内存中驱逐您的应用程序,然后用户重新启动它。进程和Activity需要重新启动,但任务可以从保存的实例状态包传递到onCreate()中。

    启动速度优化主要是针对冷启动方式。下面看下冷启动的时候会做哪些工作。

    二、冷启动
    应用发生冷启动时,系统有三件任务要做:

    加载启动App;
    App启动之后立即展示出一个空白的Window;
    创建App的进程;
    创建App进程后,会马上执行以下任务:

    初始化应用中的对象 (比如 Application 中的工作);

    启动主线程 (UI 线程) ;

    创建第一个 Activity;

    加载内容视图 (Inflating) ;

    计算视图在屏幕上的位置排版 (Laying out);

    进行第一次绘制 (draw)。

    只有当应用完成第一次绘制,系统当前展示的空白背景才会消失,才会被 Activity 的内容视图替换掉。也就是这个时候,用户才能和我们的应用开始交互。下图展示了冷启动过程系统和应用的一个工作时间流:
    在这里插入图片描述

    三、优化思路
    作为普通应用,App进程的创建等环节我们是无法主动控制的。开发人员唯一能做的就是在Application 和 第一个 Activity 中,减少 onCreate() 方法的工作量,从而缩短冷启动的时间。像应用中嵌入的一些第三方 SDK,都建议在 Application 中做一些初始化工作,开发人员不妨采取懒加载的形式移除这部分代码,而在真正需要用到第三方 SDK 时再进行初始化。

    Google也给出了启动加速的方向:

    1、利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验; 
    2、 避免在启动时做密集沉重的初始化(Heavy app initialization); 
    3、 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等
    

    四、正确测量评估启动性能的方法
    1.display time
    从Android KitKat版本开始,Logcat中会输出从程序启动到某个Activity显示到画面上所花费的时间。这个方法比较适合测量程序的启动时间。

    在这里插入图片描述

    2.reportFullyDrawn
    我们通常来说会使用异步懒加载的方式来提升程序画面的显示速度,这通常会导致的一个问题是,程序画面已经显示,可是内容却还在加载中。为了衡量这些异步加载资源所耗费的时间,我们可以在异步加载完毕之后调用activity.reportFullyDrawn()方法来告诉系统此时的状态,以便获取整个加载的耗时。
    在这里插入图片描述
    3.Traceview
    告诉我们每一个方法执行了多长时间.这个工具可以通过 Android Device Monitor 或者从代码中启动。
    3.1 Android Device Monitor启动
    启动应用,点击 Start Method Tracing,应用启动后再次点击,会自动打开刚才操作所记录下的.trace文件,建议使用DDMS来查看,功能更加方便全面。

    在这里插入图片描述

    3.2 代码启动
    ①在onCreate开始和结尾打上trace

    Debug.startMethodTracing("GithubApp");
        ...
        Debug.stopMethodTracing();
    

    注意加读写权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    运行程序, 会在sdcard上生成一个”GithubApp.trace”的文件.

    ②通过adb pull将文件导出到本地

    adb pull /sdcard/GithubApp.trace ~/temp
    

    ③打开DDMS分析trace文件
    ④分析trace文件
    在这里插入图片描述

    在下方的方法区点击”Real Time/Call”, 按照方法每次调用耗时降序排.
    耗时超过500ms都是值得注意的.
    看左边的方法名, 可以看到耗时大户就是我们用的几大平台的初始化方法, 特别是Bugly, 还加载native的lib, 用ZipFile操作-等.
    点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的).
    点击方法时, 上方的该方法执行时间轴会闪动, 可以看该方法的执行线程及相对时长.
    4.Systrace
    在onCreate方法里面添加trace.beginSection()与trace.endSection()方法来声明需要跟踪的起止位置,系统会帮忙统计中间经历过的函数调用耗时,并输出报表。在这里插入图片描述
    5.adb命令计算 App 的启动时间

    adb shell am start -W packageName/packageName.activity
    

    例如:

    `adb shell am start -W com.media.painter/com.media.painter.PainterMainActivity
    Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.media.painter/.PainterMainActivity }
    Status: ok
    Activity: com.media.painter/.PainterMainActivity
    ThisTime: 355
    TotalTime: 355
    WaitTime: 365
    Complete
    

    详细请见:
    Android 中如何计算 App 的启动时间

    五、优化方案
    1.主题切换
    通过主题设置,不显示启动时的白屏背景。有以下几种方案:

    1.1 直接不显示白屏,直到程序初始化完毕直接显示第一个Activity

    <style name="LaunchStyle" parent="Theme.AppCompat.Light.DarkActionBar">
                ......
                <item name="android:windowIsTranslucent">true</item>
                <item name="android:windowNoTitle">true</item>
    </style>
    
    

       <style name="LaunchStyle" parent="Theme.AppCompat.Light.DarkActionBar">
            ......
            <item name="android:windowDisablePreview">true</item>
        </style>
    
    

    然后设置给第一个activity

        <activity 
            android:name=".MainActivity"
            android:theme="@style/LaunchStyle">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    
    

    然后在MainActivity中在加载布局之前,重新设置主题

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            setTheme(R.style.AppTheme);
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
        }
    
    

    效果
    添加链接描述

    内存优化(内存泄漏,内存溢出)

    内存溢出
    内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。此时软件或游戏就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件
    产生原因的区别

    内存溢出产生的原因:

    1. 内存中加载的数据量过于庞大,如一次从数据库中取出过多的数据

    2. 集合类中有对对象的引用,使用完后未清空

    3. 代码中存在死循环或循环产生过多重复的实体对象

    4. 使用的第三方软件中的bug

    5. 启动参数内存值设定的过小

    内存溢出的解决方案:

    1.  修改JVM启动参数,直接增加内存 (-Xms –Xms 参数一定不要忘记加)
      
    2.  检查错误日志查看 OutOfMemory 错误前是否有其他异常或错误
      
    3.  对代码进行分步运行分析,找出可能发生溢出的位置
      

    重点排查一下几点:

    1. 检查是否一次获取大量数据的查询,一般来说,一次获取十万条以上的记录到内存,就可能产生内存溢出.所以大家在开发的时候,需要考虑如果上线后需要获取大量数据,增加预防次问题,对此建议查询尽量使用分析查询

    2. 检查代码是否有死循环或递归调用

    3. 检查是否有大量循环重复产生新对象实体

    4. 检查List Map等集合对象是否使用完后,未清除的问题,List Map等集合对象始终会有对对象的引用,这样的对象便不会被GC回收

    其他详情 请 –https://blog.csdn.net/cp_panda_5/article/details/79613870

    内存泄漏

    内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
    内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。

    泄露原因

    • 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。

    • 常发性

    • 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

    • 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

    • 隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

    展开全文
  • App性能优化之内存优化

    千次阅读 2017-04-16 21:52:58
    App性能优化之内存优化本文为慕课网《App性能优化之内存优化》课程的学习笔记,视频地址 (http://www.imooc.com/video/13670)安卓系统内存分配与回收方式# 如何查看一个app在安卓系统中的内存分配情况? 1.启动...
  • Android App性能优化之内存优化

    千次阅读 2017-03-09 10:45:27
    为什么要进行内存优化?1.App运行内存限制,OOM导致App崩溃2.App性能:流畅性、响应速度和用户体验Android的内存管理方式Android系统内存分配与回收方式● 一个App通常就是一个进程对应一个虚拟机可以通过下面的命令...
  • App内存优化整理

    千次阅读 2017-03-11 12:10:49
    一个app通常就是一个进程对应一个虚拟机,使用adb shell --> ps可以查看所有进程信息,具体内存相关信息可以使用dumpsys meminfo packagename查看,如下: generic_x86:/ $ dumpsys meminfo xxx Applications Memory...
  • 题记:APP性能优化之内存优化三步论:这是为什么呢?怎么干?开始干为什么要进行内存优化1.APP运行内存限制,OOM导致APP崩溃。 2.APP性能:流畅性,响应速度用户体验 学习路线Android的内存管理方式1.Android系统...
  • Android APP内存优化之图片优化

    千次阅读 2015-07-14 20:11:15
     网上有很多大拿分享的关于Android性能优化的文章,主要是通过各种工具分析,使用合理的技巧优化APP的体验,提升APP的流畅度,但关于内存优化的文章很少有看到。在Android设备内存动不动就上G的情况下,的确没有...
  • App内存占用优化

    千次阅读 2017-01-12 18:47:04
    尽管ART(Android Runtime)与Dalvik虚拟机会执行常规的垃圾回收,但这并不意味着可以忽略App中的内存分配与释放。我们应当避免引起内存泄露,如持有静态成员变量而导致无法释放,应当在应用的生命周期回调中释放掉...
  • 由于项目里之前线上版本出现过一定比例的OOM,虽然比例并不大,但是还是暴露了一定的问题,所以打算对我们App分为几个步骤进行内存分析和优化,当然内存优化是个长期的过程,不是一两个版本的事,每个版本都需要...
  • APP性能-内存优化-内存管理认知

    千次阅读 2017-06-04 10:58:44
    前言作为一名Java程序员,我们不需要像C/C++那样为每一个new出来的对象手动delete/free释放内存。因为有GC(垃圾回收器)...所以要对内存使用进行优化,必须先熟悉Java的内存机制。1.了解Java的内存管理我们都知道andro
  • 做了较长时间的android开发了,发现其实android应用开发入门容易,但是进阶或者成为高级工程师,需要具备的基础能力还是非常高的:性能优化内存泄露、apk瘦身、热修复等等,这些都非常的考验一个人的能力。...
  • 手机APP内存资源优化方法研究
  • APP性能优化系列:内存优化-OOM详解

    千次阅读 2016-08-18 16:12:45
    Out Of Memory(内存溢出),我们都知道Android系统会为每个APP分配一个独立的工作空间, 或者说分配一个单独的Dalvik虚拟机,这样每个APP都可以独立运行而不相互影响!而Android对于每个 Dalvik虚拟机都会有一个最大...
  • 1、官方有篇文章,详细的写了,可以通过哪些工具查看内存占用的情况https://developer.android.com/studio/profile/investigate-ram.html2、较常用的 adb 命令,adb shell dumpsys meminfo &lt;packageName&...
  • (三)App 内存优化实战

    千次阅读 2020-03-08 21:40:56
    作为性能优化专栏的第五篇,阅读本文章前可以先阅读 (四)内存优化前奏篇(Java虚拟机、垃圾回收机制、内存泄漏/溢出/抖动,再阅读本篇文章效果更佳。 本篇文章就来讲一下在项目中如何使用工具来分析内存泄漏。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 117,864
精华内容 47,145
关键字:

内存优化app