(二) 架构师晋级

2. 架构师晋级

一. 换肤核心技术

1. 首先分析View的创建流程

即分析AppCompatActivity的setContentView(R.layout.activity_main);

1
2
3
4
5
6
7
8
   public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.
setContentView(R.layout.activity_main);
}
}
1
2
3
4
5
6
7
8
9
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {

@Override
public void setContentView(@LayoutRes int layoutResID) {
//2.
getDelegate().setContentView(layoutResID);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AppCompatDelegate {
//3.
public abstract void setContentView(@LayoutRes int resId);
}


class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2 {

//4. setContentViews是在这里实现的
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//我们看这个inflate方法,看是怎么填充view的
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public abstract class LayoutInflater {
//5.
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//跟进这个方法
return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
//跟进这个方法
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// ... 省略部分代码
// Temp is the root view that was found in the xml
// 跟进这个方法
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
result = temp;
// ... 省略部分代码
return result;
}

@UnsupportedAppUsage
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
//6. 马上就能看到是怎么创建view的了
return createViewFromTag(parent, name, context, attrs, false);
}
@UnsupportedAppUsage
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// ... 省略部分代码
try {
//7. 在这里创建view ->看下面的分析8
View view = tryCreateView(parent, name, context, attrs);
//9. 如果我们拦截view的创建后,还是为空,则走系统的方法
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
//控件名有[.]的则是自定义的控件,走这个方法
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}

return view;
// ...
}

//8. 分析
public final View tryCreateView(@Nullable View parent, @NonNull String name,
// ....
View view;
// 如果我们提前做了工作,让mFactory2!=null,则调用了mFactory2.onCreateView
//我们在Actitivy中重写onCreateView 则拦截住了系统创建view的过程
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}

if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

return view;
}
}

综上分析,Activity在setContentView(R.layout.activity_main)的时候,通过mFactory2.onCreateView来创建view,我们在Activity中重写onCreateView拦截系统的方法,来创建自己可以实现换肤的控件,没有拦截的控件还是走系统的方法去创建。

既然找到了需要拦截的方法,那我们再看在哪里可以拦截这个方法。开始分析源码:

2. 源码分析,寻找突破点,即找一个可以换肤的节点。

1
2
3
4
5
6
7
8
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//1. 进入onCreate
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, 
SupportParentable, DelegateProvider {

protected void onCreate(@Nullable Bundle savedInstanceState) {
AppCompatDelegate delegate = this.getDelegate();
//2. 进入installViewFactory
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && this.mThemeId != 0) {
if (VERSION.SDK_INT >= 23) {
this.onApplyThemeResource(this.getTheme(), this.mThemeId, false);
} else {
this.setTheme(this.mThemeId);
}
}

super.onCreate(savedInstanceState);
}
}
1
2
3
4
5
6
7
8
public abstract class AppCompatDelegate {
// If you are using your own Factory or Factory2 then you can omit this call, and // instead call createView(android.view.View, String, android.content.Context,
// android.util.AttributeSet) from your factory to return any compatible widgets.
// 源码注释:如果你想使用自定义的Factory or Factory2,要避免调用此方法,并且重写factory的 // createView方法

//3. installViewFactory是一个抽象方法,进入它的实现类继续查看
public abstract void installViewFactory();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class AppCompatDelegateImpl extends AppCompatDelegate implements Callback, Factory2 {
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
// 在这里进行了判null的操作,如果我们自己设置了Factory,就阻止了下面的操作,就可以实现自定义
if (layoutInflater.getFactory() == null) {
//4. 此类实现了Factory2,因为setFactory2(layoutInflater, this)第二个参数是
//this,即把Factory2的实现类传进去,实现类实现了onCreateView方法。
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}

/**
* From {@link LayoutInflater.Factory2}.
* 5. 实现Factory2的onCreateView方法,在onCreateView方法又调用了createView方法
*/
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}

/**
* From {@link LayoutInflater.Factory2}.
*/
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}


