使用VideoView自定義一個播放器控制項

来源:http://www.cnblogs.com/yangqiangyu/archive/2016/01/28/5167812.html
-Advertisement-
Play Games

介紹 最近要使用播放器做一個簡單的視頻播放功能,開始學習VideoView,在橫豎屏切換的時候碰到了點麻煩,不過在查閱資料後總算是解決了。在寫VideoView播放視頻時候定義控制的代碼全寫在Actvity里了,寫完一看我靠代碼好亂,於是就寫了個自定義的播放器控制項,支持指定大小,可以橫豎屏切換,手動


介紹

最近要使用播放器做一個簡單的視頻播放功能,開始學習VideoView,在橫豎屏切換的時候碰到了點麻煩,不過在查閱資料後總算是解決了。在寫VideoView播放視頻時候定義控制的代碼全寫在Actvity里了,寫完一看我靠代碼好亂,於是就寫了個自定義的播放器控制項,支持指定大小,可以橫豎屏切換,手動左右滑動快進快退。好了,下麵開始。

效果圖

效果圖有點卡,我也不知道為啥。。。。。
這裡寫圖片描述

VideoView介紹

這個是我們實現視頻播放最主要的控制項,詳細的介紹大家百度就去看,這裡介紹幾個常用的方法。

用於播放視頻文件。 VideoView 類可以從不同的來源(例如資源文件或內容提供器) 讀取圖像,計算和維護視頻的畫面尺寸以使其適用於任何佈局管理器, 並提供一些諸如縮放、著色之類的顯示選項。

VideoView 常用的幾個方法

public int getDuration ()

獲得所播放視頻的總時間

public int getCurrentPosition ()

獲得當前的位置,我們可以用來設置播放時間的顯示

public int getCurrentPosition ()

獲得當前的位置,我們可以用來設置播放時間的顯示

public int pause ()

暫停播放

public int seekTo ()

設置播放位置,我們用來總快進的時候就能用到

public int setOnCompletionListener(MediaPlayer.OnCompletionListener l)

註冊在媒體文件播放完畢時調用的回調函數。

public int setOnErrorListener (MediaPlayer.OnErrorListener l)

註冊在設置或播放過程中發生錯誤時調用的回調函數。如果未指定回調函數, 或回調函數返回false,會彈一個dialog提示用戶不能播放

public void setOnPreparedListener (MediaPlayer.OnPreparedListener l)

註冊在媒體文件載入完畢,可以播放時調用的回調函數。

public void setVideoURI (Uri uri)

設置播放的視頻源,也可以用setVideoPath指定本地文件

public void start ()

開始播放

getHolder().setFixedSize(width,height);

設置VideoView的解析度,如果我們的VideoView在開始播放的時候是豎屏的,當橫屏的時候我們改變了VideoView的佈局大小,就需要這個方法重新設置它的解析度,否則你會發現改變了之後VideoView內部的視頻部分還是原來的大小,這點要註意。

自定義播放器思路

說是自定義,其實無非就是把這些VideoView和用來顯示的其它控制項結合在一起,然後在內部處理它的事件交互,我們要做的就是以下幾步:1、寫好整個空間的佈局。2、在自定義控制項的內部獲取到整個控制項內部的各個小控制項,並且為它們設置一些初始化事件。3、根據你自己的邏輯和想實現的效果在裡面寫自己的事件處理,需要在和外部進行交互就提供方法和介面咯。最後就是使用測試效果了。好了,我們就跟著這裡說的4步去實現吧!

具體實現

1、第一步,寫自己的佈局文件

