Android 开发高手课 温故知新篇

栏目: Java · 发布时间: 6年前

内容简介:首先推荐大家先阅读《Android 开发高手课》和我之前的三篇练习:最近二刷了《Android 开发高手课》,对于老师提到的一些案例,自己实践了一下。分享给学习此专栏的大家:这个是在第二课崩溃优化(下)中提到的一个问题。

首先推荐大家先阅读《Android 开发高手课》和我之前的三篇练习:

最近二刷了《Android 开发高手课》,对于老师提到的一些案例,自己实践了一下。分享给学习此专栏的大家:

1.Android 7.0 Toast的BadTokenException

这个是在第二课崩溃优化(下)中提到的一个问题。

当然,Google在Android 8.0修复了这个问题,其实就是捕获了一下异常:

try {
   mWM.addView(mView, mParams);
   trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
   /* ignore */
}
复制代码

所以如果要避免这个问题,我们就要找到HOOK点。7.0 Toast源码里面有一个变量叫 mTN,它的类型为 handler,我们只需要代理它就可以实现捕获。

简单实现代码如下:

private void hook(Toast toast) {
    try {
        Field tn = Toast.class.getField("mTN");
        tn.setAccessible(true);
        Field handler = tn.getType().getField("mHandler");
        handler.setAccessible(true);
		Field callback = handler.getClass().getField("mCallback");
		callback.setAccessible(true);
		// 替换
        callback.set(handler, new NewCallback((Handler) handler.get(tn.get(toast))));
     } catch (Exception e) {
        e.printStackTrace();
     }       
}

public class NewCallback implements Handler.Callback {

    private final Handler mHandler;

    public NewCallback(final Handler handler) {
        this.mHandler = handler;
    }

    @Override
    public boolean handleMessage(final Message msg) {
        try {
            this.mHandler.handleMessage(msg);
        } catch (final RuntimeException e) {}
        return true;
    }
}
复制代码

在前一阵didi开源的 booster 中也有对此问题的 修复 ,看了后我觉得考虑的更加完善。有兴趣的可以看看。

当然了, Toast 的小问题还有不少,相关的开源修复方案也很多,具体可以参考这篇文章。

2.Dex文件的类重排

这个是在第八课启动优化(下)中提到的一个优化启动速度的方法。主要使用redex工具来实现,对redex用法不熟悉的,可以看我之前的文章。

带来的好处:

  • 更少的IO:避免在多个dex文件中的读取。
  • 减少内存使用量:避免提前加载不必要的类。
  • 更少page cache污染。

使用方法:

  • 首先连接手机,获取应用进程的pid
adb shell ps | grep YOUR_APP_PACKAGE | awk '{print $2}'
复制代码
  • 获取Heap Dump文件
// 生成
adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof

// 获取
adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE
复制代码
  • 获取类加载顺序文件
python redex/tools/hprof/dump_classes_from_hprof.py --hprof SOMEDUMP.hprof > list_of_classes.txt
复制代码

我在这里遇到了些问题,首先这个 python 脚本只支持python2,同时需要安装 pip、enum、enum34。否则会提示类似 pip command not found 这样的错误。

pip是python的包管理工具,在Python2.7的安装包中,easy_install.py是默认安装的,而pip需要我们手动安装

安装方法如下:

sudo easy_install pip

sudo pip install enum

sudo pip install enum34
复制代码

还有这个脚本 不支持8.0以上 的设备生成的 DUMP.hprof 文件。

如果想获取8.0设备的类加载顺序,可以参考老师文中复写 ClassLoader 的方案。

以上完成后我们就获取到了一个 list_of_classes.txt 文件,大致格式如下:

com/bumptech/glide/load/resource/bitmap/BitmapResource.class
java/lang/Integer.class
android/graphics/Bitmap.class
libcore/util/NativeAllocationRegistry.class
java/lang/ref/FinalizerReference$Sentinel.class
java/lang/ref/FinalizerReference.class
libcore/util/NativeAllocationRegistry$CleanerThunk.class
sun/misc/Cleaner.class
libcore/util/NativeAllocationRegistry$CleanerRunner.class
android/graphics/Canvas.class
android/graphics/Paint.class
android/graphics/BitmapShader.class
android/graphics/RectF.class
java/util/TreeMap$TreeMapEntry.class
okhttp3/Request$Builder.class
okhttp3/Headers$Builder.class
java/util/ArrayList.class
okhttp3/HttpUrl$Builder.class
java/lang/String.class
java/lang/StringBuffer.class
android/icu/impl/ReplaceableUCharacterIterator.class
android/icu/text/ReplaceableString.class
okhttp3/HttpUrl.class
java/util/Collections$UnmodifiableRandomAccessList.class
okio/Buffer.class
java/lang/StringBuilder.class
java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1.class
java/util/HashMap$EntryIterator.class
java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry.class
okhttp3/Request.class
okhttp3/Headers.class
com/bumptech/glide/load/resource/bitmap/LazyBitmapDrawableResource.class
...
复制代码
  • 最后执行优化命令
