国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

大型Android項(xiàng)目架構(gòu):基于組件化+模塊化+Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM架構(gòu)實(shí)現(xiàn)WanAndroid客戶端

這篇具有很好參考價(jià)值的文章主要介紹了大型Android項(xiàng)目架構(gòu):基于組件化+模塊化+Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM架構(gòu)實(shí)現(xiàn)WanAndroid客戶端。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

前言:茍有恒,何必三更眠五更起;最無(wú)益,莫過(guò)一日曝十日寒。

前言

之前一直想寫個(gè) WanAndroid 項(xiàng)目來(lái)鞏固自己對(duì) Kotlin+Jetpack+協(xié)程 等知識(shí)的學(xué)習(xí),但是一直沒(méi)有時(shí)間。這里重新行動(dòng)起來(lái),從項(xiàng)目搭建到完成前前后后用了兩個(gè)月時(shí)間,平常時(shí)間比較少,基本上都是只能利用零碎的時(shí)間來(lái)寫。但不再是想寫一個(gè)簡(jiǎn)單的玩安卓項(xiàng)目,我從多個(gè)大型項(xiàng)目中學(xué)習(xí)和吸取經(jīng)驗(yàn),從0到1打造一個(gè)符合大型項(xiàng)目的架構(gòu)模式。

這或許是一個(gè)縮影,但是麻雀雖小,五臟俱全,這肯定能給大家?guī)?lái)一些想法和思考。當(dāng)然這個(gè)項(xiàng)目的功能并未全部完善,因?yàn)槲覀兊哪康牟皇窃煲粋€(gè) WanAndroid 客戶端,而是學(xué)習(xí)搭建和使用 Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM+組件化+模塊化+短視頻 這一種架構(gòu),更好的提升自己。后續(xù)我也會(huì)不斷完善和優(yōu)化,在保證擁有一個(gè)正常的 APP 功能之外,繼續(xù)加入 Compose依賴注入Hint,性能優(yōu)化MVI模式,支付功能等的實(shí)踐。

一、項(xiàng)目簡(jiǎn)介

  • 項(xiàng)目采用 Kotlin 語(yǔ)言編寫,結(jié)合 Jetpack 相關(guān)控件,NavigationLifecyle,DataBindingLiveData,ViewModel等搭建的 MVVM 架構(gòu)模式;
  • 通過(guò)組件化,模塊化拆分,實(shí)現(xiàn)項(xiàng)目更好解耦和復(fù)用,ARouter 實(shí)現(xiàn)模塊間通信;
  • 使用 協(xié)程+Flow+Retrofit+OkHttp 優(yōu)雅地實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求;
  • 通過(guò) mmkv,Room 數(shù)據(jù)庫(kù)等實(shí)現(xiàn)對(duì)數(shù)據(jù)緩存的管理;
  • 使用谷歌 ExoPlayer 實(shí)現(xiàn)短視頻播放;
  • 使用 Glide 完成圖片加載;
  • 通過(guò) WanAndroid 提供的 API 實(shí)現(xiàn)的一款玩安卓客戶端。
    kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

項(xiàng)目使用MVVM架構(gòu)模式,基本上遵循 Google 推薦的架構(gòu),對(duì)于 Repository,Google 認(rèn)為 ViewModel 僅僅用來(lái)做數(shù)據(jù)的存儲(chǔ),數(shù)據(jù)加載應(yīng)該由 Repository 來(lái)完成。通過(guò) Room 數(shù)據(jù)庫(kù)實(shí)現(xiàn)對(duì)數(shù)據(jù)的緩存,在無(wú)網(wǎng)絡(luò)或者弱網(wǎng)的情況下優(yōu)先展示緩存數(shù)據(jù)。
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

項(xiàng)目截圖:

kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

項(xiàng)目地址: https://github.com/suming77/SumTea_Android

二、項(xiàng)目詳情

2.1 基礎(chǔ)架構(gòu)

(1) BaseActicity

通過(guò)單一職責(zé)原則,實(shí)現(xiàn)職能分級(jí),使用者只需要按需繼承即可。

  • BaseActivity:?????封裝了通用的 init 方法,初始化布局,加載彈框等方法,提供了原始的添加布局的方式;
  • BaseDataBindActivity:繼承自 BaseActivity,通過(guò) dataBinding 綁定布局,利用泛型參數(shù)反射創(chuàng)建布局文件實(shí)例,獲取布局 view,不再需要 findViewById();
val type = javaClass.genericSuperclass
val vbClass: Class<DB> = type!!.saveAs<ParameterizedType>().actualTypeArguments[0].saveAs()
val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
mBinding = method.invoke(this, layoutInflater)!!.saveAsUnChecked()
setContentView(mBinding.root)
  • BaseMvvmActivity:?繼承自 BaseDataBindActivity,通過(guò)泛型參數(shù)反射自動(dòng)創(chuàng)建 ViewModel 實(shí)例,更方便使用 ViewModel 實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求。
val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
mViewModel = ViewModelProvider(this).get(argument[1] as Class<VM>)

(2) BaseFragment

BaseFragment 的封裝與上面的 BaseActivity 類似。

(3) BaseRecyclerViewAdapter

  • BaseRecyclerViewAdapter:封裝了 RecyclerViewAdapter 基類,實(shí)現(xiàn)提供創(chuàng)建 ViewHolder 能力,提供添加頭尾布局能力,通用的 Item 點(diǎn)擊事件,提供 dataBinding 能力,不再需要 findViewById(),提供了多種刷新數(shù)據(jù)的方式,全局刷新,局部刷新等等。

  • BaseMultiItemAdapter:??提供了實(shí)現(xiàn)多種不同布局的 Adapter,根據(jù)不同的 ViewType 實(shí)現(xiàn)不同的 ViewBinding,再創(chuàng)建返回不同的 ViewHolder。

(4) Ext拓展類

