Android練手小項(xiàng)目(KTReader)基于mvp架構(gòu)(一)

下路傳送眼:Android練手小項(xiàng)目(KTReader)基于mvp架構(gòu)(二)

前言

這是一個(gè)練手的小項(xiàng)目,權(quán)當(dāng)之前Android學(xué)習(xí)的總結(jié),項(xiàng)目里會(huì)有相同功能的不同實(shí)現(xiàn)
(從基本的代碼到熱門的開源項(xiàng)目實(shí)現(xiàn)础钠,比如網(wǎng)絡(luò)通信就會(huì)分別使用HttpURLConnection和retrofit2+OKhttp3實(shí)現(xiàn))入愧。

如果你在閱讀中發(fā)現(xiàn)了錯(cuò)誤或是需要改進(jìn)之處贬堵,歡迎指正划纽,謝謝。

ps:之前在P層做了model層的事情瞎领,感謝 別問了我去EDG了的指正泌辫,已改正。

GIthub地址: https://github.com/yiuhet/KTReader

啟動(dòng)界面

目錄結(jié)構(gòu):

包結(jié)構(gòu)

創(chuàng)建基類

  • BaseActivity
  • BasePresenter
  • MVPBaseActivity
  • BaseFragment

添加依賴 (之后會(huì)逐步添加)
基類創(chuàng)建會(huì)用到ButterKnife

compile 'com.jakewharton:butterknife:8.6.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0

1. BaseActivity

Activity基類里定義了以下方法(方法的作用如名所示):

  • getLayoutRes()(抽象)
  • showProgress(String msg)
  • hideProgress()
  • startActivity(Class activity)
  • toast(String msg)
public abstract class BaseActivity extends AppCompatActivity {
    private ProgressDialog mProgressDialog;
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutRes());
        //android 5.0 以上設(shè)置直接狀態(tài)欄透明
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            WindowManager.LayoutParams localLayoutParams = getWindow().getAttributes();
            localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
        }
    }
    protected void showProgress(String msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setCancelable(true);
        }
        mProgressDialog.setMessage(msg);
        mProgressDialog.show();
    }
    protected void hideProgress() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
    }
    protected void startActivity(Class activity) {
        startActivity(activity, true);
    }
    protected void startActivity(Class activity, boolean finish) {
        Intent intent = new Intent(this, activity);
        startActivity(intent);
        if (finish) {
            finish();
        }
    }
    protected abstract int getLayoutRes();
    protected void toast(String msg) {
        CommonUtils.ShowTips(this, msg); //方法類里的一個(gè)方法九默,封裝了Toast,后文會(huì)提到.
    }
}

2. BasePresenter

BasePresenter有四個(gè)方法:

  • attachView(T view) —— 建立關(guān)聯(lián)
  • getView() —— 獲取View
  • isViewAttached() —— 判斷是否與View建立了關(guān)聯(lián)
  • detachView() —— 解除關(guān)聯(lián)

Presenter對(duì)通過泛型傳進(jìn)來的VIew持有弱引用震放,防止內(nèi)存泄漏。

public abstract class BasePresenter<T> {

    protected Reference<T> mViewRef; //View 接口類型的弱引用

    public void attachView(T view) {
        mViewRef = new WeakReference<T>(view);  //建立關(guān)聯(lián)
    }

    protected T getView() {
        return mViewRef.get();
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }
}

3. MVPBaseActivity

MVPBaseActivity繼承BaseActivity驼修,含有兩個(gè)泛型參數(shù):

  • 第一個(gè)是View接口類型 V
  • 第二個(gè)是Presenter的具體類型 T
public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends BaseActivity {
    protected T mPresenter;
    
    protected abstract T createPresenter();
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
        mPresenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }
}

4. BaseFragment

