美团App 插件化实践 ---https://tech.meituan.com/2017/10/12/android-hydra.html
这个思路是对的,但是远不够。例如,Google 自己的Support包里面的一个类
android.support.v7.view.ContextThemeWrapper会生成一个新的Theme保存:
ContextThemeWrapper会生成一个新的Theme保存: Theme()
ContextThemeWrapper会生成一个新的Theme保存:
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
...
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
final Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
...
}
getBaseContext.getTheme
pruneSupportContextThemeWrapper((ContextThemeWrapper) context, newAssetManager); // 清理Theme
pruneSupportContextThemeWrapper ContextThemeWrapper context,newAssetManager
,对应的虚拟机的指令是PUTFIELD/PUTSTATIC,以此为突破口,用ASM写一个MethodVisitor:
PUTSTATIC,以此为突破口,用ASM写一个MethodVisitor: ASM MethodVistor
static class MyMethodVisitor extends MethodVisitor {
static class MyMethodVisitor extends MethodVisitor {
}
MethodVisitor
我们将正常构建过程分为4个阶段: 1. 收集依赖 2. 处理资源 3. 处理代码 4. 打包签名
收集依赖 处理资源 处理代码
宿主解析依赖之后,分析插件的依赖,进行依赖仲裁和引用计数分析
宿主处理资源之前,处理插件资源,规避了资源访问的陷阱,生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源
宿主处理代码之中,规避插件API使用的陷阱,复用宿主的Proguard和Gradle插件,做到对原生构建过程的最大兼容。我们也修复了Proguard Mapping的问题,后续会有专门的博客介绍
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
构建系统的流程如下图
宿主 插件 Merge AAPT() Merge AAPT API 是的呢 我们也修复了Proguard Proguard
Merge AAPT
美团App 插件化实践 ---https://tech.meituan.com/2017/10/12/android-hydra.html
宿主处理代码之中,规避插件API使用的陷阱,复用宿主的Proguard和Gradle插件,
做到对原生构建过程的最大兼容。我们也修复了Proguard Mapping的问题,后续会有专门的博客介绍
Proguard Gradle
宿主处理代码之中,规避插件API使用的陷阱,复用宿主的Proguard和Gradle插件,做到对原生构建过程的最大兼容。
我们也修复了Proguard Mapping的问题,后续会有专门的博客介绍 Proguard Mapping的问题,后续会有专门的博客介绍
陷阱,生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源 Merge AAPT
,生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
keytool -importkeystore -srckeystore office -destkeystore office -deststoretype pkcs12
keytool -importkeystore -srckeystore D:\office\OfficeHorizontal\offices.jks -destkeystore D:\office\OfficeHorizontal\offices.jks -deststoretype pkcs12
https://blog.csdn.net/u012551120/article/details/106670917 ---- Android横竖屏切换,列表中item布局复用错乱
美团App 插件化实践 ---https://tech.meituan.com/2017/10/12/android-hydra.html
1. 收集依赖 2. 处理资源 3. 处理代码 4. 打包签名
为4个阶段: 1. 收集依赖 2. 处理资源 3. 处理代码 4. 打包签名
1,2,3,4,收集依赖 处理资源 处理代码 打包签名
1,
宿主解析依赖之后,分析插件的依赖,进行依赖仲裁和引用计数分析
宿主处理资源之前,处理插件资源,规避了资源访问的陷阱,生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源
宿主处理代码之中,规避插件API使用的陷阱,复用宿主的Proguard和Gradle插件,做到对原生构建过程的最大兼容。
我们也修复了Proguard Mapping的问题,后续会有专门的博客介绍
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
Proguard Mapping的问题,后续会有专门的博客介绍 Hash
Proguard Mapping的问题,后续会有专门的博客介绍 Hash
宿主处理资源之前,处理插件资源,规避了资源访问的陷阱,生成需要Merge的资源列表给宿主,开发
Proguard Gradle
生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源
Merge AAPT Merge Merge AAPT
Merge AAPT Merge Merge AAPT
super.visitMethodInsn
super.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/meituan/hydra/runtime/Transformer",//
"collectTheme",
"(Landroid/content/res/Resources$Theme;)V",
false);
super.visitMethodInsn Opcodes.INVOKESTATIC,
Opcodes.INVOKESTATIC
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
800 7 * 5 = 3500 - 800 =
美团App 插件化实践 ---https://tech.meituan.com/2017/10/12/android-hydra.html
亲测Unknown host mirrors.opencas.cn You may need to adjust the proxy settings in Gradle 解决办法 ---https://blog.csdn.net/GodnessIsMyMine/article/details/106517777
https://www.jianshu.com/p/20b1c14e048f ----Android dialog 去掉状态栏全屏显示
https://www.cnblogs.com/Owen-ET/p/10868620.html --- 【Github】之突然访问不了Github地址
https://run.mockplus.cn/exLeIPdGZAZraFke/index.html
为了实现在AAR集成方式和插件集成方式之间一键切换
AAR 集成方式 和插件
我们将正常构建过程分为4个阶段:
1. 收集依赖 2. 处理资源 3. 处理代码 4. 打包签名
1,收集依赖 2,处理资源 3,处理代码 ,4,打包签名
宿主解析依赖之后,分析插件的依赖,进行依赖仲裁和引用计数分析
宿主处理资源之前,处理插件资源,规避了资源访问的陷阱,生成需要Merge的资源列表给宿主,
开发 美团AAPT 处理插件资源
宿主处理代码之中,规避插件API使用的陷阱,复用宿主的Proguard和Gradle插件,
做到对原生构建过程的最大兼容。我们也修复了Proguard Mapping的问题,后续会有专门的博客介绍
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
宿主打包签名之前,构建插件APK,计算升级兼容的Hash特征,使用V2签名加快运行时的验证
Proguard( Gradle) -> Hash()特性
宿主处理资源之前,处理插件资源,规避了资源访问的陷阱,生成需要Merge的资源列表给宿主,开发 美团AAPT 处理插件资源
Merge是啥意思
android merge的作用,Android Merge详解 ---https://blog.csdn.net/weixin_29582621/article/details/117278372
启动 tools> hierarchyviewer.bat工具查看当前UI结构视图:
tools->hierarchyviewer.bat工具查看当前UI结构视图:
tools->hierarchyviewer.bat工具查看当前UI结构视图:
PhoneWindow DecorView
http://www.javashuo.com/article/p-ktkcxayq-kz.html --- Android Merge详解
https://www.jianshu.com/p/f1dbcf66b9e1 ---- android UI布局优化之merge标签
// 继承RelativeLayout
public class RadioPalyerHistoryView extends RelativeLayout {
public RadioPalyerHistoryView(@NonNull Context context) {
this(context, null);
}
public RadioPalyerHistoryView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadioPalyerHistoryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
// 必须attachToRoot为true
View view = LayoutInflater.from(context).inflate(R.layout.radio_palyer_history_right_layout, this, true);
// 跟布局是merge标签,在跟布局设置的padding不管用了,代码设置
setPadding(CommonUtils.dip2px(context,12),CommonUtils.dip2px(context,10),CommonUtils.dip2px(context,12),CommonUtils.dip2px(context,10));
}
RadioPalyerHistoryView(context,attrs)-> defStyleAttr,CommonUtils,AttributeSet
attchToRoot = true
// 跟布局是merge标签,在跟布局设置的padding不管用了,代码设置
根布局 merge的作用 = true attchToRoot = true
在LayoutInflater的inflate方法中,
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
XmlPullParser
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
//Trace.traceBegin Trace.TRACE_TAG_VIEW,inflate
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 省略
...
// merge标签必须有root,attachToRoot为true
merge -> root ,attachToRoot = A903
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
...
} catch (XmlPullParserException e) {
...
return result;
}
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("
}
parseInclude(parser, context, parent, attrs);
parseInclude parser,context,parent,attrs
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("
} else {
。 没有用merge标签的宽高属性,padding属性,直接addview到parent中
final View view = createViewFromTag(parent, name, context, attrs);
//createViewFromTag(parent,name,context,attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
viewGroup.generateLayoutParams
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
viewGroup.addView(view,params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
https://www.jianshu.com/p/f1dbcf66b9e1 ---- android UI布局优化之merge标签
XmlPullParser.END_DOCUMENT XmlPullParser.START_TAG
depth =
viewGroup.addView(view,params);---2022 - 5 - 20 待深入研究
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {----
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
理解MeasureSpec---- https://juejin.cn/post/6844904094205739016
MeasureSpec View.
系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,
然后再根据这个measureSpec来测量出View的宽/高。
LayoutParams->MeasureSpec
makeMeasureSpec()
SpecMode有三类,每一类都表示特殊的含义,如下所示。
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。
UNSPECIFIED() UNSPECIFIED
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。
它对应于LayoutParams中的match_parent和具体的数值这两种模式。
LayoutParams
EXACTLY
AT_MOST->SpecSize有三类,每一类都表示特殊的含义,如下所示。
AT_MOST 它对应于LayoutParams中的wrap_content。
找回密码界面 ->
makeMeasureSpec getMode getSize
理解MeasureSpec---- https://juejin.cn/post/6844904094205739016
MeasureSpec->LayoutParams()
MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec
MeasureSpec ->LayoutParams
对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,对于普通View
其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定 自身的LayoutParams
DecorView->MeasureSpec->LayoutParams
DecorView->MeasureSpec->LayoutParams
MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
MeasureSpec(onMeasure View)
于普通View来说,这里是指我们布局中的View, View的measure过程由ViewGroup传递而来,
先看一下ViewGroup的measureChildWithMargins方法:
measureChildWithMargins
子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,
此外还和View的margin及padding有关
MeasureSpec ,LayoutParams margin ,padding
理解MeasureSpec---- https://juejin.cn/post/6844904094205739016
一文读懂 View 的 Measure、Layout、Draw 流程----- https://juejin.cn/post/6939540905581887502
Window 再通过 ViewRootImpl 与 DecorView(视图树的根布局) 进行交互
Window ViewRootImpl DecorView Window ViewRootImpl DecorView