想要的效果就是在底部放一個狀態欄顯示時間等信息,播放進度,進入全屏,中間放一個快進快退的狀態,佈局代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewBox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="beforeDescendants">
    <com.qiangyu.test.commonvideoview.MyVideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

     //底部狀態欄
    <LinearLayout android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="#CC282828"
        android:padding="3dip"
        android:id="@+id/videoControllerLayout"
        android:gravity="center"
        android:layout_gravity="bottom">
        <LinearLayout android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:id="@+id/videoPauseBtn"
            android:paddingRight="10dip"
            android:paddingLeft="10dp">
            <ImageView android:layout_width="22dp"
                android:layout_height="22dp"
                android:id="@+id/videoPauseImg" />
        </LinearLayout>
        <LinearLayout android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:paddingRight="0dip">

            <SeekBar android:layout_width="fill_parent"
                android:id="@+id/videoSeekBar"
                android:layout_weight="1"
                style="@android:style/Widget.Holo.SeekBar"
                android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:gravity="center"
                android:text="00:00"
                android:textSize="12dp"
                android:id="@+id/videoCurTime"
                android:textColor="#FFF"
                />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center"
                android:textSize="12dp"
                android:textColor="#FFF"
                android:text="/"/>
            <TextView android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:gravity="center"
                android:text="00:00"
                android:textSize="12dp"
                android:id="@+id/videoTotalTime"
                android:textColor="#FFF"
                android:layout_marginRight="10dp"
                />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/screen_status_btn"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center">
            <ImageView
                android:id="@+id/screen_status_img"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/iconfont_enter_32"/>
        </LinearLayout>
    </LinearLayout>

    //VideoVIEW中間的開始按鈕和進度條以及快進快退的提示
    <ProgressBar android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/progressBar"
        style="@android:style/Widget.Holo.ProgressBar.Small"/>
    <ImageView android:layout_width="30dip"
        android:layout_height="30dip"
        android:id="@+id/videoPlayImg"
        android:layout_gravity="center"
        android:src="@mipmap/video_box_play"/>
    <LinearLayout
        android:id="@+id/touch_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center"
        android:visibility="invisible"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="#000">
        <ImageView android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:id="@+id/touchStatusImg"/>
        <TextView
            android:id="@+id/touch_time"
            android:layout_width="wrap_content"
            android:text="25:00/59:00"
            android:textSize="12sp"
            android:textColor="#fff"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</FrameLayout>

上面的佈局很簡單,VideoView用了自定義是因為當佈局改變的時候,要讓VideoView重新獲取佈局位置,在裡面設置它的解析度為全屏.VideoView的代碼如下

public class MyVideoView extends VideoView {

    public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public MyVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyVideoView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = getDefaultSize(0, widthMeasureSpec);
        int height = getDefaultSize(0, heightMeasureSpec);
        this.getHolder().setFixedSize(width,height);//設置解析度
        setMeasuredDimension(width, height);
    }

}

好,佈局寫好了我來第二步,獲取內部控制項初始化事件

2、第二步,onFinishInflate()中得到內部的控制項,做初始化工作

onFinishInflate方法在xml解析完畢的時候會回調該方法,一般在做組合控制項的時候最常用

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        initView();
    }

private void initView() {
        View view = LayoutInflater.from(context).inflate(R.layout.common_video_view,null);
        viewBox = (FrameLayout) view.findViewById(R.id.viewBox);
        videoView = (MyVideoView) view.findViewById(R.id.videoView);
        videoPauseBtn = (LinearLayout) view.findViewById(R.id.videoPauseBtn);
        screenSwitchBtn = (LinearLayout) view.findViewById(R.id.screen_status_btn);
        videoControllerLayout = (LinearLayout) view.findViewById(R.id.videoControllerLayout);
        touchStatusView = (LinearLayout) view.findViewById(R.id.touch_view);
        touchStatusImg = (ImageView) view.findViewById(R.id.touchStatusImg);
        touchStatusTime = (TextView) view.findViewById(R.id.touch_time);
        videoCurTimeText = (TextView) view.findViewById(R.id.videoCurTime);
        videoTotalTimeText = (TextView) view.findViewById(R.id.videoTotalTime);
        videoSeekBar = (SeekBar) view.findViewById(R.id.videoSeekBar);
        videoPlayImg = (ImageView) view.findViewById(R.id.videoPlayImg);
        videoPlayImg.setVisibility(GONE);
        videoPauseImg = (ImageView) view.findViewById(R.id.videoPauseImg);
        progressBar = (ProgressBar) view.findViewById(R.id.progressBar);

        videoPauseBtn.setOnClickListener(this);
        videoSeekBar.setOnSeekBarChangeListener(this);
        videoPauseBtn.setOnClickListener(this);
        videoView.setOnPreparedListener(this);
        videoView.setOnCompletionListener(this);
        screenSwitchBtn.setOnClickListener(this);
        videoPlayImg.setOnClickListener(this);
        //註冊在設置或播放過程中發生錯誤時調用的回調函數。如果未指定回調函數,或回調函數返回false,VideoView 會通知用戶發生了錯誤。
        videoView.setOnErrorListener(this);
        viewBox.setOnTouchListener(this);
        viewBox.setOnClickListener(this);
        addView(view);
    }