項(xiàng)目中提供了大量控件擴(kuò)展類,能夠快速開(kāi)發(fā),提高效率

  1. ResourceExt:??資源文件擴(kuò)展類;
  2. TextViewExt:??TextView 擴(kuò)展類;
  3. SpanExt:????Span 拓展類,實(shí)現(xiàn)多種 Span 效果;
  4. RecyclerViewExt:一行代碼快速實(shí)現(xiàn)添加垂直分割線,網(wǎng)格分割線;
  5. ViewExt:????View 擴(kuò)展類,實(shí)現(xiàn)點(diǎn)擊防抖,添加間距,設(shè)置寬度,設(shè)置可見(jiàn)性等等;
  6. EditTextExt:??通過(guò) Flow 構(gòu)建輸入框文字變化流,filter{} 實(shí)現(xiàn)數(shù)據(jù)過(guò)濾,避免無(wú)效請(qǐng)求,debounce() 實(shí)現(xiàn)防抖;
  7. GsonExt:????一行代碼快速實(shí)現(xiàn) Bean 和 Json 之間的相互轉(zhuǎn)換。
//將Bean對(duì)象轉(zhuǎn)換成json字符串
fun Any.toJson(includeNulls: Boolean = true): String {
    return gson(includeNulls).toJson(this)
}
//將json字符串轉(zhuǎn)換成目標(biāo)Bean對(duì)象
inline fun <reified T> String.toBean(includeNulls: Boolean = true): T {
    return gson(includeNulls).fromJson(this, object : TypeToken<T>() {}.type)
}

(5) xlog

XLog 是一個(gè)高性能文本存儲(chǔ)方案,在真實(shí)環(huán)境中經(jīng)受了微信數(shù)億級(jí)別的考驗(yàn),具有很好的穩(wěn)定性。由于其是使用C語(yǔ)言來(lái)實(shí)現(xiàn)的,故有占用性能、內(nèi)存小,存儲(chǔ)速度快等優(yōu)點(diǎn),支持多線程,甚至多進(jìn)程的使用,支持定期刪除日志,同時(shí),擁有特定算法,進(jìn)行了文件的壓縮,甚至可以配置文件加密。

利用 Xlog 建設(shè)客戶端運(yùn)行時(shí)日志體系,遠(yuǎn)程日志按需回?fù)?,以打點(diǎn)的形式記錄關(guān)鍵執(zhí)行流程。

2.2 Jetpack組件

kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻
Android Jetpack是一組 Android 軟件組件、工具和指南,它們可以幫助開(kāi)發(fā)者構(gòu)建高質(zhì)量、穩(wěn)定的 Android 應(yīng)用程序。Jetpack 中包含多個(gè)庫(kù),它們旨在解決 Android 應(yīng)用程序開(kāi)發(fā)中的常見(jiàn)問(wèn)題,并提供一致的 API 和開(kāi)發(fā)體驗(yàn)。

項(xiàng)目中僅僅使用到上圖的一小部分組件。

(1) Navtgation

Navtgation 作為構(gòu)建應(yīng)用內(nèi)界面的框架,重點(diǎn)是讓單 Activity 應(yīng)用成為首選架構(gòu)(一個(gè)應(yīng)用只需一個(gè) Activity),它的定位是頁(yè)面路由。

項(xiàng)目中主頁(yè)分為5個(gè) Tab,主要為首頁(yè)、分類、體系、我的。使用 BottomNavigationView + Navigation 來(lái)搭建。通過(guò) menu 來(lái)配置底部菜單,通過(guò) NavHostFragment 來(lái)配置各個(gè) Fragment。同時(shí)解決了 NavigationBottomNavigationView 結(jié)合使用時(shí),點(diǎn)擊 tab,F(xiàn)ragment 每次都會(huì)重新創(chuàng)建問(wèn)題。解決方法是自定義 FragmentNavigator,將內(nèi)部 replace() 替換為 show()/hide()。

(2) ViewBinding&DataBinding

  • ViewBinding 的出現(xiàn)就是不再需要寫 findViewById();

  • DataBinding 是一種工具,它解決了 View 和數(shù)據(jù)之間的雙向綁定;減少代碼模板,不再需要寫findViewById()釋放 Activity/Fragment,可以在 XML 中完成數(shù)據(jù),事件綁定工作,讓 Activity/Fragment 更加關(guān)心核心業(yè)務(wù);數(shù)據(jù)綁定空安全,在 XML 中綁定數(shù)據(jù)它是空安全的,因?yàn)?DataBinding 在數(shù)據(jù)綁定上會(huì)自動(dòng)裝箱和空判斷,所以大大減少了 NPE 問(wèn)題。

(3) ViewModel

ViewModel 具備生命感知能力的數(shù)據(jù)存儲(chǔ)組件。頁(yè)面配置更改數(shù)據(jù)不會(huì)丟失,數(shù)據(jù)共享(單 Activity 多 Fragment 場(chǎng)景下的數(shù)據(jù)共享),以生命周期的方式管理界面相關(guān)的數(shù)據(jù),通常和 DataBinding 配合使用,為實(shí)現(xiàn) MVVM 架構(gòu)提供了強(qiáng)有力的支持。

(4) LiveData

LiveData 是一個(gè)具有生命周期感知能力的數(shù)據(jù)訂閱,分發(fā)組件。支持共享資源(一個(gè)數(shù)據(jù)支持被多個(gè)觀察者接收的),支持粘性事件的分發(fā),不再需要手動(dòng)處理生命周期(和宿主生命周期自動(dòng)關(guān)聯(lián)),確保界面符合數(shù)據(jù)狀態(tài)。在底層數(shù)據(jù)庫(kù)更改時(shí)通知 View。

(5) Room

一個(gè)輕量級(jí) orm 數(shù)據(jù)庫(kù),本質(zhì)上是一個(gè) SQLite 抽象層。使用更加簡(jiǎn)單(Builder 模式,類似 Retrofit),通過(guò)注解的形式實(shí)現(xiàn)相關(guān)功能,編譯時(shí)自動(dòng)生成實(shí)現(xiàn)類 IMPL