@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
//7. 在这个createView方法里就是做了new AppCompatViewInflater()这个工作
// ... 省略部分代码
mAppCompatViewInflater = new AppCompatViewInflater();
// ... 省略部分代码

//8. 最后调用了刚才实例化的AppCompatViewInflater的createView方法
return mAppCompatViewInflater.createView(parent, name, context, attrs,
inheritContext,
IS_PRE_LOLLIPOP,
true,
VectorEnabledTintResources.shouldBeUsed()
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class AppCompatViewInflater {
//9. 第8步调用的这个方法。
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
// ... 省略部分代码
View view = null;

// We need to 'inject' our tint aware Views in place of the standard
// framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}

//... 省略部分代码

return view;
}
}
1
2
3
4
5
6
7
8
public class Activity extends ContextThemeWrapper
//10. 通过查看Activity的源码,可以知道Activity实现了Factory2接口,所以我们可以直接在
// 自己新建的MainActivity中直接重写Factory2onCreateView方法。
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {

通过以上分析我们得出结论:

  • Activity在onCreate方法里调用了super.onCreate(savedInstanceState) -> delegate.installViewFactory() -> LayoutInflaterCompat.setFactory2(layoutInflater, this) -> this是Factory2的实现类,实现类实现了onCreateView方法 ->mAppCompatViewInflater.createView()
  • mAppCompatViewInflater.createView() 在这里面通过name(即控件的标签名)初始化了对应的view。
  • 我们可以在初始化view的时候做工作,即把控件换成自定义控件,自定义控件加上可以动态换肤的工作。
  • 所以如果想实现换肤,可以实现mAppCompatViewInflater.createView()方法里的工作,又因为Factory2.onCreateView方法调用了这个方法,那么我们可以通过重写onCreateView()来实现目的,又因为Activity实现了Factory2,所以我们可以直接在自己的Activity中重写onCreateView()方法。
  • 通过查看AppCompatActivity.

3. 换肤实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MainActivity extends SkinActivity {

private String skinPath;

@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
protected void onCreate(Bundle savedInstanceState) {
//1. 在这里调用了SkinActivity的onCreate
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (("皮肤包").equals(PreferencesUtils.getString(this, "当前皮肤"))) {
skinDynamic(skinPath, R.color.skin_item_color);
} else {
defaultSkin(R.color.colorPrimary);
}
}

// 换肤按钮(api限制:5.0版本)
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void skinDynamic(View view) {
// 真实项目中:需要先判断当前皮肤,避免重复操作!
if (!("皮肤包").equals(PreferencesUtils.getString(this, "currentSkin"))) {
skinDynamic(skinPath, R.color.skin_item_color);
PreferencesUtils.putString(this, "currentSkin", "皮肤包");
}
}

// 默认按钮(api限制:5.0版本)
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void skinDefault(View view) {
if (!("default").equals(PreferencesUtils.getString(this, "currentSkin"))) {
defaultSkin(R.color.colorPrimary);
PreferencesUtils.putString(this, "currentSkin", "default");
}
}

@Override
protected boolean openChangeSkin() {
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class SkinActivity extends AppCompatActivity {

private CustomAppCompatViewInflater viewInflater;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//2. 在这里setFactory2(layoutInflater, this)方法,可以阻止
//AppCompatDelegateImpl.installViewFactory(),从而阻止了installViewFactory到
//AppCompatViewInflater.createView()的工作,即阻止了[源码分析]的4-9步。
//从而实现我们自定义控件的逻辑
LayoutInflater layoutInflater = LayoutInflater.from(this);
LayoutInflaterCompat.setFactory2(layoutInflater, this);
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(View parent, String name, Context context, AttributeSet
attrs) {
if (openChangeSkin()) {
if (viewInflater == null) {
//3. 自定义CustomAppCompatViewInflater
viewInflater = new CustomAppCompatViewInflater(context);
}
viewInflater.setName(name);
viewInflater.setAttrs(attrs);
//4. 在这个方法里将布局中的控件,更换为自定义的换肤控件,从而实现换肤的功能。
return viewInflater.autoMatch();
}
return super.onCreateView(parent, name, context, attrs);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
protected void defaultSkin(int themeColorId) {
this.skinDynamic(null, themeColorId);
}

/**
* 动态换肤(api限制:5.0版本)
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
protected void skinDynamic(String skinPath, int themeColorId) {
SkinManager.getInstance().loaderSkinResources(skinPath);

if (themeColorId != 0) {
int themeColor = SkinManager.getInstance().getColor(themeColorId);
StatusBarUtils.forStatusBar(this, themeColor);
NavigationUtils.forNavigation(this, themeColor);
ActionBarUtils.forActionBar(this, themeColor);
}

applyViews(getWindow().getDecorView());
}

/**
* 控件回调监听,匹配上则给控件执行换肤方法
*/
protected void applyViews(View view) {
if (view instanceof ViewsMatch) {
ViewsMatch viewsMatch = (ViewsMatch) view;
viewsMatch.skinnableView();
}

if (view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view;
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
applyViews(parent.getChildAt(i));
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 自定义控件加载器(可以考虑该类不被继承)
*/
public final class CustomAppCompatViewInflater extends AppCompatViewInflater {

private String name; // 控件名
private Context context; // 上下文
private AttributeSet attrs; // 某控件对应所有属性

public CustomAppCompatViewInflater(@NonNull Context context) {
this.context = context;
}

public void setName(String name) {
this.name = name;
}

public void setAttrs(AttributeSet attrs) {
this.attrs = attrs;
}

/**
* @return 自动匹配控件名,并初始化控件对象
*/
//4. 通过将布局文件里对应name的控件,替换为我们自己的换肤自定义控件,实现换肤操作。
//注意: 这里有的控件才会实现换肤,没有控件不会实现换肤
public View autoMatch() {
View view = null;
switch (name) {
case "LinearLayout":
// view = super.createTextView(context, attrs); // 源码写法
view = new SkinnableLinearLayout(context, attrs);
this.verifyNotNull(view, name);
break;
case "RelativeLayout":
view = new SkinnableRelativeLayout(context, attrs);
this.verifyNotNull(view, name);
break;
case "TextView":
view = new SkinnableTextView(context, attrs);
this.verifyNotNull(view, name);
break;
case "ImageView":
view = new SkinnableImageView(context, attrs);
this.verifyNotNull(view, name);
break;
case "Button":
view = new SkinnableButton(context, attrs);
this.verifyNotNull(view, name);
break;
}

return view;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* 继承TextView兼容包,9.0源码中也是如此
* 参考:AppCompatViewInflater.java
* 86行 + 138行 + 206行
*/
public class SkinnableTextView extends AppCompatTextView implements ViewsMatch {

private AttrsBean attrsBean;

public SkinnableTextView(Context context) {
this(context, null);
}

public SkinnableTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}

public SkinnableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

attrsBean = new AttrsBean();

// 根据自定义属性,匹配控件属性的类型集合,如:background + textColor
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.SkinnableTextView,
defStyleAttr, 0);
// 存储到临时JavaBean对象
attrsBean.saveViewResource(typedArray, R.styleable.SkinnableTextView);
// 这一句回收非常重要!obtainStyledAttributes()有语法提示!!
typedArray.recycle();
}

//对Textview进行换肤
@Override
public void skinnableView() {
// 根据自定义属性,获取styleable中的background属性
int key = R.styleable.SkinnableTextView[R.styleable.SkinnableTextView_android_background];
// 根据styleable获取控件某属性的resourceId
int backgroundResourceId = attrsBean.getViewResource(key);
if (backgroundResourceId > 0) {
// 是否默认皮肤
if (SkinManager.getInstance().isDefaultSkin()) {
// 兼容包转换
Drawable drawable = ContextCompat.getDrawable(getContext(), backgroundResourceId);
// 控件自带api,这里不用setBackgroundColor()因为在9.0测试不通过
// setBackgroundDrawable本来过时了,但是兼容包重写了方法
setBackgroundDrawable(drawable);
} else {
// 获取皮肤包资源
Object skinResourceId = SkinManager.getInstance().getBackgroundOrSrc(backgroundResourceId);
// 兼容包转换
if (skinResourceId instanceof Integer) {
int color = (int) skinResourceId;
setBackgroundColor(color);
// setBackgroundResource(color); // 未做兼容测试
} else {
Drawable drawable = (Drawable) skinResourceId;
setBackgroundDrawable(drawable);
}
}
}

// 根据自定义属性,获取styleable中的textColor属性
key = R.styleable.SkinnableTextView[R.styleable.SkinnableTextView_android_textColor];
int textColorResourceId = attrsBean.getViewResource(key);
if (textColorResourceId > 0) {
if (SkinManager.getInstance().isDefaultSkin()) {
ColorStateList color = ContextCompat.getColorStateList(getContext(), textColorResourceId);
setTextColor(color);
} else {
ColorStateList color = SkinManager.getInstance().getColorStateList(textColorResourceId);
setTextColor(color);
}
}

// 根据自定义属性,获取styleable中的字体 custom_typeface 属性
key = R.styleable.SkinnableTextView[R.styleable.SkinnableTextView_custom_typeface];
int textTypefaceResourceId = attrsBean.getViewResource(key);
if (textTypefaceResourceId > 0) {
if (SkinManager.getInstance().isDefaultSkin()) {
setTypeface(Typeface.DEFAULT);
} else {
setTypeface(SkinManager.getInstance().getTypeface(textTypefaceResourceId));
}
}
}
}

二. 组件化框架设计

1. APT(Annotation Processing Tool)

  • 是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须通过APT工具来进行处理。

  • 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。

  • 通俗理解:根据规则,帮我们生成代码、生成类文件。

2. 工具:

  1. square/javapoet:JavaPoet 是一个生成.java源文件的Java API.
  2. alibaba/ARouter: 帮助 Android App 进行组件化改造的路由框架 .

3. 组件化里路由的基本思想

  1. (创建注解)新建Java Library类型的arouter_annotation模块。在里面新建路由注解(ARouter)。

  2. (处理注解)新建Java Library类型的compiler模块。在里面新建继承AbstractProcessor的注解处理类,根据注解获取类的信息,以及注解上的参数等信息,来编写生成源代码的代码。最后在编译时生成源代码文件。

    • 使用的类库

    • 1
      2
      3
      4
      5
      6
      7
      8
      // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
      compileOnly'com.google.auto.service:auto-service:1.0-rc4'
      annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

      // 帮助我们通过类调用的形式来生成Java代码
      implementation "com.squareup:javapoet:1.9.0"
      // 引入annotation,处理@ARouter注解
      implementation project(':arouter_annotation')
  3. (使用注解)@ARouter(path = “/app/MainActivity”)

三. 插件化框架设计

1. 原理:可安装运行的宿主APP ->可以通过插件化,运行调用下载到本地的没有安装的apk文件。

2. 占位(插桩)式插件化框架

  1. 基本思想:

    • 通过AssetManager获取sd卡中下载好的插件apk,可以获取Resources和DexClassLoader。
    • 新建公共库,定义标准(接口ActivityInterface),比如Activity、service的生命周期方法,以及获取宿主的Context等接口。
    • 作为插件的apk,里面的Activity实现标准接口ActivityInterface。
    • 宿主中新建一个占位(代理)的Activity(ProxyActivity),并且通过重写相关方法,替换为插件的Resources和DexClassLoader。
    • 当启动插件中的Activity时,先获取插件Activity的ActivityInfo,然后就可以通过反射获取到Activity对象,即ActivityInterface的实现类对象,通过这个对象就可以执行插件里面Activity的方法了。
    • 因为插件中使用的是宿主中的Activity,只需要在宿主中的清单文件中注册代理Activity就行,插件中其实不是真正的Activity,无需注册。
    • 总结为,在宿主中创建一个空的代理Activity,当调用插件中的Activity时,在代理Activity中的方法中调用插件中相同的方法。
  2. Activity、service使用插件的流程差不多,但是BroadcastReceivery有发送和接收两步,所以多了一步接收的操作。

  3. 静态注册的广播,是什么时候注册的?

    • 手机开机的时候,所有的app,再次进行安装一遍,系统会去解析AndroidManifest,解析静态广播,进行注册。
  4. 有关插件的问题

    • 在插件中,为什么不能使用 this
      答:因为插件是没有安装手机上安装的,是无法拥有组件环境的。

    • 为什么要有代理的Activity
      答:由于插件中的Activity,并不是一个能够运行的组件,所以需要代理的Activity去代替 插件中的Activity (例如:Activity任务进栈)。

    • 这种插件化,在写插件开发的时候有什么(要注意的事项)
      答:所有关于操作,组件环境的地方,都必须使用 宿主的环境。

  5. Apk解析原理系统源码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    1.静态注册的广播,是什么时候注册的?
    手机开机的时候, 所有的app,再次进行安装一遍, 系统会去解析AndroidManifest,解析静态广播后,就会自动注册

    2.我们去分析 安装
    data/app 放置目录
    data/data/包名/ 应用所属目录
    data/dalvik-cache 虚拟机去加载执行指令

    3.该分析那个目录
    data/app 放置目录

    手机开机安装app的时候,安装 过后 马上就会 全盘扫描,data/app 放置目录

    解析出 app apk 文件 里面所有组件,包括权限,系统会去解析AndroidManifest

    Android 会在 安装过后,会马上扫描此目录data/app 放置目录 ---> 解析 apk 文件 里面的配置信息AndroidManifest.xml ,如果里面有静态配置的广播
    就会要去注册广播

    分析系统源码,是如何进行解析apk

    PackageManagerService

    【目标】:看系统是如何 去解析 apk 文件里面的 组件信息的
    系统是在安装的时候,才会去扫描,apk

    PackageManagerService

    Linux内核驱动 --- init进程 -- zygote进程 孵化 SystemServer --- PackageManagerService启动

    PackageManagerService启动
    pms 如何去 处理 data/app/目录 ,如何解析apk

    dataDir /data/目录

    mAppInstallDir = new File(dataDir, "app"); /data/app/目录
    mAppInstallDir:如何去解析apk文件的

    scanDirTracedLI 是要去扫描 /data/app/目录下的apk文件 ---> 解析AndroidManifest 里面的所有信息

    扫描 apk 文件,解析apk scanPackageLI --->

    parsePackage:解析 apk 文件里面的所有信息

    Package ---> apk 里面的 AndroidManifest配置信息 (所有的)

    拿到了Package,就能拿到 静态的广播信息

    最终的目标:
    <!-- 静态注册的广播 -->
    <receiver android:name=".StaticReceiver">

    <intent-filter>

    <action android:name="plugin.static_receiver" />

    </intent-filter>

    </receiver>

    总结为:PackageManagerService可以加载手机里的任何apk,从它的构造方法可以看出,安装的apk文件都在data/app目录里,当扫描这个目录里的某个apk文件时,,调用了PackageParser.parsePackage()方法,并且返回Package类,这个类有个字段属性receivers(ArrayList ),就是AndroidMannifest里的receiver,receivers里面的泛型Activity不是四大组件的Activity,通过这个Activity可以获取跟广播相关的一些信息。所以,我们可以通过反射,根据上面的源码,一步步获取AndroidManifest里有关广播的一些信息,然后在代码中注册广播即可。

3. Hook式插件化框架

  1. Hook 基础 – 从入门到熟练

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    1.替换(把系统里面的 替换成 动态代理)
    2.添加动态代理(做我们自己的业务逻辑)


    ---- Hook 系统源码 ----> TestActivity 不再AndroidManifest里面注册,也能启动
    会报错:TestActivity}; have you declared this activity in your AndroidManifest.xml? 没有在AndroidManifest里面注册

    原因:

    startActivity ---> TestActivity ----》 (Hook) (AMS)ActivityManagerService(检测,当前要启动的Activity是否注册了)

    Hook (Hook):
    1.把TestActivity 替换我们真实有效的Activity

    startActivity(TestActivity) ---> Activity --> Instrumentation.execStartActivity ---> ActivityManagerNative.getDefault()
    IActivityManager.startActivity ---> (Hook) AMS.startActivity(检测,当前要启动的Activity是否注册了)

    思想切入点:既然会得到IActivityManager,会设置IActivityManager,(寻找替换点(动态代理))

    动态代理:由于执行startActivity之前,我们需要先执行我们的代码(把TestActivity 替换成 已经注册的 Activity)


    2.ASM检查过后,要把这个ProxyActivity 换回来 --> TestActivity

    startActivity ---> TestActivity -- (Hook ProxyActivity)(AMS)检测,当前要启动的Activity是否注册了)ok ---》
    ActivityThread(即将加载启动Activity)----(要把这个ProxyActivity 换回来 --> TestActivity)

    Hook LAUNCH_ACTIVITY

    我们要在Handler。handleMessage 之前执行,就是为了(要把这个ProxyActivity 换回来 --> TestActivity),所有需要Hook


    1.hook ams检查 把TestActivity 换成 ProxyActivity
    2.hook 即将要加载Activity又把ProxyActivity 换回来了 TestActivity
  2. 类加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    学习类加载之前,我们去startActivity跳转 到 插件中的Activity看会发生什么错误(分析错误的过程中学习类加载)

    宿主 跳转 宿主的Actvity ok

    宿主 跳转 插件里面的Activity 报错
    分析错误原因,来学习Android类加载:
    Caused by: java.lang.ClassNotFoundException: Didn't find class "com.netease.plugin_package.PluginActivity" on path:
    DexPathList[[zip file "/data/app/com.netease.hookproject-1/base.apk", zip file "/data/app/com.netease.hookproject-1/split_lib_
    dependencies_apk.apk", zip file "/data/app/com.netease.hookproject-1/split_lib_slice_0_apk.apk", zip file "/data/app/com
    .netease.hookproject-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.netease.hookproject-1/split_lib_slice_2_apk.apk"
    , zip file "/data/app/com.netease.hookproject-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.netease.hookproject-1/s
    plit_lib_slice_4_apk.apk", zip file "/data/app/com.netease.hookproject-1/split_lib_slice_5_apk.apk", zip file "/data/app/com
    .netease.hookproject-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.netease.hookproject-1/split_lib_slice_7_apk.apk",
    zip file "/data/app/com.netease.hookproject-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.netease.hookproject-1/spli
    t_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]

    startActivity --> AMS ---> ActivityThread(把代理的Activity给换回来了) ---> 要去实例化Activity (报错)

    Activity --> Instrumentation ---> AMS检查 --->
    ActivityThread (即将加载)-(handleLaunchActivity 类加载Activity performLaunchActivity ---> newActivity(cl == PathClassLoader))


    分析Android中的ClassLoader:
    1.java中的ClassLoader 和 Android的ClassLoader 是不一样
    2.ClassLoader == PathClassLoader
    3.PathClassLoader == cl.loadClass(className).newInstance();

    PathClassLoader.loadClass ---》 BaseDexClassLoader --》ClassLoader.loadClass--findClass(空方法) 让覆盖的子类方法去完成 --》
    BaseDexClassLoader.findClass() ---》pathList.findClass

    BaseDexClassLoader.findClass() -- c 为什么为null,--》 DexPathList.findClass(className) ---》DexFile.loadClassBinaryName(系列步骤后 NDK)

    for遍历 dexElements == Element[] ,分析 Element 是什么 ,为什么Element.dexFile==null?

    Android虚拟机 dex文件的 dex == 对Dex表现形式的描述 Element --- dexFile拥有可执行

    为什么 Element ==null?
    答:就是因为类加载机制加载的是 ---》 宿主的 classes.dex--Elements, 【没有插件的Element】

    解决方案:把插件的dexElements 和 宿主中的 dexElements 融为一体 PathClassLoader 就能加载到 插件/宿主 都可以加载到了
    Hook式 插件化

    ------ Android ClassLoader介绍
    1.java中的ClassLoader 和 Android的ClassLoader 是不一样
    2.Android中的ClassLoader 分为两类:系统提供的ClassLoader ---》BootClassLoader,PathClassLoader,DexClassLoader
    自定义ClassLoader

    给系统预加载使用的 :BootClassLoader
    给程序/系统程序/应用程序 加载class的 PathClassLoader
    加载 apk zip apk文件 DexClassLoader

    1.内核启动 ...
    2.init第一个进程
    3.zygote进程
    // 启动是很早就要启动
    ---> zygoteInit --> BootClassLoader.getInstance(); handleSystemServerProcess PathClassLoaderFactory --》PathClassLoader
    4.zygote进程孵化 SystemServer
    5.SystemServer启动很多的服务 ---(AMS,PSM,...)
    // 不能在这里启动
  3. 宿主和插件融为一体,解决插件没有宿主环境等问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 第一步:找到宿主 dexElements 得到此对象     PathClassLoader代表是宿主

    // 第二步:找到插件 dexElements 得到此对象,代表插件 DexClassLoader--代表插件

    // 第三步:创建出 新的 newDexElements [],类型必须是Element,必须是数组对象

    // 第四步:宿主dexElements + 插件dexElements =----> 融合 新的 newDexElements for遍历

    // 第五步:把新的 newDexElements,设置到宿主中去

    以上操作,就可以去加载插件里面的class



    需要去加载插件里面的 layout

    StringBlock ---》 string.xml color.xml anim.xml ...

    mStringBlocks[] == string.xml color.xml anim.xml

    只有mStringBlocks[] 初始化了,才能去加载 插件里面的资源
  4. LoadedApk

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    占位式 插件化 ---》(stander标准) 在插件中必须使用 宿主的环境 appActivity   宿主context  ---》 插件

    Hook式 (宿主 和 插件 element 进行融合) 在插件中可以随意使用this,既然式融合一起,插件可以使用到宿主的环境
    插件越多 内存中的 newDexElements 就会越大

    LoadedApk式 插件化框架的手写,我们控制 ClassLoader
    PathClassLoader ---> 宿主的class
    自定义ClassLoader --》 插件的class
    解决这个问题:插件越多 内存中的 newDexElements 就会越大

    ActivityThread源码的分析:

    startActivity ---》 Activity --》Instrumentation ---> AMS检查 --》
    ActivityThread -- mH LAUNCH_ACTIVITY(自己处理LoaderApk中的ClassLoader)

    case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    // 跳转的Activity纪录 --》
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    // 如果缓存mPackages中有LoadedApk 就直接返回,如果没有LoaaedApk就创建出LoadedApk ---》 宿主的LoadedApk.ClassLoader
    // 如果是加载插件,从mPackages取出 插件专用的LoadedApk.自定义ClassLoader
    r.packageInfo = getPackageInfoNoCheck(
    r.activityInfo.applicationInfo, r.compatInfo);

    // 真正的即将 实例化Activity 然后进行启动
    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } break;


    1.public final LoadedApk getPackageInfoNoCheck == 宿主的

    2.缓存中的 final ArrayMap<String, WeakReference<LoadedApk>> mPackages 默认保存的是宿主的LoadedApk


    LoadedApk --- 宿主的 ----》 LoadedApk.ClassLoader ---> 宿主中的class
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); // LoadedApk里面的ClassLoader
    (Activity)cl.loadClass(className).newInstance(); 实例化的Activity --》 宿主的 LoadedApk里面的ClassLoader 去实例化的


    以上代码结论:宿主的LoadedApk.ClassLoader 去加载 宿主中的class,然后实例化的


    --- > 自定义一个LoadedApk 自定义一个ClassLoader 用于专门加载插件里面的class,然后实例化


    自定义一个 LoadedApk 然后保存 --》 mPackages

    LoadedApk --- 插件的 ----》 LoadedApk.ClassLoader ---> 插件中的class

    3.梳理流程:
    宿主: startActivity ---》 Activity --》Instrumentation ---> AMS检查 --》ActivityThread
    mPackages.value取出 LoadedApk.ClassLoader ---> 实例化Activity (只能加载宿主的)

    插件(下一节课,要完成的功能):
    我们在取出之前,需要自定义一个 (插件专用的 LoadedApk 自定义ClassLoader) 添加到 --》 mPackages

    startActivity ---》 Activity --》Instrumentation ---> AMS检查 --》ActivityThread
    mPackages.value取出 插件专用的LoadedApk.ClassLoader --> 实例化插件的Activity


    下节课的目标:绕过 PMS 的检查处理 -->

    流程:startActivity ---》 Activity --》Instrumentation ---> AMS检查 --》ActivityThread --》

    --> 获取自定义的LoadedApk.ClassLoader --> 实例化 initializeJavaContextClassLoader(PMS检查要启动的包名是否安装)

    -->生命周期方法的处理 (才能真正启动加载到 插件包里面的Activity)

    PMS检测 插件包包名是否安装,如果没有安装就 会抛出异常:
    java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException:
    Unable to get package info for com.netease.plugin_package; is package not installed?

    pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
    UserHandle.myUserId());
    Hook 我们要在 getPackageInfo 执行之前 给Hook拦截住,控制pi不为null
    分析:pm.getPackageInfo 客户端进程 ----》 PMS进程-检测是否安装了
  5. 插件和宿主融为一体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 第一步:找到宿主 dexElements 得到此对象     PathClassLoader代表是宿主

    // 第二步:找到插件 dexElements 得到此对象,代表插件 DexClassLoader--代表插件

    // 第三步:创建出 新的 newDexElements [],类型必须是Element,必须是数组对象

    // 第四步:宿主dexElements + 插件dexElements =----> 融合 新的 newDexElements for遍历

    // 第五步:把新的 newDexElements,设置到宿主中去

    以上操作,就可以去加载插件里面的class


    需要去加载插件里面的 layout

    StringBlock ---》 string.xml color.xml anim.xml ...

    mStringBlocks[] == string.xml color.xml anim.xml

    只有mStringBlocks[] 初始化了,才能去加载 插件里面的资源
  6. 总结

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1.占位插件化 Activity  - ProxyActivity   --》插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
    2.占位插件化 Service - ProxyService --》插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
    3.占位插件化 动态广播 - ProxyReceiver --》插件里面,在插件开发中,必须时时刻刻记住是 是使用宿主的环境
    4.占位插件化 静态广播 -- 分析系统是如何解析APK文件的,源码的阅读,阅读源码的思路,PMS入手-(模仿系统是如何解析,我们就怎么解析)(难点)
    稳定,插件化开发很痛苦 -------》 开源中的框架 插件化框架 DL

    5.Hook从入门 到 熟练 --(1.替换,2.被替换的 动态代理/接口设置) --> Hook系统源码 -- (Hook1)AMS检测是否注册 --- (Hook2)换回来
    6.安卓的类加载 --》PathClassLoader(加载运行App中的class),DexClassLoader(apk zip), DexPathList Element,插件Element和宿主Element
    7.真正的融合--》就可以加载插件里面的class, 插件里面的Layout怎么去加载呢,AseetManager Resources (难点)
    Hook式插件化框架 --》 融为一体,Hook方式: 不用考虑宿主的环境,不稳定==兼容性, 360开源框架 Hook方式实现的

    8.LoadedApk插件化-- ActivityThread LAUNCH_ACTIVITY源码切入点,ClassLoader--》宿主 --> LoadedApk.ClassLoader --> 宿主class
    9.自定义LoadedApk.自定义ClassLoader ---> 插件的classmPackages -- size=2 , 没法绕过PMS检测,检测插件包名是否安装了
    10.开关 切换 宿主 插件, 再次运行会报错,是因为 PMS检测, Hook PMS,绕过了pi==null的情况
    不稳定==兼容性
  7. Hook代码推理技巧

    1
    2
    3
    1.Hook反射处理,倒序写代码,只写一行代码,推理千万行代码

    2.兼容的 21 ~ 28 系统版本 -- Hook插件化框架 手写