BaseActivity:統一編寫共有邏輯

在上一篇文章NetworkStateView的結尾說到可以在BaseActivity中對NetworkStateView進行統一設置,從而進行界面多狀態的加載,那么今天就說一說BaseActivity,在BaseActivity怎么進行NetworkStateView的設置以及BaseActivity的一些其他作用,還沒有看過NetworkStaetView的可以先看一下這一篇文章

在項目中,我們在寫Activity時一般都不會直接繼承AppCompatActivity,而是先寫一個基類BaseActivity,讓其他的Activity繼承BaseActivity,這樣就有一個好處,就是我們可以把Activity的共有邏輯寫在BaseActivity中,例如NetworkStateView的邏輯,從而不需要在每個Activity都寫一遍

下面就對BaseActivityNetworkStateView以及一些其他共有的邏輯內容進行介紹

  • 定義加載布局,進行共同界面(NetworkStateView)的加載

其實在BaseActivity中,我們可以先給BaseActivity先定義一個布局文件如activity_base,在activity_base布局文件中先定義好共同的布局控件如NetworkStateView,Toolbar等,并且在最后定義一個FrameLayout,這個FrameLayout是關鍵人物,后面再說他的作用,哈哈

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent">

   </FrameLayout>

</LinearLayout>

定義好布局文件之后,我們需要在BaseActivity中重寫setContentView方法,讓其加載activity_base的布局文件并進行設置填充,接著再加載子類Activity的布局文件

@SuppressLint("InflateParams")
@Override
public void setContentView(@LayoutRes int layoutResID) {
    View view = getLayoutInflater().inflate(R.layout.activity_base, null);
    //設置填充activity_base布局
    super.setContentView(view);

    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
        view.setFitsSystemWindows(true);
    }

    //加載子類Activity的布局
    initDefaultView(layoutResID);
}

看到這里可能有人會有疑惑,先將activity_base的布局設置填充了之后,還怎么進行設置子類Activity的布局文件?哈哈,這個時候上面的FrameLayout就要發揮作用了,其實這里設置子類Activity的布局并不是直接填充給BaseActivity,而是讓其作為一個子View添加到activity_base中的FrameLayout中,這樣不就讓BaseActivity和子類Activity的布局相結合了,看看initDefaultView(layoutResID)

private void initDefaultView(int layoutResId) {
    networkStateView = (NetworkStateView) findViewById(R.id.nsv_state_view);
    FrameLayout container = (FrameLayout) findViewById(R.id.fl_activity_child_container);
    View childView = LayoutInflater.from(this).inflate(layoutResId, null);
    container.addView(childView, 0);
}

嗯,對NetworkStateView進行findViewById查找,接著定義NetworkStateView的相關方法之后,我們就能在子類Activity中進行相關調用了

/**
 * 顯示加載中的布局
 */
public void showLoadingView() {
    networkStateView.showLoading();
}

/**
 * 顯示加載完成后的布局(即子類Activity的布局)
 */
public void showContentView() {
    networkStateView.showSuccess();
}

/**
 * 顯示沒有網絡的布局
 */
public void showNoNetworkView() {
    networkStateView.showNoNetwork();
    networkStateView.setOnRefreshListener(this);
}

/**
 * 顯示沒有數據的布局
 */
public void showEmptyView() {
    networkStateView.showEmpty();
    networkStateView.setOnRefreshListener(this);
}

/**
 * 顯示數據錯誤,網絡錯誤等布局
 */
public void showErrorView() {
    networkStateView.showError();
    networkStateView.setOnRefreshListener(this);
}

嗯,這樣我們就可以統一設置NetworkStateView了,不過大家可能注意到,這樣其實會給每個Activity增加多了一層FrameLayout,如果子類Activity本身就是LinearLayout還會增加多一層,有人可能會覺得影響性能,不過個人認為這樣會極大增加便利性,而且對于一層FrameLayout的影響并不是很大,所以覺得這種方法還是可取的,不過讀者可自行考慮

  • 定義Activity棧,進行Activity的保存

在開發的時候,我們可能會遇到這樣的情況,在啟動了多個Activity之后需要一次性銷毀并退出應用, 又或者是銷毀頂部一個或多個Activity,顯示指定的Activity,在這樣的情形下,我們可以定義一個 Activity棧,在Activity執行onCreate時在棧里添加此Activity,在Activity指定onDestroy 時在Activity棧中移除