泛型參數(shù)與MVPBaseActivity一樣

  • 第一個(gè)是View接口類型 V
  • 第二個(gè)是Presenter的具體類型殿遂。T
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {

    protected T mPresenter;
    private ProgressDialog mProgressDialog;
    
    protected abstract T createPresenter();
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(getLayoutRes(), null);
        ButterKnife.bind(this,root);
        return root;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
        mPresenter.attachView((V)this);
    }

    protected abstract int getLayoutRes();

    protected void setTitle(String title) {
        getActivity().getActionBar().setTitle(title);
    }

    protected void showProgress(String msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(getContext());
            mProgressDialog.setCancelable(true);
        }
        mProgressDialog.setMessage(msg);
        mProgressDialog.show();
    }

    protected void hideProgress() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
    }

    protected void toast(String msg) {
        Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
    }

    protected void startActivity(Class activity) {
        startActivity(activity, true);
    }

    protected void startActivity(Class activity, boolean finish) {
        Intent intent = new Intent(getContext(), activity);
        startActivity(intent);
        if (finish) {
            getActivity().finish();
        }
    }

    protected void startActivity(Class activity, String key, String extra) {
        Intent intent = new Intent(getContext(),activity);
        intent.putExtra(key, extra);
        startActivity(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
        mProgressDialog = null;
    }
}

創(chuàng)建一個(gè)漂亮的啟動(dòng)界面(Splash Screen)

這個(gè)項(xiàng)目的啟動(dòng)界面有以下特點(diǎn):

  • 啟動(dòng)App時(shí)不會(huì)出現(xiàn)白屏。
  • 背景圖有放大的動(dòng)畫效果乙各。
  • 從網(wǎng)上拉取一句勵(lì)志語錄并加載出來墨礁。

1. 不會(huì)出現(xiàn)白屏

白屏出現(xiàn)的原因:

Android是先渲染window再渲染activity,而業(yè)務(wù)邏輯(比如初始化用戶信息等)是在activity里耳峦,這就會(huì)導(dǎo)致渲染出activity的布局變慢,如果不做任何操作恩静,這時(shí)候在activity的頁面渲染出來前就會(huì)有個(gè)黑色或者白色的狀態(tài)。

所以嚴(yán)格來說妇萄,我并不是消除了白屏蜕企,而是設(shè)置window的background替換了那個(gè)階段的顯示,實(shí)際上沒有加速冠句,但從用戶角度確實(shí)提高了感知轻掩。

下面附上代碼:

styles.xml:

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
    <style name="SplashTheme" parent="AppTheme.NoActionBar">
        <item name="android:windowBackground">@drawable/splash_layers</item>
    </style>

AndroidManifest.xml:

<activity android:name=".ui.activity.SplashActivity"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

splash_layers.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <bitmap android:gravity="fill" android:src="@drawable/bg_splash_default" />
    </item>
    <item>
        <shape>
            <gradient
                android:angle="90"
                android:startColor="@android:color/black"
                android:endColor="@android:color/transparent"
                />
        </shape>
    </item>
</layer-list>

activity_splash.xml:

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

    <ImageView
        android:id="@+id/iv_show_pic"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/splash_layers"
        android:scaleType="fitXY"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="100dp"
        android:gravity="center"
        android:text="KTReader"
        android:textColor="@android:color/white"
        android:textSize="23sp"/>

    <TextView
        android:id="@+id/tv_show_saying"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="60dp"
        android:layout_centerInParent="true"
        android:textColor="@android:color/white" />
</RelativeLayout>

2. 放大的動(dòng)畫效果

SplashActivity里定義一個(gè)startAnim(Class act)方法:

private void startAnim(final Class act) {
        //傳入一個(gè)ImageView對(duì)象,圍繞X,Y進(jìn)行2D縮放,由原始的大小方法到原來的1.15倍
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(mIvShowPic, "scaleX", 1f, 1.15f);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(mIvShowPic, "scaleY", 1f, 1.15f);
        //多個(gè)動(dòng)畫的協(xié)同工作
        AnimatorSet set = new AnimatorSet();
        set.setDuration(2000).play(animatorX).with(animatorY);
        set.start();
        //對(duì)動(dòng)畫的監(jiān)聽,動(dòng)畫結(jié)束后立馬跳轉(zhuǎn)到主頁面上
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                startActivity(act); //基類里的方法
            }
        });
    }

3. 從網(wǎng)上拉取一句勵(lì)志語錄并加載出來:

啟動(dòng)界面里的數(shù)據(jù)通信是用基本的HttpURLConnection類實(shí)現(xiàn)的,數(shù)據(jù)解析也是用JSONObject類實(shí)現(xiàn)的懦底。

