Android組件化

組件化實施流程

1組件模式和集成模式的轉(zhuǎn)換

Android Studio中的Module主要有兩種屬性丰介,分別為:

application屬性,可以獨立運行的Android程序全谤,也就是我們的APP走触;

apply plugin: ‘com.android.application’

library屬性艺智,不可以獨立運行饿自,一般是Android程序依賴的庫文件;

apply plugin: ‘com.android.library’

如何讓組件在這兩種模式之間自動轉(zhuǎn)換?

解決辦法

第一步 在gradle.properties中定義一個常量值 isModule(是否是組件開發(fā)模式锈拨,true為是砌庄,false為否)
# 每次更改“isModule”的值后,需要點擊 "Sync Project" 按鈕
isModule=false
第二步 在業(yè)務組件的build.gradle中讀取 isModule

但是 gradle.properties 還有一個重要屬性: gradle.properties 中的數(shù)據(jù)類型都是String類型奕枢,使用其他數(shù)據(jù)類型需要自行轉(zhuǎn)換娄昆;也就是說我們讀到 isModule 是個String類型的值,而我們需要的是Boolean值缝彬,代碼如下:

if (isModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

2 組件之間AndroidManifest合并問題

在 AndroidStudio 中每一個組件都會有對應的 AndroidManifest.xml稿黄,用于聲明需要的權(quán)限、Application跌造、Activity、Service族购、Broadcast等壳贪,當項目處于組件模式時,業(yè)務組件的 AndroidManifest.xml 應該具有一個 Android APP 所具有的的所有屬性寝杖,尤其是聲明 Application 和要 launch的Activity违施,但是當項目處于集成模式的時候,每一個業(yè)務組件的 AndroidManifest.xml 都要合并到“app殼工程”中瑟幕,要是每一個業(yè)務組件都有自己的 Application 和 launch的Activity磕蒲,那么合并的時候肯定會沖突,試想一個APP怎么可能會有多個 Application 和 launch 的Activity呢只盹?

解決辦法
為組件開發(fā)模式下的業(yè)務組件再創(chuàng)建一個 AndroidManifest.xml辣往,然后根據(jù)isModule指定AndroidManifest.xml的文件路徑,讓業(yè)務組件在集成模式和組件模式下使用不同的AndroidManifest.xml殖卑,這樣表單沖突的問題就可以規(guī)避了站削。

業(yè)務組件的目錄結(jié)構(gòu)

上圖是組件化項目中一個標準的業(yè)務組件目錄結(jié)構(gòu),首先我們在main文件夾下創(chuàng)建一個module文件夾用于存放組件開發(fā)模式下業(yè)務組件的 AndroidManifest.xml孵稽,而 AndroidStudio 生成的 AndroidManifest.xml 則依然保留许起,并用于集成開發(fā)模式下業(yè)務組件的表單;然后我們需要在業(yè)務組件的 build.gradle 中指定表單的路徑菩鲜,代碼如下:

  sourceSets {
        main {
            if (isModule.toBoolean()) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

這樣在不同的開發(fā)模式下就會讀取到不同的 AndroidManifest.xml 园细,然后我們需要修改這兩個表單的內(nèi)容以為我們不同的開發(fā)模式服務。

首先是集成開發(fā)模式下的 AndroidManifest.xml接校,前面我們說過集成模式下猛频,業(yè)務組件的表單是絕對不能擁有自己的 Application 和 launch 的 Activity的,也不能聲明APP名稱、圖標等屬性伦乔,總之a(chǎn)pp殼工程有的屬性厉亏,業(yè)務組件都不能有,下面是一份標準的集成開發(fā)模式下業(yè)務組件的 AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guiying.girls">

    <application android:theme="@style/AppTheme">
        <activity
            android:name=".main.GirlsActivity"
            android:screenOrientation="portrait" />
        <activity
            android:name=".girl.GirlActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar" />
    </application>

</manifest>

我在這個表單中只聲明了應用的主題烈和,而且這個主題還是跟app殼工程中的主題是一致的爱只,都引用了common組件中的資源文件,在這里聲明主題是為了方便這個業(yè)務組件中有使用默認主題的Activity時就不用再給Activity單獨聲明theme了招刹。

然后是組件開發(fā)模式下的表單文件:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.guiying.girls">

    <application
        android:name="debug.GirlsApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/girls_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".main.GirlsActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".girl.GirlActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar" />
    </application>

</manifest>

組件模式下的業(yè)務組件表單就是一個Android項目普通的AndroidManifest.xml恬试。

3 全局Context的獲取及組件數(shù)據(jù)初始化

需求:任何一個業(yè)務組件中都要能獲取到全局的 Context,而且這個 Context 不管是在組件開發(fā)模式還是在集成開發(fā)模式都是生效的疯暑。
解決辦法:

在 組件化工程模型圖中训柴,功能組件集合中有一個 Common 組件, 這個組件中主要封裝了項目中需要的基礎功能妇拯,并且每一個業(yè)務組件都要依賴Common組件幻馁,在Common組件中我們封裝了項目中用到的各種Base類,這些基類中就有BaseApplication 類越锈。

BaseApplication 主要用于各個業(yè)務組件和app殼工程中聲明的 Application 類繼承用的仗嗦,只要各個業(yè)務組件和app殼工程中聲明的Application類繼承了 BaseApplication,當應用啟動時 BaseApplication 就會被動實例化甘凭,這樣從 BaseApplication 獲取的 Context 就會生效稀拐,也就從根本上解決了我們不能直接從各個組件獲取全局 Context 的問題;

這時候大家肯定都會有個疑問丹弱?不是說了業(yè)務組件不能有自己的 Application 嗎德撬,怎么還讓他們繼承 BaseApplication 呢?其實我前面說的是業(yè)務組件不能在集成模式下?lián)碛凶约旱?Application躲胳,但是這不代表業(yè)務組件也不能在組件開發(fā)模式下?lián)碛凶约旱腁pplication蜓洪,其實業(yè)務組件在組件開發(fā)模式下必須要有自己的 Application 類,一方面是為了讓 BaseApplication 被實例化從而獲取 Context坯苹,還有一個作用是蝠咆,業(yè)務組件自己的 Application 可以在組件開發(fā)模式下初始化一些數(shù)據(jù)

但是北滥,實際上業(yè)務組件中的Application在最終的集成項目中是沒有什么實際作用的刚操,組件自己的 Application 僅限于在組件模式下發(fā)揮功能,因此我們需要在將項目從組件模式轉(zhuǎn)換到集成模式后將組件自己的Application剔除出我們的項目再芋;在 AndroidManifest 合并問題小節(jié)中介紹了如何在不同開發(fā)模式下讓 Gradle 識別組件表單的路徑菊霜,這個方法也同樣適用于Java代碼;

業(yè)務組件的java目錄結(jié)構(gòu)

4 library依賴問題

我們在build.gradle中compile的第三方庫济赎,例如AndroidSupport庫經(jīng)常會被一些開源的控件所依賴鉴逞,而我們自己一定也會compile AndroidSupport庫 记某,這就會造成第三方包和我們自己的包存在重復加載,解決辦法就是找出那個多出來的庫构捡,并將多出來的庫給排除掉液南,而且Gradle也是支持這樣做的,分別有兩種方式:根據(jù)組件名排除或者根據(jù)包名排除勾徽,下面以排除support-v4庫為例:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exclude module: 'support-v4'//根據(jù)組件名排除
        exclude group: 'android.support.v4'//根據(jù)包名排除
    }
}