在這樣的每次添加和移除操作的共有邏輯,我們就可以在BaseActivity進行書寫,只要Activity繼承BaseActivity便可以在Activity進行添加移除了

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(getLayoutId());
    ...
    ActivityUtils.addActivity(this);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    ...
    ActivityUtils.removeActivity(this);
}

ActivityUtils就是我們定義的Activity棧工具類

public class ActivityUtils {

    private static Stack<Activity> mActivityStack;

    /**
     * 添加一個Activity到堆棧中
     * @param activity
     */
    public static void addActivity(Activity activity) {
        if (null == mActivityStack) {
            mActivityStack = new Stack<>();
        }
        mActivityStack.add(activity);
    }

    /**
     * 從堆棧中移除指定的Activity
     * @param activity
     */
    public static void removeActivity(Activity activity) {
        if (activity != null) {
            mActivityStack.remove(activity);
        }
    }

    /**
     * 獲取頂部的Activity
     * @return 頂部的Activity
     */
     public static Activity getTopActivity() {
        if (mActivityStack.isEmpty()) {
            return null;
        } else {
            return mActivityStack.get(mActivityStack.size() - 1);
        }
    }

    /**
     * 結束所有的Activity,退出應用
     */
    public static void removeAllActivity() {
        if (mActivityStack != null && mActivityStack.size() > 0) {
            for (Activity activity : mActivityStack) {
                activity.finish();
            }
        }
    }
}
  • 定義共有的UI操作

為了方便子類Activity可以執行共有的UI操作,我們可以將共有的UI操作寫在BaseActivity中,例如顯示DialogToastSnackbar

private void initDialog() {
    mDialog = new Dialog(this, R.style.dialog_transparent_style);
    mDialogContentView = LayoutInflater.from(this).inflate(R.layout.dialog_loading, null);
    tv_loadText = (TextView) mDialogContentView.findViewById(R.id.tv_loading_text);
    iv_loadImage = (ImageView) mDialogContentView.findViewById(R.id.iv_load_image);
    pb_loadProgress = (ProgressBar) mDialogContentView.findViewById(R.id.pb_load_progress);
    mDialog.setCanceledOnTouchOutside(false);
    mDialog.setContentView(mDialogContentView);
    Window window = mDialog.getWindow();
    if (null != window) {
        window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
    }
}

public void showProgressDialog() {
    if (mDialog != null && !mDialog.isShowing()) {
        pb_loadProgress.setVisibility(View.VISIBLE);
        iv_loadImage.setVisibility(View.GONE);
        tv_loadText.setVisibility(View.GONE);
        mDialog.show();
    }
}

public void showToast(String text) {
    if (!TextUtils.isEmpty()) {
        Toast.makeText(App.getApplication(), text, Toast.LENGTH_LONG).show();
    }
}

BaseActivity寫了顯示Toast,Dialog等的UI操作后,在子類Activity就可以共同調用了,可以減少共有操作的重寫,當然了,不寫在BaseActivity也是可以,我們可以封裝成一個工具類,在需要用的時候再調用工具類的相關方法也是可以的,這就看個人的喜好了,哈哈

  • 友盟消息推送,統計

在項目發布之后,我們可能需要向用戶推送消息,需要知道用戶在界面的點擊次數,異常信息等,以方便我們對產品的改進

由于在每個Activity或Fragment界面我們都要進行統計,所以我們需要讓每個Activity或Fragment都要調用到友盟的API,但是如果一個一個Activity或Fragment的去進行調用,是非常繁瑣的,特別是如果項目比較大,Activity和Fragment的數量較多,更是不可想象

所以我們應該在BaseActivity的生命周期方法去調用友盟API

@Override
protected void onResume() {
    MobclickAgent.onResume(this);
}
@Override
protected void onPause() {
    MobclickAgent.onPause(this);
}
  • ButterKnife,EventBus等

使用過ButterKnife和EventBus的同學都知道,ButterKnife需要進行View的綁定和解綁,而EventBus需要對Activity對象進行注冊和反注冊,這樣我們也不會在每個Activity都去重復進行這一操作,而是在BaseActivity進行bindunbind的操作,用到EventBus時,也可以在BaseActivity進行注冊和反注冊

