ListView之多種類型Item

来源:http://www.cnblogs.com/snser/archive/2016/07/01/5539749.html
-Advertisement-
Play Games

一、概述 一般而言,listview每個item的樣式是一樣的,但也有很多應用場景下不同位置的item需要不同的樣式。 拿微信舉例,前者的代表作是消息列表,而後者的典型則是聊天會話界面。 本文重點介紹後者,也就是多類型item的listview的實現思路和方法,比如實現一個這樣的聊天會話頁面: 二、 ...


一、概述

一般而言,listview每個item的樣式是一樣的,但也有很多應用場景下不同位置的item需要不同的樣式。

拿微信舉例,前者的代表作是消息列表,而後者的典型則是聊天會話界面。

本文重點介紹後者,也就是多類型item的listview的實現思路和方法,比如實現一個這樣的聊天會話頁面:

 

二、實現思路

2.1 第一種思路用“一種類型”變相實現多種類型

這種思路其實與 ListView之點擊展開菜單 這篇文章的原理一樣,每個item的佈局都包含所有類型的元素:

 

對於每個item,根據實際類型,控制“日期”、“發出的消息”、“接收的消息”這三部分的顯示/隱藏即可。

這種思路的優勢在於好理解,是單一類型的listview的擴展,卻並不適合本文描述的應用場景。

因為每個item實際上只會顯示“日期”、“發出的消息”、“接收的消息”中的一種,所以每個item都inflate出來一個“全家桶”layout再隱藏其中的兩個,實在是一種資源浪費。

 

2.2 第二種思路:利用Adapter原生支持的多類型

其實 android.widget.Adapter 類已經原生支持了多種類型item的模式,並提供了 int getViewTypeCount();  int getItemViewType(int position); 兩個方法

只不過在 android.widget.BaseAdapter 中對這兩個方法進行瞭如下的預設實現:

1 public int getViewTypeCount() {
2     return 1;
3 }
4 
5 public int getItemViewType(int position) {
6     return 0;
7 }

那我們要做的就是根據實際的數據,對這兩個方法進行正確的返回。