勵(lì)志語錄是易源數(shù)據(jù)由提供的,返回?cái)?shù)據(jù)格式如下:

{
    "showapi_res_error": "",
    "showapi_res_code": 0,
    "showapi_res_body": {
        "ret_code": 0,
        "ret_message": "Success",
        "data": [
            {
                "english": "Let the right one in. Let the old dreams die. Let the wrong ones go.",
                "chinese": "讓適合的人走進(jìn)你的生活吧唇牧,讓舊夢(mèng)逝去吧,讓不合適的那個(gè)離開吧聚唐。"
            }
        ]
    }
}

Model層 :

由于歡迎界面的數(shù)據(jù)只需要字符串丐重,所以不需要實(shí)體類。

先寫一個(gè)接口

  • 易源api勵(lì)志語句Model接口
public interface SplashModel {
    void loadSaying(OnSplashListener listener);
}

然后寫Model層的實(shí)現(xiàn)類

  • 獲取易源api勵(lì)志語句的Model實(shí)現(xiàn)
public class SplashModelImp1 implements SplashModel {
    /*獲取易源api勵(lì)志語句的Model實(shí)現(xiàn)*/

    private OnSplashListener listener; //回調(diào)接口
    @Override
    public void loadSaying(OnSplashListener listener) {
        this.listener = listener;
        new ShowAsyncTask().execute(ShowApiUtils.SAYING);
    }

    //使用基本的AsyncTask處理網(wǎng)絡(luò)請(qǐng)求
    class ShowAsyncTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... params) {
            return ShowApiUtils.parseJsonFromSaying(ShowApiUtils.getData(params[0]));
        }
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            if (s != null) {
                listener.onSuccess(s); //網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)不為空時(shí) 回調(diào)成功方法杆查。
            } else {
                listener.onError(); //回調(diào)失敗方法扮惦。
            }
        }
    }
}
  • 加載數(shù)據(jù)的方法類 —— ShowApiUtils:
public class ShowApiUtils {

    private final static String SHOWAPI_APPID = "38473";
    private final static String SHOWAPI_SECRT= "cb7ffb4054924ba2b2933a6834069bb1";
    public final static String BING_PIC = "1377-1";
    public final static String SAYING = "1211-1";  //勵(lì)志語錄的api
    // 解析url網(wǎng)址
    public static String getApiRequest(String address) {
        String url = Uri.parse("http://route.showapi.com/" + address)
                    .buildUpon()
                    .appendQueryParameter("showapi_appid", SHOWAPI_APPID)
                    .appendQueryParameter("showapi_sign", SHOWAPI_SECRT)
                    .build().toString();
        return url;
    }
    //獲取數(shù)據(jù),使用HttpURLConnection實(shí)現(xiàn)
    public static String getData(String httpUrl) {
        String jsonResult ;
        BufferedReader reader = null;
        StringBuffer sbf = new StringBuffer();
        try {
            URL url = new URL(getApiRequest(httpUrl));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();
            InputStream is = connection.getInputStream();
            reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String strRead = null;
            while ((strRead = reader.readLine()) != null) {
                sbf.append(strRead);
                sbf.append("\r\n");
            }
            reader.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        jsonResult =sbf.toString();
        return jsonResult;
    }
    //從返回的Json數(shù)據(jù)解析出結(jié)果
    public static String parseJsonFromSaying(String jsonResult) {
        String resultEnglish = null;
        String resultChinese = null;
        try {
            JSONObject jsonBody = new JSONObject(jsonResult);
            JSONObject resBody = jsonBody.getJSONObject("showapi_res_body");
            JSONArray resDataArray = resBody.getJSONArray("data");
            JSONObject result = resDataArray.getJSONObject(0);
            resultEnglish = result.getString("english");
            resultChinese = result.getString("chinese");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return resultChinese;
    }
}

__View層 __

  • SplashView接口
public interface SplashView {

    void onGetSayingSuccess(String string);

    void onGetSayingFailed();
}
  • SplashActivity:
public class SplashActivity extends MVPBaseActivity<SplashView, SplashPresenterImp1> implements SplashView {

    @BindView(R.id.iv_show_pic)
    ImageView mIvShowPic;
    @BindView(R.id.tv_show_saying)
    TextView mTvShowSaying;

    @Override
    protected int getLayoutRes() {
        return R.layout.activity_splash;
    }

    @Override
    protected SplashPresenterImp1 createPresenter() {
        return new SplashPresenterImp1(this);
    }
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ButterKnife.bind(this);
        //保持全屏窗口
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        mPresenter.loadSaying();
        startAnim(MainActivity.class);
    }

    private void startAnim(final Class act) {
        //傳入一個(gè)ImageView對(duì)象,圍繞X,Y進(jìn)行2D縮放,由原始的大小方法到原來的1.15倍
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(mIvShowPic, "scaleX", 1f, 1.15f);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(mIvShowPic, "scaleY", 1f, 1.15f);
        //多個(gè)動(dòng)畫的協(xié)同工作
        AnimatorSet set = new AnimatorSet();
        set.setDuration(2000).play(animatorX).with(animatorY);
        set.start();
        //對(duì)動(dòng)畫的監(jiān)聽,動(dòng)畫結(jié)束后立馬跳轉(zhuǎn)到主頁面上
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                startActivity(act);
            }
        });
    }
    
    @Override
    public void onGetSayingSuccess(String string) {
        mTvShowSaying.setText(string);
    }

    @Override
    public void onGetSayingFailed() {
        mTvShowSaying.setText(getString(R.string.default_saying));
    }
}