這里主要用于首頁(yè)視頻列表緩存數(shù)據(jù),與 LiveData 和 Flow 結(jié)合處理可以避免不必要的 NPE,可以監(jiān)聽(tīng)數(shù)據(jù)庫(kù)表中的數(shù)據(jù)的變化,也可以和 RXJava 的 Observer 使用,一旦發(fā)生了 insert,update,delete等操作,Room 會(huì)自動(dòng)讀取表中最新的數(shù)據(jù),發(fā)送給 UI 層,刷新頁(yè)面。

Room 庫(kù)架構(gòu)的示意圖:
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

Room 包含三個(gè)主要組件:

  • 數(shù)據(jù)庫(kù)類:用于保存數(shù)據(jù)庫(kù)并作為應(yīng)用持久性數(shù)據(jù)底層連接的主要訪問(wèn)點(diǎn);
  • 數(shù)據(jù)實(shí)體:用于表示應(yīng)用的數(shù)據(jù)庫(kù)中的表;
  • 數(shù)據(jù)訪問(wèn)對(duì)象 (DAO):提供您的應(yīng)用可用于查詢、更新、插入和刪除數(shù)據(jù)庫(kù)中的數(shù)據(jù)的方法。

Dao

@Dao
interface VideoListCacheDao {
    //插入單個(gè)數(shù)據(jù)
    @Insert(entity = VideoInfo::class, onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(videoInfo: VideoInfo) 

    //插入多個(gè)數(shù)據(jù)
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(videoList: MutableList<VideoInfo>)

    //刪除指定item 使用主鍵將傳遞的實(shí)體實(shí)例與數(shù)據(jù)庫(kù)中的行進(jìn)行匹配。如果沒(méi)有具有相同主鍵的行,則不會(huì)進(jìn)行任何更改
    @Delete
    fun delete(videoInfo: VideoInfo): Int

    //刪除表中所有數(shù)據(jù)
    @Query("DELETE FROM $TABLE_VIDEO_LIST")
    suspend fun deleteAll()

    //更新某個(gè)item,不指定的entity也可以,會(huì)根據(jù)你傳入的參數(shù)對(duì)象來(lái)找到你要操作的那張表
    @Update
    fun update(videoInfo: VideoInfo): Int

    //根據(jù)id更新數(shù)據(jù)
    @Query("UPDATE $TABLE_VIDEO_LIST SET title=:title WHERE id=:id")
    fun updateById(id: Long, title: String)

    //查詢所有數(shù)據(jù)
    @Query("SELECT * FROM $TABLE_VIDEO_LIST")
    fun queryAll(): MutableList<VideoInfo>?

    //根據(jù)id查詢某個(gè)數(shù)據(jù)
    @Query("SELECT * FROM $TABLE_VIDEO_LIST WHERE id=:id")
    fun query(id: Long): VideoInfo?

    //通過(guò)LiveData以觀察者的形式獲取數(shù)據(jù)庫(kù)數(shù)據(jù),可以避免不必要的NPE
    @Query("SELECT * FROM $TABLE_VIDEO_LIST")
    fun queryAllLiveData(): LiveData<List<VideoInfo>>
}

Database

@Database(entities = [VideoInfo::class], version = 1, exportSchema = false)
abstract class SumDataBase : RoomDatabase() {
    //抽象方法或者抽象類標(biāo)記
    abstract fun videoListDao(): VideoListCacheDao

