Android项目复盘2

2024-06-01 06:08
文章标签 android 项目 复盘

本文主要是介绍Android项目复盘2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

个人主页:https://chengang.plus/

文章将会同步到个人微信公众号:Android部落格

2、系统数据检查

2.1 dex更新

我们开发的java代码通过编译生成.class文件,然后通过dx工具生成机器可以识别的dex文件。

Android中采用ClassLoader加载dex文件,加载完成之后可以通过反射调用其中的方法,适合那些不依赖文件等资源的业务,而打点恰好比较适合使用dex加载的方式。

Android中有PathClassLoader和DexClassLoader ,他们都继承自ClassLoader。他们的继承关系如下:

  • DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
  • PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件。
  • BaseDexClassLoader是DexClassLoader和PathClassLoader的父类,针对传入不同的参数做差异化处理。

看看三个类的源码:

2.1.1 PathClassLoader

dalvik/system/PathClassLoader.java

public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}
2.1.2 DexClassLoader

dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}
2.1.3 BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {private final DexPathList pathList;public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {this(dexPath, optimizedDirectory, librarySearchPath, parent, false);}public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent, boolean isTrusted) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);if (reporter != null) {reportClassLoaderChain();}}public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {// TODO We should support giving this a library search path maybe.super(parent);this.pathList = new DexPathList(this, dexFiles);}

在BaseDexClassLoader中通过DexPathList类具体的处理Dex,他的构造函数如下:

2.1.4 DexPathList
private Element[] dexElements;
DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) {this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);
}private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {Element[] elements = new Element[files.size()];int elementsPos = 0;for (File file : files) {if (file.isDirectory()) {elements[elementsPos++] = new Element(file);} else if (file.isFile()) {String name = file.getName();DexFile dex = null;dex = loadDexFile(file, optimizedDirectory, loader, elements);}}retrun elements;
}private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {if (optimizedDirectory == null) {return new DexFile(file, loader, elements);} else {String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);}
}

最终通过DexFile加载loadDex方法在native层实现对dex的加载和处理。

而分析PathClassLoaderDexClassLoader的构造函数可以看到Android 9.0中的DexClassLoader构造函数的optimizedDirectory参数默认是null。所以这里要针对版本的不同做差异化处理。

我们一般加载Dex的方式是:

classLoader = context.getClassLoader();
classLoader.loadClass("you class path");

这里做一下差异化:

public ClassLoader load(Context context,String dexName) {mLoaded = false;ClassLoader classLoader = null;File dexOutputDir = context.getDir("dex", 0);File dexFile = new File(dexOutputDir.getAbsolutePath(), dexName);Log.d(TAG, "load start");if (!dexFile.exists()) {return null;}String dexPath = dexFile.getAbsolutePath();Log.d(TAG, "dPath = " + dexPath);int version = android.os.Build.VERSION.SDK_INT;if (version >= 25) {BaseDexClassLoader parent = (BaseDexClassLoader) context.getClassLoader();Class<BaseDexClassLoader> c = BaseDexClassLoader.class;Method method;try {method = c.getMethod("addDexPath", String.class);method.invoke(parent, dexPath);mLoaded = true;classLoader = parent;return classLoader;} catch (Exception e) {e.printStackTrace();}}Log.d(TAG, "mLoaded1 = " + mLoaded);if (!mLoaded) {ArrayList<File> files = new ArrayList<File>();files.add(dexFile);classLoader = context.getClassLoader();classLoader.loadClass("you class path");try {Field pathListField = findField(classLoader, "pathList");Object dexPathList = pathListField.get(classLoader);ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();if (version < 19) {expandFieldArray(dexPathList, "dexElements",makeDexElements(dexPathList, files, null));} else if (version < 23) {expandFieldArray(dexPathList, "dexElements",makeDexElements(dexPathList, files, null, suppressedExceptions));} else {expandFieldArray(dexPathList, "dexElements",makePathElements(dexPathList, files, null, suppressedExceptions));}} catch (Exception e1) {e1.printStackTrace();}}Log.d(TAG, "mLoaded2 = " + mLoaded);if (mLoaded) {return classLoader;}return null;
}
  • SDK_INI大于等于25