private Unbinder unbinder;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    unbinder = ButterKnife.bind(this);
    EventBus.getDefault().register(this);
    ActivityUtils.addActivity(this);
    initDialog();
    ...
}

public void onEventMainThread(BaseEvent event) {
    ...do...
}

@Override
protected void onDestroy() {
    super.onDestroy();
    unbinder.unbind();
    EventBus.getDefault().unregister(this);
    ActivityUtils.removeActivity(this);
}
  • Android6.0運行時權限處理

在Android6.0版本以上,對于一些危險權限,需要用戶授權之后才能使用,這也需要調用對危險權限進行申請

在申請危險權限時,需要在Activity中進行代碼編寫申請,雖然對于權限處理的代碼并不是很復雜,但是如果在多個Activity中都需要申請權限,就需要編寫很多重復的代碼,所以最好就能做一個封裝,最簡單的封裝方法就是將其統一進行設置在BaseActivity中,這樣子類Activity能統一調用

在進行封裝時,先寫一個權限回調接口

public interface PermissionListener {
    void onGranted();

    void onDenied(List<String> deniedPermissions);
}

在接口中定義兩個方法,分別是統一授權和取消授權,定義接口之后,在BaseActvity中定義一個申請權限的方法,該方法的代碼如下

public static void requestPermissions(String[] permissions, PermissionListener listener) {
    Activity activity = ActivityUtils.getTopActivity();
    if (null == activity) {
        return;
    }

    mPermissionListener = listener;
    List<String> permissionList = new ArrayList<>();
    for (String permission : permissionList) {
        //權限沒有授權
        if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(permission);
        }
    }

    if (!permissionList.isEmpty()) {
        ActivityCompat.requestPermissions(activity, permissionList.toArray(new String[permissionList.size()]), CODE_REQUEST_PERMISSION);
    } else {
        mPermissionListener.onGranted();
    }
}

可以看到,方法需要有兩個參數,分別是需要申請的權限,是一個String數組,另一個則是權限的回調接口,用于在授權或取消授權后能做出相應的處理,接著在方法里面對權限數組進行一個循環判斷其是否已經授權,對于沒有授權的會添加到List中,可以用于再次申請,在申請之后,需要重寫Activity的onRequestPermissionsResult方法,判斷授權結果

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case CODE_REQUEST_PERMISSION:
            if (grantResults.length > 0) {
                List<String> deniedPermissions = new ArrayList<>();
                for (int i = 0; i < grantResults.length; i++) {
                    int result = grantResults[i];
                    if (result != PackageManager.PERMISSION_GRANTED) {
                        String permission = permissions[i];
                        deniedPermissions.add(permission);
                    }
                }

                if (deniedPermissions.isEmpty()) {
                    mPermissionListener.onGranted();
                } else {
                    mPermissionListener.onDenied(deniedPermissions);
                }
            }
            break;

        default:
            break;
    }
}

這樣統一封裝在BaseActivity之后,在子類Activity只要調用requestPermissions方法并傳入權限數組和回調接口的參數就能對結果進行對應的處理了

在上面的分析知道,基類BaseActivity可以用于處理子類共有的邏輯,這樣可以比main重復進行處理同一邏輯,在本篇文章中,講到的內容還是有限的,對于其他的共有邏輯,可以自行補充

本篇文章的代碼已上傳到github,在github上對應著BaseProject項目,該項目將會對于在開發中經常需要使用到的例如BaseActivity,BaseFragment,網絡請求等進行一些基本封裝,方便以后使用,項目將會持續更新,歡迎大家進行star和關注

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,813評論 25 708
  • 1.什么是Activity?問的不太多,說點有深度的 四大組件之一,一般的,一個用戶交互界面對應一個activit...
    JoonyLee閱讀 5,754評論 2 51
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,497評論 0 17
  • 【1】 女兒出生了。 全家忙手忙腳的照顧只懂得哭和笑的小家伙。 第一次當媽媽,很累。沒有睡過一天安穩覺。時刻注意女...
    Da白紙閱讀 635評論 1 1
  • 公元2061年,人類社會飛速發展。中國科技發展程度已經超越世界上任何的一個國家。人工智能,從45年前就開始萌...
    missyxy閱讀 327評論 0 2