Android Activity 基类教程 (内附代码 Kotlin 版本)

494 阅读7分钟

“我正在参加「掘金·启航计划」” 1.最近接收公司Android 项目 其中发现项目中每个Activity 类都继承了一个基类把 所有 的Activity 基本的方法都实现 感觉不错 这里记录一下也分享给大家

首先是BASEDBActivity

import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.weiaiyun.baselib.base.viewmodel.WayBaseViewModel
/**
 * 项目名称:
 * 类名称:WayBaseDbActivity
 * 类描述:DataBindingActivity基类,,自动把ViewModel注入Activity和Databind注入进来了需要使用Databind的清继承它
 * 作者:AlanPaine
 * 创建时间: 2022/03/01-16:48
 * 邮箱:AlanPaine@163.COM
 * 修改简介:
 */
abstract class      BaseDbActivity <VM : BaseViewModel, DB : ViewDataBinding> :BaseActivity<VM>(){
    lateinit var mDatabind: DB

    override fun onCreate(savedInstanceState: Bundle?) {
        setUserDataBinding(true)
        super.onCreate(savedInstanceState)
    }
    /**
     * 创建DataBinding
     */
    override fun initDataBind() {
        mDatabind = DataBindingUtil.setContentView(this, getLayoutId())
        mDatabind.lifecycleOwner = this
    }
}

2 . BaseViewModel

import androidx.lifecycle.ViewModel

open class WayBaseViewModel : ViewModel() {


}

3.BaseActivity

这个类是 把Activity 绑定xml  和一些事件的方法抽出来

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.weiaiyun.baselib.action.BundleAction
import com.weiaiyun.baselib.action.ClickAction
import com.weiaiyun.baselib.action.HandlerAction
import com.weiaiyun.baselib.base.viewmodel.WayBaseViewModel
import com.weiaiyun.baselib.ext.getVmClazz
import com.weiaiyun.baselib.ext.hideSoftKeyboard
import java.util.*


abstract class BaseActivity <VM : BaseViewModel> : AppCompatActivity(), HandlerAction,
    BundleAction, ClickAction {
    lateinit var mViewModel: VM

    /**
     * startActivityForResult 方法优化
     */
    private var mActivityCallback: OnActivityCallback? = null
    private var mActivityRequestCode = 0

    /**
     * 是否需要使用DataBinding 供子类BaseVmDbActivity修改,用户请慎动
     */
    private var isUserDb = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initLayout()
        onIntentData()//获取数据
        onViewCreated(savedInstanceState)//界面创建完成
        createObserver()
        initData()
    }

    /**
     * 获取getIntent();数据
     */
    open fun onIntentData() {

    }

    /**
     * 获取布局id
     */
    abstract fun getLayoutId(): Int

    /**
     * 初始化view
     */
    abstract fun onViewCreated(savedInstanceState: Bundle?)

    /**
     * 创建观察者
     */
    open fun createObserver(){}

    /**
     * 初始化数据
     */
    open fun initData() {

    }

    /**
     * 初始化布局
     */
    protected open fun initLayout() {
        mViewModel = createViewModel()
        if (!isUserDb) {
            setContentView(getLayoutId())
        } else {
            initDataBind()
        }
        initSoftKeyboard()

    }
    /**
     * 如果当前的 Activity(singleTop 启动模式) 被复用时会回调
     */
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // 设置为当前的 Intent,避免 Activity 被杀死后重启 Intent 还是最原先的那个
        setIntent(intent)
    }

    fun setUserDataBinding(isUserDb: Boolean) {
        this.isUserDb = isUserDb
    }

    /**
     * 供子类BaseVmDbActivity 初始化Databinding操作
     */
    open fun initDataBind() {}

    /**
     * 创建viewModel
     */
    private fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVmClazz(this))
    }

    /**
     * 初始化软键盘
     */
    protected fun initSoftKeyboard() {
        // 点击外部隐藏软键盘,提升用户体验
        getContentView()?.setOnClickListener { v: View? -> hideSoftKeyboard(this) }
    }

    /**
     * 和 setContentView 对应的方法
     */
    fun getContentView(): ViewGroup? {
        return findViewById(Window.ID_ANDROID_CONTENT)
    }

    fun startActivityForResult(
        clazz: Class<out Activity?>?,
        callback: OnActivityCallback
    ) {
        startActivityForResult(Intent(this, clazz), null, callback)
    }

    fun startActivityForResult(intent: Intent?, callback: OnActivityCallback) {
        startActivityForResult(intent, null, callback)
    }

    fun startActivityForResult(
        intent: Intent?,
        options: Bundle?,
        callback: OnActivityCallback
    ) {
        // 回调还没有结束,所以不能再次调用此方法,这个方法只适合一对一回调,其他需求请使用原生的方法实现
        if (mActivityCallback == null) {
            mActivityCallback = callback
            // 随机生成请求码,这个请求码必须在 2 的 16 次幂以内,也就是 0 - 65535
            mActivityRequestCode = Random().nextInt(Math.pow(2.0, 16.0).toInt())
            startActivityForResult(intent, mActivityRequestCode, options)
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (mActivityCallback != null && mActivityRequestCode == requestCode) {
            mActivityCallback?.onActivityResult(resultCode, data)
            mActivityCallback = null
        } else{
            super.onActivityResult(requestCode, resultCode, data)
        }
    }
    override fun startActivityForResult(intent: Intent?, requestCode: Int, options: Bundle?) {
        hideSoftKeyboard(this)
        // 查看源码得知 startActivity 最终也会调用 startActivityForResult
        super.startActivityForResult(intent, requestCode, options)
    }

    interface OnActivityCallback {
        /**
         * 结果回调
         *
         * @param resultCode        结果码
         * @param data              数据
         */
        fun onActivityResult(resultCode: Int, data: Intent?)
    }

    override fun finish() {
        hideSoftKeyboard(this)
        super.finish()
    }
    override fun onDestroy() {
        removeCallbacks()
        super.onDestroy()
    }
}