当sdk_int大于等于25时,通过反射BaseDexClassLoader的addDexPath方法直接添加dex文件到DexPathList的Element[]数组中,而后续findClass方法的逻辑就是遍历这个数据找到对应的dex文件。

  • SDK_INI小于25

这种情况下,先反射获取ClassLoader的pathList对象,这里的ClassLoader实际是PathClassLoader,但是最终都会到BaseDexClassLoader的pathList。

获取到这个变量之后,先调用makeDexElements方法将生成的dex对象放到一个数组中,接着在expandFieldArray方法中将就的dex数组和新的dex数组合并:

private Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
}

这里的makeDexElements方法对应的是DexPathListmakeDexElements方法,最终目的是将dex对象添加到Element[]数组中,作为新的数组返回。

expandFieldArray对应的是DexPathListaddDexPath方法,将新旧Element[]数组合到一个数组中,旧的数组在前面。这样就导致了相同文件名的dex文件,最新修复了bug的dex不能立即生效。

private void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {Field jlrField = findField(instance, fieldName);Object[] original = (Object[]) jlrField.get(instance);Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);System.arraycopy(original, 0, combined, 0, original.length);System.arraycopy(extraElements, 0, combined, original.length,extraElements.length);jlrField.set(instance, combined);mLoaded = true;Log.d(TAG, "expandFieldArray");
}

对比看下DexPathList中的addDexPath方法:

public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) {final List<IOException> suppressedExceptionList = new ArrayList<IOException>();final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptionList, definingContext, isTrusted);if (newElements != null && newElements.length > 0) {final Element[] oldElements = dexElements;dexElements = new Element[oldElements.length + newElements.length];System.arraycopy(oldElements, 0, dexElements, 0, oldElements.length);System.arraycopy(newElements, 0, dexElements, oldElements.length, newElements.length);}
}

可见我们自己的操作对应着DexPathListaddDexPath方法。

在我们自己的expandFieldArray方法最后通过执行jlrField.set(instance, combined);,将合并后的Element[]数组赋值给DexPathListElement[] dexElements

当上述操作完成之后,就要调用loadClass方法加载dex文件中的类了,这个方法在ClassLoader类中定义:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{Class<?> c = findLoadedClass(name);if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}if (c == null) {c = findClass(name);}return c;            
}

parentContext.getClassLoader所属的PathClassLoader传递,一直从BaseDexClassLoaderClassLoader

到这里要熟悉应用被创建初始化的流程了,这里先不引申过去,只需要知道这个parent是BootClassLoader类型。

@Override
protected Class<?> loadClass(String className, boolean resolve)throws ClassNotFoundException {Class<?> clazz = findLoadedClass(className);if (clazz == null) {clazz = findClass(className);}return clazz;
}

如果还找不到,就调用BaseDexClassLoaderfindClass方法了。

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();Class c = pathList.findClass(name, suppressedExceptions);return c;
}

到这里如果还找不到就抛异常出来了,ClassNotFoundException

上边的流程就是各大博客上面说的双亲委派机制,父类先从已经加载的类里面找,找不到的话,再从自己BaseDexClassLoaderfindClass方法里面去找。

2.1.5 问题复盘

到这里,基本就将Dex加载的流程搞清楚了,但是这样的加载会导致新加载的Dex不能立即生效,必须重新启动应用之后才能生效。针对这种问题,可以将Dex热更新模块放到一个单独的进程中,当Dex加载完毕之后,调用killProcess方法自杀,然后由另一个进程拉活重启。

这篇关于Android项目复盘2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020183

相关文章

用Microsoft.Extensions.Hosting 管理WPF项目.

首先引入必要的包: <ItemGroup><PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /><PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /><PackageReference Include="Serilog

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

React+TS前台项目实战(十七)-- 全局常用组件Dropdown封装

文章目录 前言Dropdown组件1. 功能分析2. 代码+详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局Dropdown组件封装,可根据UI设计师要求自定义修改。 Dropdown组件 1. 功能分析 (1)通过position属性,可以控制下拉选项的位置 (2)通过传入width属性, 可以自定义下拉选项的宽度 (3)通过传入classN

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。