本文采用第二種思路實現多種類型item的listview。

   [轉載請保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

三、開始幹活

3.1 首先準備好listview的數據和三種item佈局

ListViewMultiTypeActivity$JsonListData:

 1     private static class JsonListData {
 2         public static class Message {
 3             public static final int TYPE_COUNT = 3;
 4             public static final int TYPE_DATE = 0x00;
 5             public static final int TYPE_TXT_SENT = 0x01;
 6             public static final int TYPE_TXT_RECV = 0x02;
 7             public int type;
 8             public String txt;
 9             public long time;
10         }
11         public List<Message> messages = new ArrayList<Message>();
12     }
View Code

listview_multitype_data.json:

{
    "messages": [
        {
            "type": 0,
            "time": 1467284175
        },
        {
            "type": 1,
            "txt": "你好"
        },
        {
            "type": 2,
            "txt": "你才好"
        },
        {
            "type": 1,
            "txt": "對話,指兩個或更多的人用語言交談,多指小說或戲劇里的人物之間的"
        },
        {
            "type": 2,
            "txt": "京東童書節低至300減180"
        },
        {
            "type": 1,
            "txt": "http://www.cnblogs.com/snser/"
        },
        {
            "type": 2,
            "txt": "京東商城目前已成長為中國最大的自營式電商企業,2015年第三季度在中國自營式B2C電商市場的占有率為56.9%。"
        },
        {
            "type": 0,
            "time": 1467289175
        },
        {
            "type": 1,
            "txt": "京東金融現已建立七大業務板塊,分別是供應鏈金融、消費金融、眾籌、財富管理、支付、保險、證券,陸續推出了京保貝、白條、京東錢包、小金庫、京小貸、產品眾籌、私募股權融資、小白理財等創新產品"
        },
        {
            "type": 2,
            "txt": "您目前沒有新消息"
        },
        {
            "type": 2,
            "txt": "黑炎凝聚,竟是直接化為了一頭仰天長嘯的黑色巨鳥,而後它仿佛是發現了牧塵飄蕩的意識,化為一道黑色火焰,眼芒凶狠的對著他的意識暴沖而來"
        },
        {
            "type": 0,
            "time": 1467294175
        },
        {
            "type": 2,
            "txt": "國務院罕見派出民間投資督查組:活力不夠形勢嚴峻"
        },
        {
            "type": 1,
            "txt": "那一道清鳴,並不算太過的響亮,但卻是讓得牧塵如遭雷擊,整個身體都是僵硬了下來,腦子裡迴蕩著嗡嗡的聲音。"
        },
        {
            "type": 2,
            "txt": "據海關統計,今年前4個月,我國進出口總值7.17萬億元人民幣,比去年同期(下同)下降4.4%。其中,出口4.14萬億元,下降2.1%;進口3.03萬億元,下降7.5%;貿易順差1.11萬億元,擴大16.5%。"
        },
        {
            "type": 1,
            "txt": "在介紹演算法的時空複雜度分析方法前,我們先來介紹以下如何來量化演算法的實際運行性能,這裡我們選取的衡量演算法性能的量化指標是它的實際運行時間。"
        },
        {
            "type": 2,
            "txt": "你拍一"
        },
        {
            "type": 2,
            "txt": "我拍一"
        },
        {
            "type": 1,
            "txt": "一二三四五六七"
        }
    ]
}
View Code

ListViewMultiTypeActivity.onCreate 

 1     protected void onCreate(Bundle savedInstanceState) {
 2         super.onCreate(savedInstanceState);
 3         setContentView(R.layout.listview_multi_type);
 4         
 5         JsonListData data = null;
 6         try {
 7             InputStream is = getResources().getAssets().open("listview_multitype_data.json");
 8             InputStreamReader isr = new InputStreamReader(is);
 9             Gson gson = new GsonBuilder().serializeNulls().create();
10             data = gson.fromJson(isr, JsonListData.class);
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14         
15         if (data != null && data.messages != null) {
16             mList = (ListView)findViewById(R.id.listview_multi_type_list);
17             mList.setAdapter(new MultiTypeAdapter(ListViewMultiTypeActivity.this, data.messages));
18         }
19     }

listview_multi_type_item_date.xml

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_date_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:layout_gravity="center_horizontal"
14         android:layout_margin="6dp"
15         android:padding="3dp"
16         android:background="#CCCCCC"
17         android:textColor="@android:color/white"
18         android:textSize="12sp"
19         android:text="2015年3月25日 18:44" />
20     
21 </LinearLayout>
View Code

listview_multi_type_item_txt_sent.xml

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_sent_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="right"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="10dp"
19         android:paddingLeft="5dp"
20         android:background="@drawable/listview_multi_type_item_txt_sent_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="發出的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>
View Code

listview_multi_type_item_txt_recv.xml

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_recv_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="left"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="5dp"
19         android:paddingLeft="10dp"
20         android:background="@drawable/listview_multi_type_item_txt_recv_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="接收的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>
View Code

 

3.2 重頭戲在於Adapter的處理

  1     private class MultiTypeAdapter extends BaseAdapter {
  2         private LayoutInflater mInflater;
  3         private List<JsonListData.Message> mMessages;
  4         private SimpleDateFormat mSdfDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault());
  5         
  6         public MultiTypeAdapter(Context context, List<JsonListData.Message> messages) {
  7             mInflater = LayoutInflater.from(context);
  8             mMessages = messages;
  9         }
 10         
 11         private class DateViewHolder {
 12             public DateViewHolder(View viewRoot) {
 13                 date = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_date_txt);
 14             }
 15             public TextView date;
 16         }
 17         
 18         private class TxtSentViewHolder {
 19             public TxtSentViewHolder(View viewRoot) {
 20                 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_sent_txt);
 21             }
 22             public TextView txt;
 23         }
 24         
 25         private class TxtRecvViewHolder {
 26             public TxtRecvViewHolder(View viewRoot) {
 27                 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_recv_txt);
 28             }
 29             public TextView txt;
 30         }
 31         
 32         @Override
 33         public int getViewTypeCount() {
 34             return JsonListData.Message.TYPE_COUNT;
 35         }
 36         
 37         @Override
 38         public int getItemViewType(int position) {
 39             return getItem(position).type;
 40         }
 41         
 42         @Override
 43         public int getCount() {
 44             return mMessages.size();
 45         }
 46 
 47         @Override
 48         public JsonListData.Message getItem(int position) {
 49             return mMessages.get(position);
 50         }
 51 
 52         @Override
 53         public long getItemId(int position) {
 54             return position;
 55         }
 56 
 57         @Override
 58         public View getView(int position, View convertView, ViewGroup parent) {
 59             switch (getItemViewType(position)) {
 60                 case JsonListData.Message.TYPE_DATE:
 61                     return handleGetDateView(position, convertView, parent);
 62                 case JsonListData.Message.TYPE_TXT_SENT:
 63                     return handleGetTxtSentView(position, convertView, parent);
 64                 case JsonListData.Message.TYPE_TXT_RECV:
 65                     return handleGetTxtRecvView(position, convertView, parent);
 66                 default:
 67                     return null;
 68             }
 69         }
 70         
 71         private View handleGetDateView(int position, View convertView, ViewGroup parent) {
 72             if (convertView == null) {
 73                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_date, parent, false);
 74                 convertView.setTag(new DateViewHolder(convertView));
 75             }
 76             if (convertView != null && convertView.getTag() instanceof DateViewHolder) {
 77                 final DateViewHolder holder = (DateViewHolder)convertView.getTag();
 78                 holder.date.setText(formatTime(getItem(position).time));
 79             }
 80             return convertView;
 81         }
 82         
 83         private View handleGetTxtSentView(int position, View convertView, ViewGroup parent) {
 84             if (convertView == null) {
 85                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_sent, parent, false);
 86                 convertView.setTag(new TxtSentViewHolder(convertView));
 87             }
 88             if (convertView != null && convertView.getTag() instanceof TxtSentViewHolder) {
 89                 final TxtSentViewHolder holder = (TxtSentViewHolder)convertView.getTag();
 90                 holder.txt.setText(getItem(position).txt);
 91             }
 92             return convertView;
 93         }
 94         
 95         private View handleGetTxtRecvView(int position, View convertView, ViewGroup parent) {
 96             if (convertView == null) {
 97                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_recv, parent, false);
 98                 convertView.setTag(new TxtRecvViewHolder(convertView));
 99             }
