AppCompat中的坑
http://blog.csdn.net/liuxu0703/article/details/70145168
在使用类似textView等View时, 5.0以下的版本中,android会做一个兼容性的处理,生成AppCompatTextView,这样的view的getContext()是TineContextWrapper,如果要强制转为activity或者instanceOfActivity的判断时,不会符合预期的操作
有两种解决办法
根本的解决办法,采用stackoverflow中的办法,一个工具方法来替换所有的view的getContext(),适用于工程中的这个调用的方式不多,而且修改起来风险不大的地方
当前问题的解决办法,在5.0以下,看有没有可以禁止这种行为的,是支持的,方式是: 在我们的BaseActivity的onCreate()中,调用getDegate().setCompatVectorFromResourcesEnabled()
可以通过源码查看,在AppCompatActivity的onCreate()中,是安装了一个LayoutInfatorFactory,这里就用于替换的
1) AppCompatDelegateImplV9.onCreateView
/**
* From {@link android.support.v4.view.LayoutInflaterFactory}
*/
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
2) AppCompatDelegateImplV11.callActivityOnCreateView()
@Override
View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
// On Honeycomb+, Activity's private inflater factory will handle calling its
// onCreateView(...)
return null;
}
发现 callActivityOnCreateView返回null,因而是createView()来控制的
3) AppCompatDelegateImplV9.createView()
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
// Otherwise we have to use the old heuristic
: shouldInheritContext((ViewParent) parent);
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
4) mAppCompatViewInflater.createView()的实现
继续翻看代码,能够看到view替换的过程,但是这里我们也看到了有控制的参数,wrapContext为true时,才会做替换,这就是VectorEnabledTintResources.shouldBeUsed()
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it's android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
5) VectorEnabledTintResources.shouldBeUsed()
public static boolean shouldBeUsed() {
return AppCompatDelegate.isCompatVectorFromResourcesEnabled()
&& Build.VERSION.SDK_INT <= MAX_SDK_WHERE_REQUIRED;
}
继续追踪 AppCompatDelegate.isCompatVectorFromResourcesEnabled()
6) AppCompatDelegate.isCompatVectorFromResourcesEnabled()
在相关的方法中,可以看到,这个是可以控制的
/**
* Sets whether vector drawables on older platforms (< API 21) can be used within
* {@link android.graphics.drawable.DrawableContainer} resources.
*
* <p>When enabled, AppCompat can intercept some drawable inflation from the framework, which
* enables implicit inflation of vector drawables within
* {@link android.graphics.drawable.DrawableContainer} resources. You can then use those
* drawables in places such as {@code android:src} on {@link android.widget.ImageView},
* or {@code android:drawableLeft} on {@link android.widget.TextView}. Example usage:</p>
*
* <pre>
* <selector xmlns:android="...">
* <item android:state_checked="true"
* android:drawable="@drawable/vector_checked_icon" />
* <item android:drawable="@drawable/vector_icon" />
* </selector>
*
* <TextView
* ...
* android:drawableLeft="@drawable/vector_state_list_icon" />
* </pre>
*
* <p>This feature defaults to disabled, since enabling it can cause issues with memory usage,
* and problems updating {@link Configuration} instances. If you update the configuration
* manually, then you probably do not want to enable this. You have been warned.</p>
*
* <p>Even with this disabled, you can still use vector resources through
* {@link android.support.v7.widget.AppCompatImageView#setImageResource(int)} and it's
* {@code app:srcCompat} attribute. They can also be used in anything which AppCompat inflates
* for you, such as menu resources.</p>
*
* <p>Please note: this only takes effect in Activities created after this call.</p>
*/
public static void setCompatVectorFromResourcesEnabled(boolean enabled) {
sCompatVectorFromResourcesEnabled = enabled;
}
/**
* Returns whether vector drawables on older platforms (< API 21) can be accessed from within
* resources.
*
* @see #setCompatVectorFromResourcesEnabled(boolean)
*/
public static boolean isCompatVectorFromResourcesEnabled() {
return sCompatVectorFromResourcesEnabled;
}
找到了设置的方法,就是在BaseActivity中super.oncreate()后,调用getDegate(). setCompatVectorFromResourcesEnabled(false), 注意这个方法的副作用,那就是在5.0以前的系统中,禁用了vector