    companion object {
        private var dataBase: SumDataBase? = null

        //同步鎖,可能在多個(gè)線程中同時(shí)調(diào)用
        @Synchronized
        fun getInstance(): SumDataBase {
            return dataBase ?: Room.databaseBuilder(SumAppHelper.getApplication(), SumDataBase::class.java, "SumTea_DB")
                    //是否允許在主線程查詢,默認(rèn)是false
                    .allowMainThreadQueries()
                    .build()
        }
    }
}

注意:Room 數(shù)據(jù)庫(kù)中的 Dao 中定義數(shù)據(jù)庫(kù)操作的方法一定要確保用法正確,否則會(huì)導(dǎo)致 Room 編譯時(shí)生成的實(shí)現(xiàn)類錯(cuò)誤,編譯不通過(guò)等問(wèn)題。

2.3 網(wǎng)絡(luò)請(qǐng)求庫(kù)

項(xiàng)目的網(wǎng)絡(luò)請(qǐng)求封裝提供了兩種方式的實(shí)現(xiàn),一種是協(xié)程+Retrofit+ViewModel+Repository,像官網(wǎng)那樣加一層 Repository 去管理網(wǎng)絡(luò)請(qǐng)求調(diào)用;另一種方式是通過(guò) Flow 流配合 Retrofit 更優(yōu)雅實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,對(duì)比官網(wǎng)的做法更加簡(jiǎn)潔。

(1) Retrofit+協(xié)程+Repository

BaseViewModel

open class BaseViewModel : ViewModel() {
    //需要運(yùn)行在協(xié)程作用域中
    suspend fun <T> safeApiCall(
        errorBlock: suspend (Int?, String?) -> Unit,
        responseBlock: suspend () -> T?
    ): T? {
        try {
            return responseBlock()
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtil.e(e)
            val exception = ExceptionHandler.handleException(e)
            errorBlock(exception.errCode, exception.errMsg)
        }
        return null
    }
}

BaseRepository

open class BaseRepository {
    //IO中處理請(qǐng)求
    suspend fun <T> requestResponse(requestCall: suspend () -> BaseResponse<T>?): T? {
        val response = withContext(Dispatchers.IO) {
            withTimeout(10 * 1000) {
                requestCall()
            }
        } ?: return null

        if (response.isFailed()) {
            throw ApiException(response.errorCode, response.errorMsg)
        }
        return response.data
    }
}

HomeRepository的使用

class HomeRepository : BaseRepository() {
    //項(xiàng)目tab
    suspend fun getProjectTab(): MutableList<ProjectTabItem>? {
        return requestResponse {
            ApiManager.api.getProjectTab()
        }
    }
}

HomeViewModel的使用

class HomeViewModel : BaseViewModel() {
    //請(qǐng)求項(xiàng)目Tab數(shù)據(jù)
    fun getProjectTab(): LiveData<MutableList<ProjectTabItem>?> {
        return liveData {
            val response = safeApiCall(errorBlock = { code, errorMsg ->
                TipsToast.showTips(errorMsg)
            }) {
                homeRepository.getProjectTab()
            }
            emit(response)
        }
    }
}

(2) Flow優(yōu)雅實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求

Flow 其實(shí)和 RxJava 很像,非常方便,用它來(lái)做網(wǎng)絡(luò)請(qǐng)求更加簡(jiǎn)潔。
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

suspend fun <T> requestFlowResponse(
    errorBlock: ((Int?, String?) -> Unit)? = null,
    requestCall: suspend () -> BaseResponse<T>?,
    showLoading: ((Boolean) -> Unit)? = null
): T? {
    var data: T? = null
    //1.執(zhí)行請(qǐng)求
    flow {
        //設(shè)置超時(shí)時(shí)間
        val response = requestCall()

        if (response?.isFailed() == true) {
            errorBlock.invoke(response.errorCode, response.errorMsg)
        }
        //2.發(fā)送網(wǎng)絡(luò)請(qǐng)求結(jié)果回調(diào)
        emit(response)
        //3.指定運(yùn)行的線程,flow {}執(zhí)行的線程
    }.flowOn(Dispatchers.IO)
            .onStart {
                //4.請(qǐng)求開(kāi)始,展示加載框
                showLoading?.invoke(true)
            }
            //5.捕獲異常
            .catch { e ->
                e.printStackTrace()
                LogUtil.e(e)
                val exception = ExceptionHandler.handleException(e)
                errorBlock?.invoke(exception.errCode, exception.errMsg)
            }
            //6.請(qǐng)求完成,包括成功和失敗
            .onCompletion {
                showLoading?.invoke(false)
                //7.調(diào)用collect獲取emit()回調(diào)的結(jié)果,就是請(qǐng)求最后的結(jié)果
            }.collect {
                data = it?.data
            }
    return data
}

2.4 圖片加載庫(kù)

Glide

圖片加載利用 Glide 進(jìn)行了簡(jiǎn)單的封裝,對(duì) ImageView 做擴(kuò)展函數(shù)處理:

//加載圖片,開(kāi)啟緩存
fun ImageView.setUrl(url: String?) {
    if (ActivityManager.isActivityDestroy(context)) {
        return
    }
    Glide.with(context).load(url)
            .placeholder(R.mipmap.default_img) // 占位符,異常時(shí)顯示的圖片
            .error(R.mipmap.default_img) // 錯(cuò)誤時(shí)顯示的圖片
            .skipMemoryCache(false) //啟用內(nèi)存緩存
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE) //磁盤緩存策略
            .into(this)
}

//加載圓形圖片
fun ImageView.setUrlCircle(url: String?) {
    if (ActivityManager.isActivityDestroy(context)) return
    Glide.with(context).load(url)
            .placeholder(R.mipmap.default_head)
            .error(R.mipmap.default_head)
            .skipMemoryCache(false) //啟用內(nèi)存緩存
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
            .transform(CenterCrop()) // 圓形
            .into(this)
}

//加載圓角圖片
fun ImageView.setUrlRound(url: String?, radius: Int = 10) {
    if (ActivityManager.isActivityDestroy(context)) return
    Glide.with(context).load(url)
            .placeholder(R.mipmap.default_img)
            .error(R.mipmap.default_img)
            .skipMemoryCache(false) // 啟用內(nèi)存緩存
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
            .transform(CenterCrop(), RoundedCorners(radius))
            .into(this)
}

//加載Gif圖片
fun ImageView.setUrlGif(url: String?) {
    if (ActivityManager.isActivityDestroy(context)) return
    Glide.with(context).asGif().load(url)
            .skipMemoryCache(true)
            .diskCacheStrategy(DiskCacheStrategy.DATA)
            .placeholder(R.mipmap.default_img)
            .error(R.mipmap.default_img)
            .into(this)
}

/**
 * 設(shè)置圖片高斯模糊
 * @param radius 設(shè)置模糊度(在0.0到25.0之間),默認(rèn)25
 * @param sampling  圖片縮放比例,默認(rèn)1
 */
fun ImageView.setBlurView(url: String?, radius: Int = 25, sampling: Int = 1) {
    if (ActivityManager.isActivityDestroy(context)) return
    //請(qǐng)求配置
    val options = RequestOptions.bitmapTransform(BlurTransformation(radius, sampling))
    Glide.with(context)
            .load(url)
            .placeholder(R.mipmap.default_img)
            .error(R.mipmap.default_img)
            .apply(options)
            .into(this)
}
  1. 修復(fù) Glide 的圖片裁剪和 ImageView 的 scaleType 的沖突問(wèn)題,Bitmap 會(huì)先圓角裁剪,再加載到 ImageView 中,如果 Bitmap 圖片尺寸大于 ImageView 尺寸,則會(huì)看不到,使用 CenterCrop() 重載,會(huì)先將 Bitmap 居中裁剪,再進(jìn)行圓角處理,這樣就能看到。
  2. 提供了 GIF 圖加載和圖片高斯模糊效果功能。

2.5 WebView

我們都知道原生的 WebView 存在很多問(wèn)題,使用騰訊X5內(nèi)核 WebView 進(jìn)行封裝,兼容性,穩(wěn)定性,安全性,速度都有很大的提升。

項(xiàng)目中使用 WebView 展示文章詳情頁(yè)。

2.6 MMKV