4.BaseActivity


import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.SparseArray
import android.view.KeyEvent
import android.view.ViewGroup
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import com.weiaiyun.baselib.action.*
import com.weiaiyun.baselib.base.fragment.BaseFragment
import java.util.*
import kotlin.math.pow

/**
 *    author : Android 轮子哥
 *    github : https://github.com/getActivity/AndroidProject-Kotlin
 *    time   : 2018/10/18
 *    desc   : Activity 技术基类
 */
abstract class BaseActivity : AppCompatActivity(), ActivityAction,
    ClickAction, HandlerAction, BundleAction, KeyboardAction {

    companion object {

        /** 错误结果码 */
        const val RESULT_ERROR: Int = -2
    }

    /** Activity 回调集合 */
    private val activityCallbacks: SparseArray<OnActivityCallback?> by lazy { SparseArray(1) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initActivity()
    }

    protected open fun initActivity() {
        initLayout()
        initView()
        initData()
    }

    /**
     * 获取布局 ID
     */
    protected abstract fun getLayoutId(): Int

    /**
     * 初始化控件
     */
    protected abstract fun initView()

    /**
     * 初始化数据
     */
    protected abstract fun initData()

    /**
     * 初始化布局
     */
    protected open fun initLayout() {
        if (getLayoutId() > 0) {
            setContentView(getLayoutId())
            initSoftKeyboard()
        }
    }

    /**
     * 初始化软键盘
     */
    protected open fun initSoftKeyboard() {
        // 点击外部隐藏软键盘,提升用户体验
        getContentView()?.setOnClickListener {
            // 隐藏软键,避免内存泄漏
            hideKeyboard(currentFocus)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        removeCallbacks()
    }

    override fun finish() {
        super.finish()
        // 隐藏软键,避免内存泄漏
        hideKeyboard(currentFocus)
    }

    /**
     * 如果当前的 Activity(singleTop 启动模式) 被复用时会回调
     */
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // 设置为当前的 Intent,避免 Activity 被杀死后重启 Intent 还是最原先的那个
        setIntent(intent)
    }

    override fun getBundle(): Bundle? {
        return intent.extras
    }

    /**
     * 和 setContentView 对应的方法
     */
    open fun getContentView(): ViewGroup? {
        return findViewById(Window.ID_ANDROID_CONTENT)
    }

    override fun getContext(): Context {
        return this
    }

    override fun startActivity(intent: Intent) {
        return super<AppCompatActivity>.startActivity(intent)
    }

    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
        val fragments: MutableList<Fragment?> = supportFragmentManager.fragments
        for (fragment: Fragment? in fragments) {
            // 这个 Fragment 必须是 BaseFragment 的子类,并且处于可见状态
            if (fragment !is BaseFragment<*> || fragment.getLifecycle().currentState != Lifecycle.State.RESUMED) {
                continue
            }
            // 将按键事件派发给 Fragment 进行处理
            if (fragment.dispatchKeyEvent(event)) {
                // 如果 Fragment 拦截了这个事件,那么就不交给 Activity 处理
                return true
            }
        }
        return super.dispatchKeyEvent(event)
    }

    @Suppress("deprecation")
    override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
        // 隐藏软键,避免内存泄漏
        hideKeyboard(currentFocus)
        // 查看源码得知 startActivity 最终也会调用 startActivityForResult
        super.startActivityForResult(intent, requestCode, options)
    }

    /**
     * startActivityForResult 方法优化
     */
    open fun startActivityForResult(clazz: Class<out Activity>, callback: OnActivityCallback?) {
        startActivityForResult(Intent(this, clazz), null, callback)
    }

    open fun startActivityForResult(intent: Intent, callback: OnActivityCallback?) {
        startActivityForResult(intent, null, callback)
    }

    @Suppress("deprecation")
    open fun startActivityForResult(intent: Intent, options: Bundle?, callback: OnActivityCallback?) {
        // 请求码必须在 2 的 16 次方以内
        val requestCode: Int = Random().nextInt(2.0.pow(16.0).toInt())
        activityCallbacks.put(requestCode, callback)
        startActivityForResult(intent, requestCode, options)
    }

    @Suppress("deprecation")
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        var callback: OnActivityCallback?
        if ((activityCallbacks.get(requestCode).also { callback = it }) != null) {
            callback?.onActivityResult(resultCode, data)
            activityCallbacks.remove(requestCode)
            return
        }
        super.onActivityResult(requestCode, resultCode, data)
    }

    interface OnActivityCallback {

        /**
         * 结果回调
         *
         * @param resultCode        结果码
         * @param data              数据
         */
        fun onActivityResult(resultCode: Int, data: Intent?)
    }
}