很簡單的做了代碼初始化和videoView的播放事件的註冊,這裡的事件待會要處理的有viewBox.setOnTouchListener(this)註冊的onTouch事件,我們要在裡面寫左右滑動快進快退的效果,videoSeekBar.setOnSeekBarChangeListener(this);處理拖動seekbar快進快退。

3、第三步,事件、效果處理

viewBox.setOnTouchListener(this);

1、onTouch里的實現快進快退

@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //沒播放的時候不處理
                if (!videoView.isPlaying()){
                    return false;
                }
                float downX =  event.getRawX();
                touchLastX = downX;
                Log.d("FilmDetailActivity", "downX" + downX);
                //保存當前播放的位置用與做事件顯示
                this.position = videoView.getCurrentPosition();
                break;
            case MotionEvent.ACTION_MOVE:
                //沒播放的時候不處理
                if (!videoView.isPlaying()){
                    return false;
                }
                float currentX =  event.getRawX();
                float deltaX = currentX - touchLastX;
                float deltaXAbs  =  Math.abs(deltaX);
                if (deltaXAbs>1){//正向移動,快進
                    if (touchStatusView.getVisibility()!=View.VISIBLE){
                        touchStatusView.setVisibility(View.VISIBLE);
                        //顯示快進的時間view
                    }
                    touchLastX = currentX;
                    Log.d("FilmDetailActivity","deltaX"+deltaX);
                    if (deltaX > 1) {
                        position += touchStep;
                        if (position > duration) {
                            position = duration;
                        }
                        touchPosition = position;
                        touchStatusImg.setImageResource(R.mipmap.ic_fast_forward_white_24dp);
                        int[] time = getMinuteAndSecond(position);
                        touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
                    } else if (deltaX < -1) {//快退
                        position -= touchStep;
                        if (position < 0) {
                            position = 0;
                        }
                        touchPosition = position;
                        touchStatusImg.setImageResource(R.mipmap.ic_fast_rewind_white_24dp);
                        int[] time = getMinuteAndSecond(position);
                        touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
                        //mVideoView.seekTo(position);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (touchPosition!=-1){
                    videoView.seekTo(touchPosition);
//放開手指的時候快進或快退到滑動決定的時間位置                    touchStatusView.setVisibility(View.GONE);
                    touchPosition = -1;
                    if (videoControllerShow){
                        return true;
                    }
                }
                break;
        }
        return false;
    }

2、處理 seekBar的拖動事件

@Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        int[] time = getMinuteAndSecond(progress);
        videoCurTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
        //設置底部時間顯示
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        videoView.pause();
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        videoView.seekTo(videoSeekBar.getProgress());
        videoView.start();
        videoPlayImg.setVisibility(View.INVISIBLE);
        videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
       //拖動之後到指定的時間位置
    }

3、videoView的回調事件

@Override
    public void onPrepared(MediaPlayer mp) {
        duration = videoView.getDuration();
        int[] time = getMinuteAndSecond(duration);
        videoTotalTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
        formatTotalTime = String.format("%02d:%02d", time[0], time[1]);
        videoSeekBar.setMax(duration);
        progressBar.setVisibility(View.GONE);


        mp.start();
        videoPauseBtn.setEnabled(true);
        videoSeekBar.setEnabled(true);

        videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
        timer.schedule(timerTask, 0, 1000);
            //初始化總時間等一些界面顯示,同時用timer定時去修改時間進度textView
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        videoView.seekTo(0);
        videoSeekBar.setProgress(0);
        videoPauseImg.setImageResource(R.mipmap.icon_video_play);
        videoPlayImg.setVisibility(View.VISIBLE);
    }

還有一些其它的點擊時間就不放了,都是暫停,播放,點擊顯示隱藏底部狀態欄,全屏切換等的事件。到了這裡我們的事件處理完畢啦,接下來就要我們視頻怎麼播放呢?為了播放我們為外部提供一個方法