MMKV 是基于 mmap 內(nèi)存映射的 key-value 組件,底層序列化 / 反序列化使用 protobuf 實(shí)現(xiàn),性能高,穩(wěn)定性強(qiáng)。使用簡(jiǎn)單,支持多進(jìn)程。

在 App 啟動(dòng)時(shí)初始化 MMKV,設(shè)定 MMKV 的根目錄(files/mmkv/),例如在 Application 里:

public void onCreate() {
    super.onCreate();

    String rootDir = MMKV.initialize(this);
    LogUtil.e("mmkv root: " + rootDir);
}

MMKV 提供一個(gè)全局的實(shí)例,可以直接使用:

import com.tencent.mmkv.MMKV;
//……

MMKV kv = MMKV.defaultMMKV();

kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");

kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");

kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");

循環(huán)寫入隨機(jī)的 int 1k 次,有如下性能對(duì)比:
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻
項(xiàng)目中使用 MMKV 保存用戶相關(guān)信息,包括用戶登錄 Cookies,用戶名稱,手機(jī)號(hào)碼,搜索歷史數(shù)據(jù)等信息。

2.7 ExoPlayer視頻播放器

ExoPlayer 是 google 推出的開(kāi)源播放器,主要是集成了 Android 提供的一套解碼系統(tǒng)來(lái)解析視頻和音頻,將 MediaCodec 封裝地非常完善,形成了一個(gè)性能優(yōu)越,播放穩(wěn)定性較好的一個(gè)開(kāi)發(fā)播放器,支持更多的視頻播放格式(包含 DASH 和 SmoothStreaming,這2種 MediaPlayer 不支持),通過(guò)組件化自定義播放器,方便擴(kuò)展定制,持久的高速緩存,另外 ExoPlayer 包大小輕便,接入簡(jiǎn)單。

項(xiàng)目中使用 ExoPlayer 實(shí)現(xiàn)防抖音短視頻播放:

class VideoPlayActivity : BaseDataBindActivity<ActivityVideoPlayBinding>() {
    //創(chuàng)建exoplayer播放器實(shí)例,視屏畫面渲染工廠類,語(yǔ)音選擇器,緩存控制器
    private fun initPlayerView(): Boolean {
        //創(chuàng)建exoplayer播放器實(shí)例
        mPlayView = initStylePlayView()

        // 創(chuàng)建 MediaSource 媒體資源 加載的工廠類
        mMediaSource = ProgressiveMediaSource.Factory(buildCacheDataSource())

        mExoPlayer = initExoPlayer()
        //緩沖完成自動(dòng)播放
        mExoPlayer?.playWhenReady = mStartAutoPlay
        //將顯示控件綁定ExoPlayer
        mPlayView?.player = mExoPlayer
        
        //資源準(zhǔn)備,如果設(shè)置 setPlayWhenReady(true) 則資源準(zhǔn)備好就立馬播放。
        mExoPlayer?.prepare()
        return true
    }

    //初始化ExoPlayer
    private fun initExoPlayer(): ExoPlayer {
        val playerBuilder = ExoPlayer.Builder(this).setMediaSourceFactory(mMediaSource)
        //視頻每一幀的畫面如何渲染,實(shí)現(xiàn)默認(rèn)的實(shí)現(xiàn)類
        val renderersFactory: RenderersFactory = DefaultRenderersFactory(this)
        playerBuilder.setRenderersFactory(renderersFactory)
        //視頻的音視頻軌道如何加載,使用默認(rèn)的軌道選擇器
        playerBuilder.setTrackSelector(DefaultTrackSelector(this))
        //視頻緩存控制邏輯,使用默認(rèn)的即可
        playerBuilder.setLoadControl(DefaultLoadControl())

        return playerBuilder.build()
    }

    //創(chuàng)建exoplayer播放器實(shí)例
    private fun initStylePlayView(): StyledPlayerView {
        return StyledPlayerView(this).apply {
            controllerShowTimeoutMs = 10000
            setKeepContentOnPlayerReset(false)
            setShowBuffering(SHOW_BUFFERING_NEVER)//不展示緩沖view
            resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
            useController = false //是否使用默認(rèn)控制器,如需要可參考PlayerControlView
//            keepScreenOn = true
        }
    }

    //創(chuàng)建能夠 邊播放邊緩存的 本地資源加載和http網(wǎng)絡(luò)數(shù)據(jù)寫入的工廠類
    private fun buildCacheDataSource(): DataSource.Factory {
        //創(chuàng)建http視頻資源如何加載的工廠對(duì)象
        val upstreamFactory = DefaultHttpDataSource.Factory()

        //創(chuàng)建緩存,指定緩存位置,和緩存策略,為最近最少使用原則,最大為200m
        mCache = SimpleCache(
            application.cacheDir,
            LeastRecentlyUsedCacheEvictor(1024 * 1024 * 200),
            StandaloneDatabaseProvider(this)
        )

        //把緩存對(duì)象cache和負(fù)責(zé)緩存數(shù)據(jù)讀取、寫入的工廠類CacheDataSinkFactory 相關(guān)聯(lián)
        val cacheDataSinkFactory = CacheDataSink.Factory().setCache(mCache).setFragmentSize(Long.MAX_VALUE)
        return CacheDataSource.Factory()
                .setCache(mCache)
                .setUpstreamDataSourceFactory(upstreamFactory)
                .setCacheReadDataSourceFactory(FileDataSource.Factory())
                .setCacheWriteDataSinkFactory(cacheDataSinkFactory)
                .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
    }
}    

2.8 組件化&模塊化

組件化&模塊化有利于業(yè)務(wù)模塊分離,高內(nèi)聚,低耦合,代碼邊界清晰。有利于團(tuán)隊(duì)合作多線開(kāi)發(fā),加快編譯速度,提高開(kāi)發(fā)效率,管理更加方便,利于維護(hù)和迭代。
kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

