一、关于Applicationc对象创建过程后执行的第一个方法attachBaseContext的源码追踪
linux下的zygote进程fork出一个子进程,调用ActivityThread中的main方法,在main方法中初始化ActivityThread,并且调用ActivityThread的attach方法;在此方法汇总获得IActivityManager对象,并调用此对象的attachApplication方法,这里是个进程间通信,IActivityManager的实现类是ActivityManagerService,实现类的方法中调用了attachApplicationLocked方法,在此方法中调用了IApplicationThread的bindApplication方法,这个方法也是进程间通信,会回调的ThreadActivity类的bindApplication方法,此方法中发送一个message,调用handleBindApplication方法,在此方法中调用了
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
进行初始化,data.info是LoadedApk对象,调用此对象方法中的
Application app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
Instrumentation对象的newApplication方法进行返回Application对象的,在此方法中又调用了
Application app = (Application)clazz.newInstance();
app.attach(context);
最终,调用到Application对象中的attach方法,在此方法中,调用了attachBaseContext()方法,这个方法也是Application生命周期第一个执行的方法,可以在此方法中做需要提前做得业务;然后再调用onCreate方法;
二、关于ClassLoader加载机制
在Android系统中,我们通过context.getClassLoader获得对象都是PathClassLoader,通过调用其父类BaseDexClassLoader中的findClass方法来加载需要加载的类;在此方法中又调用DexPathList中的findClass方法,在此方法中遍历Element[]数组,这个数组中保存着apk解压后的所有classes.dex文件,通过传进来的参数className来从classes.dex中找到需要加载的类;
三、关于热修复加载机制
从5.0以后,android系统内部对第一次安装的app进行了优化;我们会发现5.0以后安装的速度比之前版本慢了许多,这是典型的:时间换空间;这是因为在第一次安装时,系统会把安装的apk解压到data/user/包名/app_odex目录下;这样以后启动app时,直接从这个文件夹下读取dex文件进行findClass,而不是从安装的apk中解压读取,这样能大大提升app运行的速度;
由于Android自身的加载dex文件的系统漏洞:按照顺序加载dex文件(dex顺序加载机制),在加载需要加载类时,按照dex文件的顺序中去找,直到找到为止,找到后就停止不在继续找,即使后续的dex文件中也存在一个同样的类文件;热修复tinker正是利用这个系统漏洞,通过把我们修复的dex文件放到dex数组的前面进行截胡(加载)实现的;而阿里的Andfix使用的是jvm的特性进行热修复的;
四、build-tool命令的使用:
- 1.将jar生成dex文件
命令所在位置:sdk\build-tools,将classes.jar生成dex文件
生成命令:
dx --dex --output out.dex classes.jar
- 2.对齐命令
zipalign
sdk\build-tools ,apk整理对齐工具。
未压缩的数据开头均相对于文件开头部分执行特定的字节对齐,减少应用运行内存。
https://developer.android.google.cn/studio/command-line/zipalign.html
zipalign [-v] [-f] 4 in.apk out.apk
- 3.签名
apksigner
sdk\build-tools\24.0.3 以上,apk签名工具
https://developer.android.google.cn/studio/command-line/apksigner.html#options-sign-general
签名命令:
apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out out.apk in.apk
- 4.签名校验
校验:
apksigner verify -v out.apk
五、合并Element数组示例
package com.zcbl.airport_assist.proxy_guard_core;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import static android.content.ContentValues.TAG;
/**
* Created by serenitynanian on 2018/10/23.
* 合并自己的Element数组和系统的Element数组
*/
public class CombineElement {
public static final String DEX_DIR = "odex";
public static HashSet loadedDex = new HashSet<>();
static {
loadedDex.clear();
}
/**
* 执行此方法前,先执行loadExternalDexToOdex()方法,将外置卡里的dex转移到data/user/包名/app_odex文件夹下
*
* @param context
*/
public static void loadDex(Context context) {
if (context == null) {
return;
}
File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
File[] listFiles = filesDir.listFiles();
for (File file : listFiles) {
if (file.getName().startsWith("classes") || file.getName().endsWith(".dex")) {
Log.i("INFO", "dexName:" + file.getName());
loadedDex.add(file);
}
}
String optimizeDir = filesDir.getAbsolutePath() + File.separator + "opt_dex";
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
for (File dex : loadedDex) {
try {
// -----------------------系统的ClassLoader------------------------------------
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
Class baseDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListFiled = baseDexClazzLoader.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathListObject = pathListFiled.get(pathClassLoader);
Class systemPathClazz = pathListObject.getClass();
Field systemElementsField = systemPathClazz.getDeclaredField("dexElements");
systemElementsField.setAccessible(true);
Object systemElements = systemElementsField.get(pathListObject);
DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), fopt.getAbsolutePath(), null, context.getClassLoader());
// ------------------自己的ClassLoader--------------------------
Class myDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
Field myPathListFiled = myDexClazzLoader.getDeclaredField("pathList");
myPathListFiled.setAccessible(true);
Object myPathListObject = myPathListFiled.get(classLoader);
Class myPathClazz = myPathListObject.getClass();
Field myElementsField = myPathClazz.getDeclaredField("dexElements");
myElementsField.setAccessible(true);
Object myElements = myElementsField.get(myPathListObject);
//------------------------融合-----------------------------
Class> sigleElementClazz = systemElements.getClass().getComponentType();
int systemLength = Array.getLength(systemElements);
int myLength = Array.getLength(myElements);
int newSystenLength = systemLength + myLength;
//生成一个新的 数组 类型为Element类型
Object newElementsArray = Array.newInstance(sigleElementClazz, newSystenLength);
for (int i = 0; i < newSystenLength; i++) {
if (i < myLength) {
Array.set(newElementsArray, i, Array.get(myElements, i));
} else {
Array.set(newElementsArray, i, Array.get(systemElements, i - myLength));
}
}
//-------------------融合完毕 将新数组 放到系统的PathLoad内部---------------------------------
Field elementsField = pathListObject.getClass().getDeclaredField("dexElements");
elementsField.setAccessible(true);
elementsField.set(pathListObject, newElementsArray);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 加载dex文件集合
*
* @param dexFiles
*/
private void loadDex(Context context, List dexFiles, File optimizedDirectory) throws
NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
InvocationTargetException {
/**
* 1.获得 系统 classloader中的dexElements数组
*/
//1.1 获得classloader中的pathList => DexPathList
Field pathListField = ReflectUtils.findField(context.getClassLoader(), "pathList");
Object pathList = pathListField.get(context.getClassLoader());
//1.2 获得pathList类中的 dexElements
Field dexElementsField = ReflectUtils.findField(pathList, "dexElements");
Object[] dexElements = (Object[]) dexElementsField.get(pathList);
/**
* 2.创建新的 element 数组 -- 解密后加载dex
*/
//5.x 需要适配6.x 7.x
Method makeDexElements;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <
// Build.VERSION_CODES.M) {
makeDexElements = ReflectUtils.findMethod(pathList, "makeDexElements", ArrayList.class,
File.class, ArrayList.class);
// } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
// makeDexElements = Utils.findMethod(pathList, "makePathElements", ArrayList.class,
// File.class, ArrayList.class);
// }
ArrayList suppressedExceptions = new ArrayList();
Object[] addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,
optimizedDirectory,
suppressedExceptions);
/**
* 3.合并两个数组
*/
//创建一个数组
Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass()
.getComponentType(), dexElements.length +
addElements.length);
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
System.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);
/**
* 4.替换classloader中的 element数组
*/
dexElementsField.set(pathList, newElements);
}
/**
* 将外置卡里面的dex文件转移到data/user/包名/app_odex文件下
*
* @param context
*/
private void loadExternalDexToOdex(Context context) {
File filesDir = context.getDir("odex", Context.MODE_PRIVATE);
String name = "out.dex";
String filePath = new File(filesDir, name).getAbsolutePath();
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
InputStream is = null;
FileOutputStream os = null;
try {
Log.i(TAG, "fixBug: " + new File(Environment.getExternalStorageDirectory(), name).getAbsolutePath());
is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), name));
os = new FileOutputStream(filePath);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
File f = new File(filePath);
if (f.exists()) {
Toast.makeText(context, "dex overwrite", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
四、反射得到指定对象的Method和Field
package com.zcbl.airport_assist.proxy_guard_core;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Created by serenitynanian on 2018/10/23.
*/
public class ReflectUtils {
/**
* 反射获得 指定对象(当前->父类—>父类..) 中的 成员属性
* @param object
* @param name
* @return
*/
public static Field findField(Object object, String name) throws NoSuchFieldException {
Class> aClass = object.getClass();
while (null != aClass) {
try {
Field declaredField = aClass.getDeclaredField(name);
//如果无法访问 设置可访问
if (!declaredField.isAccessible()) {
declaredField.setAccessible(true);
}
} catch (NoSuchFieldException e) {
// e.printStackTrace();
//如果找不到 往父类中找
aClass = aClass.getSuperclass();
}
}
throw new NoSuchFieldException("Field "+name+" not found in "+object.getClass());
}
/**
* 反射获得 指定 对象(当前->父类—>父类..) 中的 方法
* @param object
* @param name
* @param parametersTypes
* @return
*/
public static Method findMethod(Object object, String name,Class... parametersTypes) throws NoSuchMethodException {
Class> aClass = object.getClass();
while (null != aClass) {
try {
Method declaredMethod = aClass.getDeclaredMethod(name, parametersTypes);
if (!declaredMethod.isAccessible()) {
declaredMethod.setAccessible(true);
}
} catch (NoSuchMethodException e) {
// e.printStackTrace();
//如果找不到 往父类中找
aClass = aClass.getSuperclass();
}
}
throw new NoSuchMethodException("Method "+name +" witd parameters "+ Arrays.asList(parametersTypes)+
" not found in "+object.getClass());
}
}
六、OpenSSL的使用与编译
密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议;
https://www.openssl.org/source/
https://wiki.openssl.org/index.php/Android
具体使用步骤
- 1、下载:使用linux下载命令:wget进行下载
wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz - 2、解压
tar xvf openssl-1.1.0g.tar.gz - 3、编译脚本
复制https://wiki.openssl.org/images/7/70/Setenv-android.sh中的内容,修改以 '_' 开头的变量( _ANDROID_NDK 不用管,《export ANDROID_NDK_ROOT=NDK目录》 即可)
在末尾加上
./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=pwd
/android/x86/openssl --prefix=pwd
/android/x86
make depend
make all
sudo -E make install CC=ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
(arm-linux-androideabi- 如果是x86需要为i686-linux-android-)
备注:动态获取AndroidManifest.xml中的的meta-data值
private void getAndroidManifestMetaData(){
PackageManager packageManager = getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(getPackageName(),
PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if (null != metaData) {
if (metaData.containsKey("app_name")) {
String app_name = metaData.getString("app_name");
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}