library重復依賴的問題算是都解決了滑凉,但是我們在開發(fā)項目的時候會依賴很多開源庫,而這些庫每個組件都需要用到喘帚,要是每個組件都去依賴一遍也是很麻煩的畅姊,尤其是給這些庫升級的時候,為了方便我們統(tǒng)一管理第三方庫吹由,我們將給給整個工程提供統(tǒng)一的依賴第三方庫的入口若未,前面介紹的Common庫的作用之一就是統(tǒng)一依賴開源庫,因為其他業(yè)務組件都依賴了Common庫倾鲫,所以這些業(yè)務組件也就間接依賴了Common所依賴的開源庫粗合。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //Android Support
    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:design:$rootProject.supportLibraryVersion"
    compile "com.android.support:percent:$rootProject.supportLibraryVersion"
    //網(wǎng)絡請求相關(guān)
    compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
    compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
    compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
    //穩(wěn)定的
    compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
    compile "com.orhanobut:logger:$rootProject.loggerVersion"
    compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
    compile "com.google.code.gson:gson:$rootProject.gsonVersion"
    compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"

    compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
    compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"

    //router
    compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
}

5 組件之間調(diào)用和通信

解決方案:

ActivityRouter
ActivityRouter支持給Activity定義 URL,這樣就可以通過 URL 跳轉(zhuǎn)到Activity乌昔,并且支持從瀏覽器以及 APP 中跳入我們的Activity舌劳,而且還支持通過 url 調(diào)用方法。