Presenter層

先寫一個(gè)回調(diào)接口(在Presenter層實(shí)現(xiàn)亲桦,給Model層回調(diào)崖蜜,更改View層的狀態(tài),確保Model層不直接操作View層)

  • OnSplashListener
public interface OnSplashListener {
    /**
     * 成功時(shí)回調(diào)
     * @param saying
     */
    void onSuccess(String saying);
    /**
     * 失敗時(shí)回調(diào)
     */
    void onError();
}

再寫一個(gè)presenter接口:

  • SplashPresenter
public interface SplashPresenter {
    void loadSaying();
}

最后寫Prestener實(shí)現(xiàn)類

  • SplashPresenterImp1:
public class SplashPresenterImp1 extends BasePresenter<SplashView> implements SplashPresenter,OnSplashListener{
    /*Presenter作為中間層客峭,持有View和Model的引用*/
    private SplashView mSplashView;
    private SplashModelImp1 splashModelImp1;

    public SplashPresenterImp1(SplashView splashView) {
        mSplashView = splashView;
        splashModelImp1 = new SplashModelImp1();
    }

    @Override
    public void loadSaying() {
        splashModelImp1.loadSaying(this);
    }

    @Override
    public void onSuccess(String saying) {
        mSplashView.onGetSayingSuccess(saying);
    }

    @Override
    public void onError() {
        mSplashView.onGetSayingFailed();
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末豫领,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子舔琅,更是在濱河造成了極大的恐慌等恐,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件备蚓,死亡現(xiàn)場(chǎng)離奇詭異课蔬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)郊尝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門购笆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虚循,你說我怎么就攤上這事同欠。” “怎么了横缔?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵铺遂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我茎刚,道長(zhǎng)襟锐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任膛锭,我火速辦了婚禮粮坞,結(jié)果婚禮上蚊荣,老公的妹妹穿的比我還像新娘。我一直安慰自己莫杈,他們只是感情好互例,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筝闹,像睡著了一般媳叨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上关顷,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天糊秆,我揣著相機(jī)與錄音,去河邊找鬼议双。 笑死痘番,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的平痰。 我是一名探鬼主播夫偶,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼觉增!你這毒婦竟也來了兵拢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤逾礁,失蹤者是張志新(化名)和其女友劉穎说铃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘹履,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腻扇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砾嫉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幼苛。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖焕刮,靈堂內(nèi)的尸體忽然破棺而出舶沿,到底是詐尸還是另有隱情,我是刑警寧澤配并,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布括荡,位于F島的核電站,受9級(jí)特大地震影響溉旋,放射性物質(zhì)發(fā)生泄漏畸冲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邑闲。 院中可真熱鬧算行,春花似錦、人聲如沸苫耸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲸阔。三九已至偷霉,卻和暖如春迄委,著一層夾襖步出監(jiān)牢的瞬間褐筛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工叙身, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渔扎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓信轿,卻偏偏與公主長(zhǎng)得像晃痴,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子财忽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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