//開始播放
public void start(String url){
        videoPauseBtn.setEnabled(false);
        videoSeekBar.setEnabled(false);
        videoView.setVideoURI(Uri.parse(url));
        videoView.start();
    }


//進入全屏時候調用
public void setFullScreen(){
        touchStatusImg.setImageResource(R.mipmap.iconfont_exit);
        this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        videoView.requestLayout();
    }

//退出全屏時候調用
    public void setNormalScreen(){
        touchStatusImg.setImageResource(R.mipmap.iconfont_enter_32);
        this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
        videoView.requestLayout();
    }

上面提供的setFullScreen()和setNormalScreen()需要在Activity的 onConfigurationChanged(Configuration newConfig)橫豎屏發生改變的 回調方法裡面調用,還需要註意的是我這裡寫的是LinearLayout的LayoutParams,所以我們自定義的view的父空間要是LinearLayout,當然你也可以修改。

4、控制項的使用

我們只需要在獲得空間調用start方法,然後在onConfigurationChanged方法里調用setFullScreen和setNormalScreen就可以了,

佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.qiangyu.test.commonvideoview.MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>

    <com.qiangyu.test.commonvideoview.CommonVideoView
        android:id="@+id/common_videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp" />
</LinearLayout>

activity代碼

public class MainActivity extends AppCompatActivity {

    CommonVideoView videoView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.content_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        videoView = (CommonVideoView) findViewById(R.id.common_videoView);
        videoView.start("你的伺服器視頻地址");
    }



    @Override public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            videoView.setFullScreen();
        }else {
            videoView.setNormalScreen();
        }
    }
}

最後為了防止你的Activity在橫豎屏切換的時候重新創建別忘記在AndroidManifest.xml文件裡面配置
android:configChanges=”orientation|screenSize|screenLayout”, 如果你這裡有疑惑可以參考我的文章–>深入瞭解Activity-生命周期

<activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar"
            android:configChanges="orientation|screenSize|screenLayout">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

好了,整個控制項到這裡就完成了,源碼下載請戳它—>歡迎戳我

如果你覺得有對你有幫助,請動動手給個贊吧,有問題記得反饋給我哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 點擊enter回車鍵實現表單元素切換焦點效果:現在網站都比較追求人性化,比如填寫表單的時候,能夠實現點擊回車就可以切換表單元素的焦點,這樣比使用滑鼠進行切換更能讓人接受,下麵就通過代碼實例介紹一下如何實現此功能。代碼如下: <!DOCTYPE html> <html> <head> <meta ch
  • javascript截取字元串代碼實例:截取字元串是常見的操作,例如新聞列表對新聞標題長度的控制就是如此,下麵分享一段這樣的代碼實例,也不多做介紹了,因為代碼很簡單,很多朋友需要的僅僅是一個例子而已,因為只要看到例子就知道該怎麼用了。代碼如下: var str="螞蟻部落歡迎您,只有努力才會有美好的
  • css如何實現讓文字沉到元素的底部:在實際應用中可能有這樣的需求,那就是將指定的文本沉降到元素的底部。下麵就通過代碼實例介紹以下如何實現此效果。代碼如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author
  • 1.介面部分:對外聲明類的行為和特征(類的定義分為:介面部分和實現部分) ① @interface 介面關鍵字:用於表示這是一個類的介面部分 介面部分功能:是定義類的靜態特征和聲明動態行為 @end 作為結束標誌 對外介面:通過介面就可以在不知道實現的情況下,瞭解這個類有什麼 Person:類名,每
  • 在web頁面中,有a標簽的超鏈接實現跳轉,同樣在Android當中,用TextView控制項來顯示文字,實現它的事件來跳轉。 核心代碼如下: //以下代碼寫在onCreate()方法當中 textView1=(TextView)findViewById(R.id.sound_help); String
  • Remote Displayer for Android V1.0.
  • 代碼: #import "RootViewController.h" //為判斷手機的型號 -(NSString*)deviceString添加頭文件 #import "sys/utsname.h" @interface RootViewController () @end @implementat
  • 一、問題描述 使用百度地圖實現如圖所示應用,首先自動定位當前我起始位置(小圓點位置),並跟隨移動不斷自動定位我的當前位置 百度Api不同版本使用會有些差異,本例中加入lib如下: 二、編寫MyApplication類 public class MyApplication extends Applic
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...