6 組件之間資源名沖突

在項目中約定資源文件命名規(guī)約玫荣,比如強制使每個資源文件的名稱以組件名開始,這個可以根據(jù)實際情況和開發(fā)人員制定規(guī)則大诸。當然了萬能的Gradle構(gòu)建工具也提供了解決方法捅厂,通過在在組件的build.gradle中添加如下的代碼:

    //設置了resourcePrefix值后,所有的資源名必須以指定的字符串做前綴资柔,否則會報錯焙贷。
    //但是resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源贿堰,所有圖片資源仍然需要手動去修改資源名辙芍。
    resourcePrefix "girls_"

但是設置了這個屬性后有個問題,所有的資源名必須以指定的字符串做前綴羹与,否則會報錯故硅,而且resourcePrefix這個值只能限定xml里面的資源,并不能限定圖片資源纵搁,所有圖片資源仍然需要手動去修改資源名吃衅;所以我并不推薦使用這種方法來解決資源名沖突。

組件化項目的工程類型

在組件化工程模型中主要有:app殼工程腾誉、業(yè)務組件和功能組件3種類型徘层,而業(yè)務組件中的Main組件和功能組件中的Common組件比較特殊峻呕,下面將分別介紹。

1app殼工程

app殼工程是從名稱來解釋就是一個空殼工程趣效,沒有任何的業(yè)務代碼瘦癌,也不能有Activity,但它又必須被單獨劃分成一個組件跷敬,而不能融合到其他組件中讯私,是因為它有如下幾點重要功能:

1、app殼工程中聲明了我們Android應用的 Application干花,這個 Application 必須繼承自 Common組件中的 BaseApplication)妄帘,因為只有這樣,在打包應用后才能讓BaseApplication中的Context生效池凄。

2抡驼、app殼工程的 AndroidManifest.xml 是我Android應用的根表單,應用的名稱肿仑、圖標以及是否支持備份等等屬性都是在這份表單中配置的致盟,其他組件中的表單最終在集成開發(fā)模式下都被合并到這份 AndroidManifest.xml 中。

3尤慰、app殼工程的 build.gradle 是比較特殊的馏锡,app殼不管是在集成開發(fā)模式還是組件開發(fā)模式,它的屬性始終都是:com.android.application伟端,因為最終其他的組件都要被app殼工程所依賴杯道,被打包進app殼工程中,這一點從組件化工程模型圖中就能體現(xiàn)出來责蝠,所以app殼工程是不需要單獨調(diào)試單獨開發(fā)的党巾。另外Android應用的打包簽名,以及buildTypes和defaultConfig都需要在這里配置霜医,而它的dependencies則需要根據(jù)isModule的值分別依賴不同的組件齿拂,在組件開發(fā)模式下app殼工程只需要依賴Common組件,或者為了防止報錯也可以根據(jù)實際情況依賴其他功能組件肴敛,而在集成模式下app殼工程必須依賴所有在應用Application中聲明的業(yè)務組件署海,并且不需要再依賴任何功能組件。