宿主 App 中只有一個(gè) Application,整個(gè)業(yè)務(wù)被拆分為各個(gè) mod 模塊和 lib 組件庫(kù)。對(duì)一些功能組件進(jìn)行封裝抽取為 lib,給上層提供依賴。mod 模塊之間沒(méi)有任務(wù)依賴關(guān)系,通過(guò) Arouter 進(jìn)行通信。

(1) 模塊化

項(xiàng)目中通過(guò)以業(yè)務(wù)為維度把 App 拆分成主頁(yè)模塊,登錄模塊,搜索模塊,用戶模塊,視頻模塊等,相互間不可以訪問(wèn)不可以作為依賴,與此同時(shí)他們共同依賴于基礎(chǔ)庫(kù),網(wǎng)絡(luò)請(qǐng)求庫(kù),公共資源庫(kù),圖片加載庫(kù)等。如果還需要使用到啟動(dòng)器組件、Banner組件、數(shù)據(jù)庫(kù)Room組件等則單獨(dú)按需添加。

APP 殼工程負(fù)責(zé)打包環(huán)境,簽名,混淆規(guī)則,業(yè)務(wù)模塊集成,APP 主題等配置等工作,一般不包含任何業(yè)務(wù)。

(2) 組件化

模塊化和組件化最明顯的區(qū)別就是模塊相對(duì)組件來(lái)說(shuō)粒度更大。一個(gè)模塊中可能包含多個(gè)組件。在劃分的時(shí)候,模塊化是業(yè)務(wù)導(dǎo)向,組件化是功能導(dǎo)向。組件化是建立在模塊化思想上的一次演進(jìn)。

項(xiàng)目中以功能維度拆分了啟動(dòng)器組件、Banner組件、數(shù)據(jù)庫(kù)Room組件等組件。模塊化&組件化拆分后工程圖:

kotlin 組件化,Kotlin,android,kotlin,mvvm,組件化,短視頻

(3) 組件間通信

組件化之后就無(wú)法直接訪問(wèn)其他模塊的類和方法,這是個(gè)比較突出的問(wèn)題,就像原來(lái)可以直接使用 LogintManager 來(lái)拉起登錄,判斷是否已登錄,但是這個(gè)類已經(jīng)被拆分到了 mod_login 模塊下,而業(yè)務(wù)模塊之間是不能互相作為依賴的,所以無(wú)法在其他模塊直接使用 LogintManager。

主要借助阿里的路由框架 ARouter 實(shí)現(xiàn)組件間通信,把對(duì)外提供的能力,以接口的形式暴露出去。

比如在公共資源庫(kù)中的 service 包下創(chuàng)建 ILoginService,提供對(duì)外暴露登錄的能力,在 mod_login 模塊中提供 LoginServiceImpl 實(shí)現(xiàn)類,任意模塊就可以通過(guò) LoginServiceProvider 使用 iLoginService 對(duì)外提供暴露的能力。

  1. 公共資源庫(kù)中創(chuàng)建 ILoginService,提供對(duì)外暴露登錄的能力。
interface ILoginService : IProvider {
    //是否登錄
    fun isLogin(): Boolean

    //跳轉(zhuǎn)登錄頁(yè)
    fun login(context: Context)

    //登出
    fun logout(
        context: Context,
        lifecycleOwner: LifecycleOwner?,
        observer: Observer<Boolean>
    )
}
  1. mod_login 模塊中 LoginService 提供 ILoginService 的具體實(shí)現(xiàn)。
@Route(path = LOGIN_SERVICE_LOGIN)
class LoginService : ILoginService {

    //是否登錄
    override fun isLogin(): Boolean {
        return UserServiceProvider.isLogin()
    }

    //跳轉(zhuǎn)登錄頁(yè)
    override fun login(context: Context) {
        context.startActivity(Intent(context, LoginActivity::class.java))
    }

    //登出
    override fun logout(
        context: Context,
        lifecycleOwner: LifecycleOwner?,
        observer: Observer<Boolean>
    ) {
        val scope = lifecycleOwner?.lifecycleScope ?: GlobalScope
        scope.launch {
            val response = ApiManager.api.logout()
            if (response?.isFailed() == true) {
                TipsToast.showTips(response.errorMsg)
                return@launch
            }
            LogUtil.e("logout${response?.data}", tag = "smy")
            observer.onChanged(response?.isFailed() == true)
            login(context)
        }
    }

    override fun init(context: Context?) {}
}
  1. 公共資源庫(kù)中創(chuàng)建 LoginServiceProvider,獲取 LoginService,提供使用方法。
object LoginServiceProvider {
    //獲取loginService實(shí)現(xiàn)類
    val loginService = ARouter.getInstance().build(LOGIN_SERVICE_LOGIN).navigation() as? ILoginService

    //是否登錄
    fun isLogin(): Boolean {
        return loginService.isLogin()
    }

    //跳轉(zhuǎn)登錄
    fun login(context: Context) {
        loginService.login(context)
    }

    //登出
    fun logout(
        context: Context,
        lifecycleOwner: LifecycleOwner?,
        observer: Observer<Boolean>
    ) {
        loginService.logout(context, lifecycleOwner, observer)
    }
}

那么其他模塊就可以通過(guò) LoginServiceProvider 使用 iLoginService 對(duì)外提供暴露的能力。雖然看起來(lái)這么做會(huì)顯得更復(fù)雜,單一工程可能更加適合我們,每個(gè)類都能直接訪問(wèn),每個(gè)方法都能直接調(diào)用,但是我們不能局限于單人開(kāi)發(fā)的環(huán)境,在實(shí)際場(chǎng)景上多人協(xié)作是常態(tài),模塊化開(kāi)發(fā)是主流。

(4) Module單獨(dú)運(yùn)行

使得模塊可以在集成和獨(dú)立調(diào)試之間切換特性。在打包時(shí)是 library,在調(diào)試是 application。

  1. config.gradle 文件中加入 isModule 參數(shù):
//是否單獨(dú)運(yùn)行某個(gè)module
isModule = false
  1. 在每個(gè) Modulebuild.gradle 中加入 isModule 的判斷,以區(qū)分是 application 還是 library:
