問題描述:
如果一個 View 繪制于某個 Activity 的 ContentView 上, 那它的 Context 一定是和這個 Activity 相關聯的. 因此我們想在 View 中直接用 Activity 方法時 (最常用的應該就是 Activity.startActivity() 方法了), 不必再向 View 中傳遞 Activity 對象.
一般在 View 中獲取這個 Activity 對象都是簡單的用下面代碼就可以了:
Activity activity = (Activity) getContext();
但在 View 繼承自 AppCompat 系的 View 時 (比如 AppCompatTextView, AppCompatImageView), 上面方法可能會得到下面異常:
**java.lang.ClassCastException: android.support.v7.widget.TintContextWrapper cannot be cast to ...Activity**
問題原因:
原因其實很簡單. 上 AppCompatTextView 的構造函數代碼:
public class AppCompatTextView extends TextView implements TintableBackgroundView {
......
public AppCompatTextView(Context context) {
this(context, null);
}
public AppCompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr); // 注意這行
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = AppCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
}
......
}
在構造函數中, context 被用 TintContextWrapper
包了一層. 所以這時 Activity 其實被保存在了 TintContextWrapper
中的 BaseContext 中了.
即 TintContextWrapper
不能直接強制轉換為 Activity.
網上有說 23.3.0 的 v7 之后的包中會有這個問題, 之前的不會. 我手頭只有 25.3.0 的源碼, 沒有驗證其他版本的代碼是什么樣子. 不過這不重要, 因為下面的解決方案可以兼容各個情況.
問題解決:
知道原因就簡單了. 可以簡單的用 TintContextWrapper.getBaseContext()
得到這個 Activity.
但其實一層層的從 ContextWrapper 中把 Activity 剝出來更保險:
/**
* try get host activity from view.
* views hosted on floating window like dialog and toast will sure return null.
* @return host activity; or null if not available
*/
public static Activity getActivityFromView(View view) {
Context context = view.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
如上方法可以通用的解決從 View 中獲取 Activity 的問題.
事實上, 谷歌 v7 包中的 android.support.v7.app.MediaRouteButton
就是這么干的.