2 功能組件和Common組件

功能組件是為了支撐業(yè)務組件的某些功能而獨立劃分出來的組件医男,功能實質(zhì)上跟項目中引入的第三方庫是一樣的砸狞,功能組件的特征如下:

1、功能組件的 AndroidManifest.xml 是一張空表镀梭,這張表中只有功能組件的包名趾代;

2、功能組件不管是在集成開發(fā)模式下還是組件開發(fā)模式下屬性始終是: com.android.library丰辣,所以功能組件是不需要讀取 gradle.properties 中的 isModule 值的撒强;另外功能組件的 build.gradle 也無需設置 buildTypes 禽捆,只需要 dependencies 這個功能組件需要的jar包和開源庫垂券。

Common組件除了有功能組件的普遍屬性外这刷,還具有其他功能

1、Common組件的 AndroidManifest.xml 不是一張空表暂衡,這張表中聲明了我們 Android應用用到的所有使用權(quán)限 uses-permission 和 uses-feature芽隆,放到這里是因為在組件開發(fā)模式下浊服,所有業(yè)務組件就無需在自己的 AndroidManifest.xm 聲明自己要用到的權(quán)限了。

2胚吁、Common組件的 build.gradle 需要統(tǒng)一依賴業(yè)務組件中用到的 第三方依賴庫和jar包牙躺,例如我們用到的ActivityRouter、Okhttp等等腕扶。

3孽拷、Common組件中封裝了Android應用的 Base類和網(wǎng)絡請求工具、圖片加載工具等等半抱,公用的 widget控件也應該放在Common 組件中脓恕;業(yè)務組件中都用到的數(shù)據(jù)也應放于Common組件中,例如保存到 SharedPreferences 和 DataBase 中的登陸數(shù)據(jù)窿侈;

4炼幔、Common組件的資源文件中需要放置項目公用的 Drawable、layout史简、sting乃秀、dimen、color和style 等等圆兵,另外項目中的 Activity 主題必須定義在 Common中跺讯,方便和 BaseActivity 配合保持整個Android應用的界面風格統(tǒng)一。

3 業(yè)務組件和Main組件

業(yè)務組件就是根據(jù)業(yè)務邏輯的不同拆分出來的組件衙傀,業(yè)務組件的特征如下:

1、業(yè)務組件中要有兩張AndroidManifest.xml萨咕,分別對應組件開發(fā)模式和集成開發(fā)模式统抬,這兩張表的區(qū)別請查看 組件之間AndroidManifest合并問題 小節(jié)。

2危队、業(yè)務組件在集成模式下是不能有自己的Application的聪建,但在組件開發(fā)模式下又必須實現(xiàn)自己的Application并且要繼承自Common組件的BaseApplication,并且這個Application不能被業(yè)務組件中的代碼引用茫陆,因為它的功能就是為了使業(yè)務組件從BaseApplication中獲取的全局Context生效金麸,還有初始化數(shù)據(jù)之用。

3簿盅、業(yè)務組件有debug文件夾挥下,這個文件夾在集成模式下會從業(yè)務組件的代碼中排除掉揍魂,所以debug文件夾中的類不能被業(yè)務組件強引用,例如組件模式下的 Application 就是置于這個文件夾中棚瘟,還有組件模式下開發(fā)給目標 Activity 傳遞參數(shù)的用的 launch Activity 也應該置于 debug 文件夾中现斋;

4、業(yè)務組件必須在自己的 Java文件夾中創(chuàng)建業(yè)務組件聲明類偎蘸,以使 app殼工程 中的 應用Application能夠引用庄蹋,實現(xiàn)組件跳轉(zhuǎn),具體請查看 組件之間調(diào)用和通信 小節(jié)迷雪;