redex --sign -s test.jks -a key0 -p 111111 -c interdex.config -P proguard-rules.pro -o app_1.apk app.apk
复制代码

其中 interdex.config 文件配置如下:

{
  "redex" : {
    "passes" : [
      "InterDexPass"
    ]
  },
  "coldstart_classes" : "list_of_classes.txt"
}
复制代码

我们可以使用 010 Editor 来查看我们的前后对比。

Android 开发高手课 温故知新篇
Android 开发高手课 温故知新篇

观察第二张图可以看到,就是我们 list_of_classes.txt 文件中的顺序。(Interdex Pass将忽略它在apk中找不到相应的类)

其实,Google在Android 8.0的ART优化中也有引用一个叫dexlayout的来实现类和方法的重排,大家可以了解一下。

3.使用FileVisitor替代 ListFiles

在第十三课存储优化(中)中,老师提到文件遍历在 API 26 之后建议使用 FileVisitor 替代 ListFiles ,因为文件遍历的耗时跟文件夹下的文件数量有关。老师文中说道:“曾经我们出现过一次 bug 导致某个文件夹下面有几万个文件,在遍历这个文件夹时,用户手机直接重启了。”

一般的文件夹删除方法:

public static void deleteDir(String path) {
		File dir = new File(path);
		if (dir == null || !dir.exists() || !dir.isDirectory()){
			return;
		}

		for (File file : dir.listFiles()) {
			if (file.isFile()){
				file.delete();
			}else if (file.isDirectory()){
				deleteDir(path);
			}
		}
		dir.delete();
	}
复制代码

就是利用 dir.listFiles() 方法递归删除子文件。FileVisitor是 Java 7的新特性之一,在Android 8.0开始支持。所以完善后的删除文件方法如下:

public static void deleteDir(String path) throws IOException {
        File dir = new File(path);
        if (dir == null || !dir.exists() || !dir.isDirectory()){
            return;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>(){
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    //表示继续遍历
                    return FileVisitResult.CONTINUE;
                }
                /**
                 * 访问某个path失败时调用
                 */
                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    //如果目录的迭代完成而没有错误,有时也会返回null
                    if (exc == null) {
                        Files.delete(file);
                        return FileVisitResult.CONTINUE;
                    } else {
                        throw exc;
                    }
                }
            });
        }else {
            for (File file : dir.listFiles()) {
                if (file.isFile()){
                    file.delete();
                }else if (file.isDirectory()){
                    deleteDir(path);
                }
            }
        }
        dir.delete();
    }
复制代码

4.PrecomputedText

第一次听到 PrecomputedText 就是在二十一课UI优化(下)中,它可以异步进行 measure 和 layout。我也写了一篇博客,有兴趣可以点击查看。

5. Litho

Litho 如我前面提到的 PrecomputedText 一样,把 measure 和 layout 都放到了后台线程,只留下了必须要在主线程完成的 draw,这大大降低了 U线程的负载。

具体的原理我就不介绍了,大家可以参看美团技术团队前一阵分享的文章: 基本功 | Litho的使用及原理剖析 。我主要说说我的使用过程。

我用 LithoRecyclerCollectionComponent 实现了和我在 PrecomputedText 博客中相同的例子。代码很简单,我就不贴出来了,有兴趣的可以查看 Github 。我们直接看看效果:

Android 开发高手课 温故知新篇 Android 开发高手课 温故知新篇

帧数效果来说略逊色 PrecomputedText ,但是 Litho 支持的组件更多,不单单是 TextView ,还有 ImageEditView 等。并且它还能实现界面扁平化、占用更少的内存的优点。据说给美团App带来了不错的性能提升,官方文档也很详细,所以还是值得大家尝试的。

好了,暂时就分享这么多了。如果对你有启发帮助,希望可以 点赞支持


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning ARKit for iPhone and iPad

Beginning ARKit for iPhone and iPad

Wallace Wang / Apress / 2018-11-5 / USD 39.99

Explore how to use ARKit to create iOS apps and learn the basics of augmented reality while diving into ARKit specific topics. This book reveals how augmented reality allows you to view the screen on ......一起来看看 《Beginning ARKit for iPhone and iPad》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试