1. 程式人生 > >我一行程式碼都不寫實現Toolbar!你卻還在封裝BaseActivity?

我一行程式碼都不寫實現Toolbar!你卻還在封裝BaseActivity?

前言

距離 上篇文章 的發表時間已經過去兩個多月了,這兩個月時間裡我沒寫文章但一直在更新著我的 MVPArms 框架,讓他逐漸朝著 可配置化整合框架 發展

就在前段時間我在 鴻洋公眾號 上看到了一篇文章,大概是介紹怎麼封裝 BaseActivity ,讓 Activity 通過幾行程式碼就可以實現 ToolBar

剛好我的 MVPArms 框架也更新了一個功能:

通過非繼承 Activity Fragment 來實現以前需要封裝進 BaseActivity BaseFragment 通過繼承來實現的一些公共邏輯,以及監聽整個 App 所有 Activity 以及 Fragment 的生命週期(包括三方庫),並可向其生命週期內插入程式碼

那我就來說說我怎麼在不使用繼承的情況下讓 Activty 一行程式碼都不寫就能實現 Toolbar

為什麼我提倡少封裝 BaseActvity 少用繼承

BaseActivity 封裝多了,除了不好管理外,還有最重要的一點就是, Java 只能單繼承,當如果你的 Activity 需要使用到某個三方庫,那個三方庫必須讓你繼承於它的 Activity 但是你又需要你自己封裝的 BaseActivity 的某些特性,這時你怎麼辦? 你不能改三方庫的 Activity 所以你只有改你的 BaseActivity 讓它去繼承三方庫的 Activity,但是當改了 BaseActivity 後,發現有很多繼承於 BaseActivity

Activity 並不需要這個三方庫,卻被迫繼承了它的 Activity ,這時你又要重新封裝一個 BaseActivity

當這種情況發生的多了,你的 BaseActiviy 也越來越多,這樣就惡性迴圈了,所以我不僅提倡 App 開發者少封裝 BaseActivity 少用繼承,也提倡 三方庫 開發者少封裝 BaseActivity 少用繼承,為什麼呢?因為當 App 開發者的 Activity 需要使用到兩個三方庫,兩個三方庫都需要繼承它的 Activity,這時你讓 App 開發者怎麼辦?所以作為一個可配置化整合框架作者,我不能讓開發者去直接改我的 BaseActivity 我必須通過其他擴充套件的方式去解決這個問題

進入正題

好了進入正題,要想解決上面提到的問題,我們就要思考我們為什麼一定要封裝 BaseActivity 通過繼承來實現一些 Activity 的公共邏輯,而不能將公共邏輯封裝到其他類裡面?

答案很簡單,因為我們必須使用到 Activity 的一些生命週期,在對應的生命週期裡執行對應的邏輯,這個就是我們不能通過封裝其他類來實現的原因,找到了問題關鍵,那我們就從生命週期上下手來解決問題

ActivityLifecycleCallbacks

提到 Activity 的生命週期,這時我就要介紹一個介面了,它叫 ActivityLifecycleCallbacks ,不知道有同學之前瞭解過它嗎?不瞭解不要緊,我現在來介紹介紹它

ActivityLifecycleCallbacksApplication 中宣告的一個內部介面,我們來看看它的結構

public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity, Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity, Bundle outState);
        void onActivityDestroyed(Activity activity);
    }


這個介面有什麼用呢?

Application 提供有一個 registerActivityLifecycleCallbacks() 的方法,需要傳入的引數就是這個 ActivityLifecycleCallbacks 介面,作用和你猜的沒錯,就是在你呼叫這個方法傳入這個介面實現類後,系統會在每個 Activity 執行完對應的生命週期後都呼叫這個實現類中對應的方法,請記住是每個!

這個時候我們就會想到一個需求實現,關閉所有 Activity !你還在通過繼承 BaseActivityBaseActivityonCreate 中將這個 Activity 加入集合???

那我現在就告訴你這樣的弊端,如果你 App 中開啟有其他三方庫的 Activity ,這個三方庫肯定不可能繼承你的 BaseActivity ,這時你怎麼辦?怎麼辦?