// 組件模式和基礎(chǔ)模式切換
def root = rootProject.ext
if (root.isModule) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    sourceSets {
        main {
            if (rootProject.ext.isModule) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //library模式下排除debug文件夾中的所有Java文件
                java {
                    exclude 'debug/**'
                }
            }
        }
    }
}
  1. 將通過(guò)修改 SourceSets 中的屬性,可以指定需要被編譯的源文件,如果是library,則編譯 manifest 下 AndroidManifest.xml,反之則直接編譯 debug 目錄下 AndroidManifest.xml,同時(shí)加入 Applicationintent-filter 等參數(shù)。

存疑一

至于模塊單獨(dú)編譯單獨(dú)運(yùn)行,這種是一個(gè)偽需求,實(shí)際上必然存在多個(gè)模塊間通信的場(chǎng)景。不然跨模塊的服務(wù)提取和獲取,初始化任務(wù),模塊間的聯(lián)合測(cè)試該怎么解決呢?一個(gè)模塊運(yùn)行后需要和其他的模塊通信,比如對(duì)外提供服務(wù),獲取服務(wù),與之相關(guān)聯(lián)的模塊如果沒(méi)有運(yùn)行起來(lái)的話是無(wú)法使用的。

與此同時(shí)還需要在 suorceSets 下維護(hù)兩套 AndoidManifest 以及 Javasource 目錄,這個(gè)不僅麻煩而且每次更改都需要同步一段時(shí)間。所以這種流傳的模塊化獨(dú)立編譯的形式,是否真的適合就仁者見(jiàn)仁了。

三、寫在最后

如需要更詳細(xì)的代碼可以到項(xiàng)目源碼中查看,地址在下面給出。由于時(shí)間倉(cāng)促,項(xiàng)目中有部分功能尚未完善,或者部分實(shí)現(xiàn)方式有待優(yōu)化,也有更多的Jetpack組件尚未在項(xiàng)目中實(shí)踐,比如 依賴注入Hilt相機(jī)功能CameraX,權(quán)限處理Permissions, 分頁(yè)處理Paging等等。項(xiàng)目的持續(xù)迭代更新依然是一項(xiàng)艱苦持久戰(zhàn)。

除去可以學(xué)到 Kotlin + MVVM + Android Jetpack + 協(xié)程 + Flow + 組件化 + 模塊化 + 短視頻 的知識(shí),相信你還可以在我的項(xiàng)目中學(xué)到:

  1. 如何使用 Charles 抓包。
  2. 提供大量擴(kuò)展函數(shù),快速開(kāi)發(fā),提高效率。
  3. ChipGroupFlexboxLayoutManager 等多種原生方式實(shí)現(xiàn)流式布局。
  4. 符合阿里巴巴 Java 開(kāi)發(fā)規(guī)范和阿里巴巴 Android 開(kāi)發(fā)規(guī)范,并有良好的注釋。
  5. CoordinatorLayoutToolbar 實(shí)現(xiàn)首頁(yè)欄目吸頂效果和輪播圖電影效果。
  6. 利用 ViewOutlineProvider 給控件添加圓角,大大減少手寫 shape 圓角 xml。
  7. ConstraintLayout 的使用,幾乎每個(gè)界面布局都采用的 ConstraintLayout。
  8. 異步任務(wù)啟動(dòng)器,優(yōu)雅地處理 Application 中同步初始化任務(wù)問(wèn)題,有效減少 APP啟動(dòng)耗時(shí)。
  9. 無(wú)論是模塊化或者組件化,它們本質(zhì)思想都是一樣的,都是化整為零,化繁為簡(jiǎn),兩者的目的都是為了重用和解耦,只是叫法不一樣。

項(xiàng)目地址:ST_Wan_Android

點(diǎn)關(guān)注,不迷路

好了各位,以上就是這篇文章的全部?jī)?nèi)容了,很感謝您閱讀這篇文章。我是suming,感謝支持和認(rèn)可,您的點(diǎn)贊就是我創(chuàng)作的最大動(dòng)力。山水有相逢,我們下篇文章見(jiàn)!

本人水平有限,文章難免會(huì)有錯(cuò)誤,請(qǐng)批評(píng)指正,不勝感激 !

感謝

API: 鴻洋提供的 WanAndroid API

主要使用的開(kāi)源框架:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-697455.html

  • Retrofit
  • OkHttp
  • Glide
  • ARouter
  • MMKV
  • RxPermission
  • SmartRefreshLayout

希望我們能成為朋友,在 Github、博客 上一起分享知識(shí),一起共勉!Keep Moving!