100             if (convertView != null && convertView.getTag() instanceof TxtRecvViewHolder) {
101                 final TxtRecvViewHolder holder = (TxtRecvViewHolder)convertView.getTag();
102                 holder.txt.setText(getItem(position).txt);
103             }
104             return convertView;
105         }
106         
107         private String formatTime(long time) {
108             return mSdfDate.format(new Date(time * 1000));
109         }
110     }

可以看到, int getViewTypeCount();  int getItemViewType(int position); 的處理是非常清晰的。

需要註意的在於,ViewType必須在 [0, getViewTypeCount() - 1] 範圍內

 

3.3 ViewHolder為何能正確的工作

回顧一下單一類型的listview,其ViewHolder的工作機制在於系統會將滑出屏幕的item的view回收起來,並作為getView的第二個參數 convertView 傳入。

那麼,在多種類型的listview中,滑出屏幕的view與即將滑入屏幕的view類型很可能是不同的,那這麼直接用不就掛了嗎?

其實不然,android針對多種類型item的情況已經做好處理了,如果getView傳入的 convertView 不為null,那它一定與當前item的view類型是匹配的。

所以,在3.2節中對ViewHolder的處理方式與單類型的listview並沒有本質區別,卻也能正常的工作。

  [轉載請保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

四、demo工程

保存下麵的圖片,擴展名改成 .zip 即可

  [轉載請保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

五、番外篇 —— ListView回收機制簡要剖析

在3.3節中簡單介紹了android系統會處理好多類型item的回收和重用,那具體是怎麼實現的呢?

下麵簡要剖析一下支持多種類型item的listview中,View回收的工作機制。

5.1 View回收站的初始化

ListView的父類AbsListView中定義了一個內部類RecycleBin,這個類維護了listview滑動過程中,view的回收和重用。

在ListView的 setAdapter 方法中,會通過調用 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()) 來初始化RecycleBin。

讓我們看下RecycleBin中對應都做了什麼:

 1         public void setViewTypeCount(int viewTypeCount) {
 2             if (viewTypeCount < 1) {
 3                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
 4             }
 5             //noinspection unchecked
 6             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
 7             for (int i = 0; i < viewTypeCount; i++) {
 8                 scrapViews[i] = new ArrayList<View>();
 9             }
10             mViewTypeCount = viewTypeCount;
11             mCurrentScrap = scrapViews[0];
12             mScrapViews = scrapViews;
13         }

看源碼,說白了就是創建了一個大小為 getViewTypeCount() 數組 mScrapViews ,從而為每種類型的view維護了一個回收站,此外每種類型的回收站自身又是一個View數組。

這也就解釋了為什麼ViewType必須在 [0, getViewTypeCount() - 1] 範圍內。

 

5.2 View回收站的構建和維護

AbsListView在滑動時,會調用 trackMotionScroll 方法:

 1     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
 2         //...
 3         final boolean down = incrementalDeltaY < 0;
 4         //...
 5         if (down) {
 6             int top = -incrementalDeltaY;
 7             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
 8                 top += listPadding.top;
 9             }
10             for (int i = 0; i < childCount; i++) {
11                 final View child = getChildAt(i);
12                 if (child.getBottom() >= top) {
13                     break;
14                 } else {
15                     count++;
16                     int position = firstPosition + i;
17                     if (position >= headerViewsCount && position < footerViewsStart) {
18                         // The view will be rebound to new data, clear any
19                         // system-managed transient state.
20                         if (child.isAccessibilityFocused()) {
21                             child.clearAccessibilityFocus();
22                         }
23                         mRecycler.addScrapView(child, position);
24                     }
25                 }
26             }
27         } else {
28             int bottom = getHeight() - incrementalDeltaY;
29             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
30                 bottom -= listPadding.bottom;
31             }
32             for (int i = childCount - 1; i >= 0; i--) {
33                 final View child = getChildAt(i);
34                 if (child.getTop() <= bottom) {
35                     break;
36                 } else {
37                     start = i;
38                     count++;
39                     int position = firstPosition + i;
40                     if (position >= headerViewsCount && position < footerViewsStart) {
41                         // The view will be rebound to new data, clear any
42                         // system-managed transient state.
43                         if (child.isAccessibilityFocused()) {
44                             child.clearAccessibilityFocus();
45                         }
46                         mRecycler.addScrapView(child, position);
47                     }
48                 }
49             }
50         }
51         //...
52     }

 trackMotionScroll 方法中,會根據不同的滑動方向,調用 addScrapView ,將滑出屏幕的view加到RecycleBin中:

 1         void addScrapView(View scrap, int position) {
 2             final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
 3             if (lp == null) {
 4                 return;
 5             }
 6 
 7             lp.scrappedFromPosition = position;
 8 
 9             // Remove but don't scrap header or footer views, or views that
10             // should otherwise not be recycled.
11             final int viewType = lp.viewType;
12             if (!shouldRecycleViewType(viewType)) {
13                 return;
14             }
15 
16             scrap.dispatchStartTemporaryDetach();
17 
18             // The the accessibility state of the view may change while temporary
19             // detached and we do not allow detached views to fire accessibility
20             // events. So we are announcing that the subtree changed giving a chance
<

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

-Advertisement-
Play Games
更多相關文章
  • 一、理解web標準含義--為什麼採用web標準*****將內容與樣式分離1、web標準是一系列標準,就是一系列技術標準在使用時,是組合應用【1】、結構化內容 xhtml【2】、表現化內容 css【3】、行為化內容 JavaScript2、網頁開發的小工具--火狐瀏覽器中的firebug工具--附加組 ...
  • 說到JavaScript中的分支結構,我們就不得不提到流程式控制制這個詞,我們所有的程式都是由數據和演算法組成的。程式=數據+演算法通常我們所說的演算法都可以通過"順序","分支","迴圈"三種結構來組合完成。 在ECMA中規定了一些語句(也稱為流程式控制制語句,分支結構語句),從本質上來說,這些語句定義了ECM ...
  • 在一些網站進行上傳時,當單擊了“瀏覽”按鈕之後會彈出【選擇文件】的對話框。想要實現這一功能,用input的file控制項來實現就好啦~ 效果圖是醬嬸的: 註意!別以為這個是由一個text和一個button組合成的,其實它就是一個file控制項哦 今天工作中遇到要求:不顯示“未選擇任何文件”,搗鼓夠一個小 ...
  • 【問題產生】 Webview 通過 addjavascriptInterface 傳遞對象給前端,一切正常。但是 Android官方已提醒此功能是有安全風險,改用 safe-java-js-webview-bridge 做java和js交互。 官方的用法正常: 但如果我們在body里的<script ...
  • 閉包 閉包: 指有權訪問另一個函數作用域中的變數的函數。 創建閉包的常見方式就是在一個函數內部創建另一個函數: function createComparisonFunction(propertyName) { return function (obj1, obj2) { // 訪問了外部函數中的變 ...
  • 1.流(flow)是瀏覽器在頁面上擺放HTML元素所用的方法。 對於塊元素,瀏覽器從上到下沿著元素流逐個顯示所遇到的各個元素,會在每個塊元素之間加一個換行; 對於內聯元素,在水平方向會相互挨著,總體上會從左上方留向右下方。 2.流與盒模型 盒模型:從CSS角度來看,每個元素都是一個盒子。由內容區(c ...
  • Chrome 瀏覽器具有強大的跨平臺能力以及豐富的擴展插件,一直是許多開發者的首要選擇。而利用許多 Chrome 插件,開發者們在開發流程中能夠極大地提高開發效率。我們就整理了十款開發者常用的 Chrome 插件推薦給大家,相信能夠在你的開發中助你一臂之力。 1. 掘金 Chrome 插件 :幫你發 ...
  • 一、1xx 消息 該類型的狀態碼代表請求已被接受,需要繼續處理。 100 Continue 客戶端應當繼續發送請求,這個臨時響應是用來通知客戶端的部分請求已經被伺服器接收,且仍未被拒絕。客戶端應當繼續發送請求的剩餘部分。如果請求已經完成,忽略這個響應。伺服器必須在請求完成後向客戶端發送一個最終響應。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...