這時 ActivityLifecycleCallbacks 就派上用場了, App 中的所有 Activity 只要執行完生命週期就一定會呼叫這個介面實現類的對應方法, 那你就可以在 onActivityCreated 中將所有 Activity 加入集合,這樣不管你是不是三方庫的 Activity 我都可以遍歷集合 finish 所有的 Activity

使用 ActivityLifecycleCallbacks 實現 ToolBar

設定 NoActionBar 主題

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    ...
</application>

建立 ToolBar 的佈局並引用

大家不要糾結我的 Toolbar 佈局方式,這裡只是實現思想,你可以自己改成自己的佈局方式

<me.jessyan.art.widget.autolayout.AutoToolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="120px"
    android:background="?attr/colorPrimary"
    app:contentInsetStart="0dp"
    >

    // toolbar 中的返回按鈕
    <com.zhy.autolayout.AutoRelativeLayout
        android:id="@+id/toolbar_back"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="left"
        >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30px"
            android:layout_marginRight="30px"
            android:layout_centerVertical="true"
            android:src="@mipmap/login_return"/>

    </com.zhy.autolayout.AutoRelativeLayout>

    // toolbar 中的標題名
    <TextView
        android:id="@+id/toolbar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:textColor="#fff"
        android:layout_gravity="center"
        tools:text="MVPArt"/>


</me.jessyan.art.widget.autolayout.AutoToolbar>

在你需要 ToolbarActivity 佈局中 include 上面的佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    //將 Toolbar 包裹進來
    <include layout="@layout/include_title"/>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="15px"
        android:paddingTop="15px"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"
            tools:listitem="@layout/recycle_list"
            />

    </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

實現 ActivityLifecycleCallbacks 並註冊給 Application

下面只是對 ToolBar 簡單的設定,你可以自己配置更多複雜的功能,你想象力有多豐富,這裡就有多強大

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                //這裡全域性給Activity設定toolbar和title,你想象力有多豐富,這裡就有多強大,以前放到BaseActivity的操作都可以放到這裡
                if (activity.findViewById(R.id.toolbar) != null) { //找到 Toolbar 並且替換 Actionbar
                    if (activity instanceof AppCompatActivity) {
                        ((AppCompatActivity) activity).setSupportActionBar((Toolbar) activity.findViewById(R.id.toolbar));
                        ((AppCompatActivity) activity).getSupportActionBar().setDisplayShowTitleEnabled(false);
                    } else {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            activity.setActionBar((android.widget.Toolbar) activity.findViewById(R.id.toolbar));
                            activity.getActionBar().setDisplayShowTitleEnabled(false);
                        }
                    }
                }
                if (activity.findViewById(R.id.toolbar_title) != null) { //找到 Toolbar 的標題欄並設定標題名
                    ((TextView) activity.findViewById(R.id.toolbar_title)).setText(activity.getTitle());
                }
                if (activity.findViewById(R.id.toolbar_back) != null) { //找到 Toolbar 的返回按鈕,並且設定點選事件,點選關閉這個 Activity
                    activity.findViewById(R.id.toolbar_back).setOnClickListener(v -> {
                        activity.onBackPressed();
                    });
                }
            }

            ...
            
        });
    }
}

在 AndroidManifest.xml 中設定 Label

以後你想讓一個 Activity 實現 ToolBar ,只用做兩件事情:

1.在佈局檔案中

 <include layout="@layout/include_title"/>

2.在 AndroidManifest.xml 給這個 Activity 設定 Label ,這個 Label 就是標題名

        <activity
            android:name=".mvp.ui.activity.SplashActivity"
            android:label="@string/app_name"
            />

再介紹給大家一個全域性配置所有 Activity 進入退出過渡動畫的方法,設定 Theme 屬性

 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowAnimationStyle">@style/AnimaActivity</item>
    </style>
    <style name="AnimaActivity">
        <item name="android:activityOpenEnterAnimation">@anim/translate_right_to_center</item>
        <item name="android:activityOpenExitAnimation">@anim/translate_center_to_left</item>
        <item name="android:activityCloseEnterAnimation">@anim/translate_left_to_center</item>
        <item name="android:activityCloseExitAnimation">@anim/translate_center_to_right</item>
    </style>