import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.databinding.ViewDataBinding
import com.amap.api.location.AMapLocation
import com.amap.api.location.AMapLocationClient
import com.amap.api.location.AMapLocationClientOption
import com.amap.api.location.AMapLocationClientOption.AMapLocationMode
import com.amap.api.location.AMapLocationClientOption.AMapLocationProtocol
import com.gyf.immersionbar.ImmersionBar
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.weiaiyun.baselib.base.activity.WayBaseDbActivity
import com.weiaiyun.baselib.base.dialog.WayBaseDialog
import com.weiaiyun.baselib.base.viewmodel.WayBaseViewModel
import com.weiaiyun.commonlib.action.TitleBarAction
import com.weiaiyun.commonlib.ext.showToast
import com.weiaiyun.dialoglib.HintDialog
import com.weiaiyun.dialoglib.WaitDialog
import com.weiaiyun.module_main.R
import com.weiaiyun.widgetlib.bar.TitleBar

abstract class BaseDbActivity<VM : BaseViewModel, DB : ViewDataBinding> :
    BaseDbActivity<VM, DB>(),
    TitleBarAction {
    private var mImmersionBar: ImmersionBar? = null
    private var mDialog: WayBaseDialog? = null
    private var mDialogTotal = 0
    public var mLocation: AMapLocation? = null
    private var mLocationListener:LocationListener?=null
    private var mTitleBar: TitleBar? = null
    private var locationClient: AMapLocationClient? = null
    private var locationOption: AMapLocationClientOption? = null
    override fun initLayout() {
        super.initLayout()
        if (titleBar != null) {
            titleBar?.setOnTitleBarListener(this)
        }
        if (isStatusBarEnabled()) {
            getStatusBarConfig()?.init()
            if (titleBar != null) {
                ImmersionBar.setTitleBar(this, titleBar)
            } else {
                if (setStatusBarLayout() != null) {
                    ImmersionBar.setTitleBar(this, setStatusBarLayout())
                }
            }
        }

    }


    override fun onViewCreated(savedInstanceState: Bundle?) {
        initLocation()
    }

    open fun setLocationListener(listener: LocationListener) {
        mLocationListener = listener
    }

    private fun initLocation() {
        //初始化client
        try {
            locationClient = AMapLocationClient(this.applicationContext)
            locationOption = getDefaultOption()
            //设置定位参数
            locationClient?.setLocationOption(locationOption)
            // 设置定位监听
            locationClient?.setLocationListener {
                Log.d("xxx","$it")
                mLocation =it
                mLocationListener?.onAMapLocationListener(it)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }


    private fun getDefaultOption(): AMapLocationClientOption? {
        val mOption = AMapLocationClientOption()
        mOption.locationMode =
            AMapLocationMode.Hight_Accuracy //可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式
        mOption.isGpsFirst = false //可选,设置是否gps优先,只在高精度模式下有效。默认关闭
        mOption.httpTimeOut = 30000 //可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效
        mOption.interval = 2000 //可选,设置定位间隔。默认为2秒
        mOption.isNeedAddress = true //可选,设置是否返回逆地理地址信息。默认是true
        mOption.isOnceLocation = false //可选,设置是否单次定位。默认是false
        mOption.isOnceLocationLatest =
            false //可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用
        AMapLocationClientOption.setLocationProtocol(AMapLocationProtocol.HTTP) //可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP
        mOption.isSensorEnable = false //可选,设置是否使用传感器。默认是false
        mOption.isWifiScan =
            true //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差
        mOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true
        mOption.geoLanguage =
            AMapLocationClientOption.GeoLanguage.DEFAULT //可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言)
        return mOption
    }

    open fun startLocation(listener: LocationListener) {
        this.mLocationListener = listener
        XXPermissions.with(this)
            //.permission(Permission.Group.STORAGE)
            .permission(Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION)
            .request(object : OnPermissionCallback {
                override fun onGranted(permissions: MutableList<String>?, all: Boolean) {
                    if (all) {
                        try {
                            // 设置定位参数
                            locationClient?.setLocationOption(locationOption)
                            // 启动定位
                            locationClient?.startLocation()
                        } catch (e: java.lang.Exception) {
                            showToast(HintDialog.ICON_ERROR, e.message)
                            //e.printStackTrace()
                        }
                    }
                }

                override fun onDenied(permissions: MutableList<String>?, never: Boolean) {

                    if (never) {
                        showToast(R.string.txt_common_permission_fail)
                        XXPermissions.startPermissionActivity(this@BaseDbActivity, permissions)
                    } else {
                        showToast(R.string.txt_common_permission_hint)
                    }
                }

            })

    }

    open fun stopLocation() {
        try {
            // 停止定位
            locationClient?.stopLocation()

        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    open fun isShowDialog(): Boolean {
        return mDialog != null && mDialog?.isShowing!!
    }

    open fun showDialog(text: String?) {
        mDialogTotal++
        postDelayed({
            if (mDialogTotal > 0 && !isFinishing) {
                if (mDialog == null) {
                    mDialog = WaitDialog.Builder(this)
                        .setMessage(text)
                        .setCancelable(false)
                        .create()
                }
                mDialog?.let {
                    if (!it.isShowing) {
                        it.show()
                    }
                }

            }
        }, 300)
    }

    open fun hideDialog() {
        if (mDialogTotal > 0) {
            mDialogTotal--
        }
        mDialog?.let {
            if (mDialogTotal == 0 && it.isShowing && !isFinishing) {
                it.dismiss()
            }
        }

    }
    override fun getBundle(): Bundle? {
        return intent.extras
    }

    override fun setTitle(id: Int) {
        title = getString(id)
    }

    override fun setTitle(title: CharSequence?) {
        if (titleBar != null) {
            titleBar?.title = title
        }
    }

    override fun getTitleBar(): TitleBar? {
        if (mTitleBar == null) {
            mTitleBar = obtainTitleBar(getContentView())
        }
        return mTitleBar
    }

    override fun onLeftClick(v: View?) {
        onBackPressed()
    }

    protected open fun setStatusBarLayout(): View? {
        return null
    }

    protected open fun isStatusBarEnabled(): Boolean {
        return true
    }

    protected open fun isStatusBarDarkFont(): Boolean {
        return true
    }

    protected open fun createStatusBarConfig(): ImmersionBar {
        return ImmersionBar.with(this)
            .transparentStatusBar()
            .statusBarDarkFont(isStatusBarDarkFont())
            .navigationBarColor(R.color.white)
    }

    open fun getStatusBarConfig(): ImmersionBar? {
        if (mImmersionBar == null) {
            mImmersionBar = createStatusBarConfig()
        }
        return mImmersionBar
    }

    override fun startActivityForResult(intent: Intent?, requestCode: Int, options: Bundle?) {
        super.startActivityForResult(intent, requestCode, options)
        overridePendingTransition(R.anim.right_in_activity, R.anim.right_out_activity)
    }

    override fun finish() {
        super.finish()
        overridePendingTransition(R.anim.left_in_activity, R.anim.left_out_activity)
    }

    interface LocationListener {
        fun onAMapLocationListener(location: AMapLocation) {

        }
    }

    private fun destroyLocation() {
        if (null != locationClient) {

            locationClient?.onDestroy()
            locationClient = null
            locationOption = null
        }
    }

    override fun onDestroy() {
        if (isShowDialog()) {
            hideDialog()
        }
        mDialog = null
        destroyLocation()
        super.onDestroy()
    }
}
这个类有引用 高德地图的方法 如果有同学使用的话 把报错的AMAP 的方法删除就可以了

这样整个BaseActivy 基类就抽出来了 我们来看一下使用

class MainActivity : BaseDbActivity<BaseViewModel, ActivityMainBinding>() {
/*
所有的Activity 都继承与这个 BaseDbActivity
*/

override fun getLayoutId(): Int { return R.layout.activity_main } getLayoutId 绑定xml 视图

}

onViewCreated 视图创建完成后

mDatabind 来绑定XMl 的组件

大功告成