5限书、業(yè)務組件必須在自己的 build.gradle 中根據(jù) isModule 值的不同改變自己的屬性,在組件模式下是:com.android.application章咧,而在集成模式下com.android.library倦西;同時還需要在build.gradle配置資源文件,如 指定不同開發(fā)模式下的AndroidManifest.xml文件路徑慧邮,排除debug文件夾等调限;業(yè)務組件還必須在dependencies中依賴Common組件,并且引入ActivityRouter的注解處理器annotationProcessor误澳,以及依賴其他用到的功能組件耻矮。

Main組件除了有業(yè)務組件的普遍屬性外,還有一項重要功能

工程的build.gradle和gradle.properties文件

1 組件化工程的build.gradle文件

在組件化項目中因為每個組件的 build.gradle 都需要配置 compileSdkVersion忆谓、buildToolsVersion和defaultConfig 等的版本號裆装,而且每個組件都需要用到 annotationProcessor,為了能夠使組件化項目中的所有組件的 build.gradle 中的這些配置都能保持統(tǒng)一倡缠,并且也是為了方便修改版本號哨免,我們統(tǒng)一在Android工程根目錄下的build.gradle中定義這些版本號,當然為了方便管理Common組件中的第三方開源庫的版本號昙沦,最好也在這里定義這些開源庫的版本號琢唾,然后在各個組件的build.gradle中引用Android工程根目錄下的build.gradle定義的版本號,組件化工程的 build.gradle 文件代碼如下:

2 組件化工程的gradle.properties文件

在組件化實施流程中我們了解到gradle.properties有兩個屬性對我們非常有用:

1盾饮、在Android項目中的任何一個build.gradle文件中都可以把gradle.properties中的常量讀取出來采桃,不管這個build.gradle是組件的還是整個項目工程的build.gradle;

2丘损、gradle.properties中的數(shù)據(jù)類型都是String類型普办,使用其他數(shù)據(jù)類型需要自行轉(zhuǎn)換;

利用gradle.properties的屬性不僅可以解決集成開發(fā)模式和組件開發(fā)模式的轉(zhuǎn)換徘钥,而且還可以解決在多人協(xié)同開發(fā)Android項目的時候衔蹲,因為開發(fā)團隊成員的Android開發(fā)環(huán)境(開發(fā)環(huán)境指Android SDK和AndroidStudio)不一致而導致頻繁改變線上項目的build.gradle配置。

作者: Android組件化方案
來源:https://www.cnblogs.com/ldq2016/
著作權(quán)歸作者所有呈础,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處舆驶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末橱健,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贞远,更是在濱河造成了極大的恐慌畴博,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓝仲,死亡現(xiàn)場離奇詭異俱病,居然都是意外死亡,警方通過查閱死者的電腦和手機袱结,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門亮隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垢夹,你說我怎么就攤上這事溢吻。” “怎么了果元?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵促王,是天一觀的道長。 經(jīng)常有香客問我而晒,道長蝇狼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任倡怎,我火速辦了婚禮迅耘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘监署。我一直安慰自己颤专,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布钠乏。 她就那樣靜靜地躺著栖秕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晓避。 梳的紋絲不亂的頭發(fā)上簇捍,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音够滑,去河邊找鬼垦写。 笑死吕世,一個胖子當著我的面吹牛彰触,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播命辖,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼况毅,長吁一口氣:“原來是場噩夢啊……” “哼分蓖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尔许,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤么鹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后味廊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒸甜,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年余佛,在試婚紗的時候發(fā)現(xiàn)自己被綠了柠新。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辉巡,死狀恐怖恨憎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郊楣,我是刑警寧澤憔恳,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站净蚤,受9級特大地震影響钥组,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塞栅,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一者铜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧放椰,春花似錦作烟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至如蚜,卻和暖如春压恒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背错邦。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工探赫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撬呢。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓伦吠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子毛仪,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容