Activity 中你根本不用繼承任何 Activity ,不用寫任何一行程式碼,就可以實現很多繁瑣複雜的功能

很多公共邏輯都可以寫到 ActivityLifecycleCallbacks 中,只要敢於嘗試,你想象力有多豐富,這裡就有多強大

擴充套件

因為所有 Activity 在執行對應生命週期時, ActivityLifecycleCallbacks 對應的方法都會被呼叫,有些 Activity 可能不需要 Toolbar ,比如三方庫的 Activity ,雖然在 onActivityCreated 方法中,判斷了 ToolBarId 找不到就不執行設定 ToolBar 的邏輯,但是未免不夠優雅

自定義介面

其實我們可以讓 Activity 實現對應的自定義介面, 在 onActivityCreatedinstanceof 這個自定義介面,如果不實現這個自定義介面,就說明不需要設定 ToolBar ,這樣就優雅很多

儲存資料

ActivityLifecycleCallbacks 中,所有 Activity 執行對應的生命週期後,它對應的方法都會被呼叫,所以我們必須區分這個 Activity 到底是哪個 Activity ,所以 ActivityLifecycleCallbacks 每個方法都會傳入 Activity 做為引數,我們就可以用來區分 Activity

public void onActivityCreated(Activity activity, Bundle savedInstanceState){
    if (activity instanceof xxxActivity){
        ....
    }

}

但是又有一個問題出現這個 ActivityLifecycleCallbacks 是公用的,當一個 ActivityonCreate 方法產生了一個物件 ,我們需要在這個 Activity 執行 onDestroy 時用到這個物件,怎麼辦?因為每個 Activity 都要產生這個物件,我們不可能把這個物件儲存在 ActivityLifecycleCallbacks 中啊

現在就可以用到 Activity.getIntent 來儲存一些資料, Intent 中持有一個 Bundle 物件可以儲存一些資料,

舉個例子

我們需要使用 ActivityLifecycleCallbacks 實現給所有 Activity 執行 ButterKnife.bind(activity)

Bundle 中可以儲存 Parcelable 物件

public class ActivityBean extends Parcelable {
    private Unbinder unbinder;
    public void setUnbinder(Unbinder unbinder){
        thid.unbinder = unbinder;
    }
    
    public Unbinder getUnbinder(){
        return unbinder;
    }
}

ActivityLifecycleCallbacks 執行對應邏輯

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                ActivityBean bean = new ActivityBean();
                Unbinder unbinder = ButterKnife.bind(activity);
                bean.setUnbinder(unbinder);
                activity.getIntent().putExtra("ActivityBean", bean);
           }

            ...
            
            @Override
            public void onActivityDestroyed(Activity activity) {
                ActivityBean bean = activity.getIntent().getParcelableExtra("ActivityBean");
                bean.getUnbinder().unbind();
            }
        }
            
        });
    }
}

需要Activity初始化某些事,或者提供某些資料

BaseActivity 有些時候需要,子 Activity 實現某些方法,或者提供某些資料,如需要子 Activity
實現 initView 返回 setContentView() 中的佈局 ID ,實現 initData 初始化一些資料,這樣就可以不需要 Activity 再重寫 onCreate ,達到規範的目的, 這樣使用 ActivityLifecycleCallbacks 同樣能做到,那我該怎麼做呢?

只需要 Activity 實現某個自定義介面

public interface IActivity {

    int initView();

    void initData();

}

然後在 ActivityLifecycleCallbacksonActivityCreated 中呼叫這些方法,就可以實現

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            if (activity instanceof IActivity) {
               activity.setContentView(((IActivity)activity).initView());
               ((IActivity)activity).initData();          
            }
              
           }

            ...
       
        }
            
        });
    }
}

注意事項