到了這里,關(guān)于大型Android項(xiàng)目架構(gòu):基于組件化+模塊化+Kotlin+協(xié)程+Flow+Retrofit+Jetpack+MVVM架構(gòu)實(shí)現(xiàn)WanAndroid客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Android 組件化○

    Android 組件化○

    1.組件化 組件化是指解耦復(fù)雜系統(tǒng)時(shí),將多個(gè)功能模板拆分、重組的過(guò)程。在Android工程表現(xiàn)上就是把a(bǔ)pp按照其業(yè)務(wù)的不同,劃分為不同的Module。 組件化架構(gòu)的目的就是讓每個(gè)業(yè)務(wù)模塊變得相對(duì)獨(dú)立,各個(gè)組件在組件模式下可以獨(dú)立開(kāi)發(fā)調(diào)試,集成模式下又可以集成到“app殼

    2023年04月20日
    瀏覽(20)
  • Android GreenDao 使用全面講解,Android組件化入門

    Android GreenDao 使用全面講解,Android組件化入門

    5. 在多個(gè)線程中使用QueryBuilder 如果在多個(gè)線程中使用查詢,則必須調(diào)用 forCurrentThread ()以獲取當(dāng)前線程的Query實(shí)例。Query的對(duì)象實(shí)例綁定到構(gòu)建查詢的擁有線程。 這使您可以安全地在Query對(duì)象上設(shè)置參數(shù),而其他線程不會(huì)干擾。如果其他線程嘗試在查詢上設(shè)置參數(shù)或執(zhí)行綁

    2024年04月27日
    瀏覽(11)
  • Android AGP8.1.0組件化初探

    Android AGP8.1.0組件化初探

    前面兩篇完成了從AGP4.2到 AGP8.1.0的升級(jí),本文是由于有哥們留言說(shuō)在AGP8.0中使用ARouter組件化有問(wèn)題,于是趁休息時(shí)間嘗試了一下,寫了幾個(gè)demo,發(fā)現(xiàn)都沒(méi)有問(wèn)題,跳轉(zhuǎn)和傳值都是正常的,這里我也是直接從groovy轉(zhuǎn)換成version-catalogs的依賴方式,由于之前升級(jí)過(guò),所以這次很順

    2024年02月10日
    瀏覽(21)
  • Android徹底組件化—UI跳轉(zhuǎn)升級(jí)改造

    Android徹底組件化—UI跳轉(zhuǎn)升級(jí)改造

    (2)host對(duì)應(yīng)的是share。在組件化框架中,每個(gè)組件對(duì)應(yīng)一個(gè)唯一的host,例如分享組件的host就是share,讀書組件的host是reader等等。 host是路由分發(fā)的第一級(jí),根據(jù)host可以定位到每個(gè)組件。 host還可以對(duì)所有的路由URL進(jìn)行一個(gè)分組,只有調(diào)用到該分組的路由的時(shí)候,組內(nèi)的路由

    2024年04月16日
    瀏覽(21)
  • Android學(xué)習(xí)之路(22) 從模塊化到組件化

    Android學(xué)習(xí)之路(22) 從模塊化到組件化

    Android 應(yīng)用項(xiàng)目 , 都存在一個(gè)應(yīng)用模塊 ( Application Module ) , 在 build.gradle 構(gòu)建腳本中 , 第一個(gè)插件配置 com.android.application , 表明 該 Module 編譯打包后的輸出是 APK 安裝包 ; 該項(xiàng)目可以直接運(yùn)行 ; 如果在 build.gradle 配置的是 com.android.library 插件 , 那么 編譯 Module 打包后輸出的是 a

    2024年01月22日
    瀏覽(23)
  • Android學(xué)習(xí)之路(23)組件化框架ARouter的使用

    Android學(xué)習(xí)之路(23)組件化框架ARouter的使用

    支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn),并自動(dòng)注入?yún)?shù)到目標(biāo)頁(yè)面中 支持多模塊工程使用 支持添加多個(gè)攔截器,自定義攔截順序 支持依賴注入,可單獨(dú)作為依賴注入框架使用 支持InstantRun 支持MultiDex (Google方案) 映射關(guān)系按組分類、多級(jí)管理,按需初始化 支持用戶指定全局降級(jí)與局

    2024年01月22日
    瀏覽(25)
  • 現(xiàn)代化 Android 開(kāi)發(fā):組件化與模塊化的抉擇

    作者:古哥E下 項(xiàng)目初始的時(shí)候,一般都是使用一個(gè)分層架構(gòu),接入各種框架,然后就寫業(yè)務(wù)代碼。但如果項(xiàng)目慢慢變大,那就會(huì)出現(xiàn)很多項(xiàng)目管理的問(wèn)題,諸如: 1.代碼復(fù)用與抽象問(wèn)題 2.編譯速度問(wèn)題 3.版本迭代速度問(wèn)題 所以組件化、模塊化、動(dòng)態(tài)化、插件化、跨平臺(tái)等各

    2024年02月11日
    瀏覽(26)
  • Android技術(shù)棧(二)組件化改造,目前最穩(wěn)定和高效的UI適配方案

    Android技術(shù)棧(二)組件化改造,目前最穩(wěn)定和高效的UI適配方案

    .build(PR.navi.navi) .navigation(); 而 Activity 則不需要,它會(huì)立即顯示 ARouter.getInstance() .build(PR.navi.navi) //還可以設(shè)置參數(shù),ARouter會(huì)幫你存在Bundle中 .withString(“pathId”,UUID.randomUUID().toString()) //Activity 或 Context .navigation(this); navi 模塊是典型的業(yè)務(wù)邏輯模塊,這里你可導(dǎo)入一些只有這個(gè)模塊才

    2024年03月24日
    瀏覽(21)
  • 一篇讀懂 Android 開(kāi)發(fā)中模塊化、組件化、插件化和熱修復(fù)

    一篇讀懂 Android 開(kāi)發(fā)中模塊化、組件化、插件化和熱修復(fù)

    網(wǎng)上關(guān)于 “Android 開(kāi)發(fā)\\\" 的文章很多,我本人學(xué)習(xí) Android 開(kāi)發(fā)的過(guò)程也借鑒了網(wǎng)上先輩們的文章;但大多數(shù)文章都從底層的細(xì)枝末節(jié)開(kāi)始講述,由下而上給人一種這門技術(shù)“博大精深”望而生畏的感覺(jué);而我寫這篇文章的初衷就是由上而下,希望別人在閱讀的過(guò)程中能夠覺(jué)得

    2023年04月08日
    瀏覽(23)
  • WebSecurityConfigurerAdapter被棄用Spring Security基于組件化的配置和使用

    WebSecurityConfigurerAdapter被棄用Spring Security基于組件化的配置和使用

    在Spring Security 5.7及之后的版本中 WebSecurityConfigurerAdapter 將被啟用,安全框架將轉(zhuǎn)向基于組件的安全配置。 spring security官方文檔 Spring Security without the WebSecurityConfigurerAdapter 如果使用的Spring Boot版本高于低于2.7.0、Spring Security版本高于5.7,就會(huì)出現(xiàn)如下的提示: 1、被啟用的原因

    2024年02月02日
    瀏覽(23)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包