由於 ActivityLifecycleCallbacks 中所有方法的呼叫時機都是在 Activity 對應生命週期的 Super 方法中進行的,所以在 ActivityonCreate 方法中使用 setContentView 必須在 super.onCreate(savedInstanceState); 之前,不然在 onActivityCreated 方法中 findViewById 會發現找不到

@Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_home);
        super.onCreate(savedInstanceState);
    }


也可以結合上面的方式使用自定義介面,呼叫 initView 後,在 findViewById() 找到 ToolBar

一不小心實現了這麼多以前必須寫到 BaseActivity 中通過繼承才能達到的功能,發現沒有 BaseActivity ,也並沒有什麼不舒服的地方,突然感覺自己好牛逼 ?,趕快關注我吧,雖然我並不會經常更新部落格,但是我更新的文章在質量上絕對有保證!

總結

值得注意的是 ActivityLifecycleCallbacks 可以註冊多個,可以針對不同情況新增各種 ActivityLifecycleCallbacks 按照需要進行組合從而達到不同的需求 ,和 OkhttpInterceptor 類似

現在整個 App 的所有 ActivityFragment 只要生命週期呼叫都會被攔截,應用到我框架上就可以動態的向所有 ActivityFragment 的對應生命週期插入任意程式碼,比如說 LeakCanaryRefWatcher.watch(fragment) 也可以直接插入到三方庫的 Fragment 中,並且如果程式碼有任何改動也不用再去改基類,有點Aop的意思

以上提到的思想以及解決方案已經使用到了我的 MVPArms 框架中,想知道更詳細的用法可以去看看我的框架實現,我上面提到的所有的實現,其實都是最簡單的一些需求,相信已經顛覆了以前的實現方式了,而且更加優雅(因為我是站在三方庫設計者的角度提出這個功能的,我必須將惟一繼承的機會留給其他 Activity ,你如果相較於之前的方式覺得不夠優雅,那就當我沒說),更不用擔心 Java 單繼承的束縛

但是千萬不要以為, ActivityLifecycleCallbacks 就只能實現這些簡單的需求,它還可以用到更多更復雜的功能之上,還是我之前的話, 只要敢於嘗試,你想象力有多豐富,這裡就有多強大 ,思想以及解決方案已經介紹的很清楚了,至於更多需求的實現就靠大家去嘗試咯,雖然我不敢保證以前封裝 BaseActivity 通過繼承實現的所有功能都能被 ActivityLifecycleCallbacks 所替代,但是我想大多數功能還是能實現的

以前大家都是分享自己封裝的 BaseActivity ,說不定有一天大家就開始分享自己寫的 ActivityLifecycleCallbacks 呢! 當以前的方式已經不能滿足我們的需求,敢於跳出傳統的方式,嘗試不同的解決方案,才能擴寬自己的視野,增長自己的技術, MVPArms 正在不斷的努力著!

這裡還要說一句,每個人的思路不一樣,考慮問題的角度也不一樣,你認同我也好, 不認同我也好,都不會影響我探索未知的腳步,至少我是在用我的思路創新,解決一些我認為有必要解決的問題,和上一篇的文章一樣,我就是喜歡使用不一樣的思路解決同樣的問題,不管你是否覺得可行,我至少用這個你覺得不可行的思路實現了我想達到的效果,仁者見仁智者見智,如果我們思維沒有碰撞,那也請珍惜我的勞動成果

對於一些評論我再說一句, registerActivityLifecycleCallbacks() 內部是把 ActivityLifecycleCallbacks 加到一個集合中,所以 ActivityLifecycleCallbacks 可以新增多個,並且 ActivityLifecycleCallbacks 只是在專案初始化的時候被裝到集合中,並不會初始化任何東西,和新增監聽器一個道理,使用的是觀察者模式,所以不要說 Application 程式碼這麼多會怎麼怎麼樣, OkhttpInterceptor 的程式碼更多,也是在 Okhttp 初始化時被新增,你覺得會有什麼影響嗎?

Hello 我叫Jessyan,如果您喜歡我的文章,可以在以下平臺關注我

-- The end