做了個Android項目,需要接入新浪微博,實現時也順帶著研究了下騰訊微博和人人網的Android客戶端接入,本文就跟大家分享下三者的Android客戶端接入方法。
一、實例概述
說白了,接入微博就是讓你的應用程序可以調用新浪微博,騰訊微博和人人網的api,實現微博發送,分享給好友等等的功能啦。當然也可以實現讓你的客戶端使用微博賬號進行登錄。我們這篇文章要講的就是訪問這些社交平台提供的api的時候比較關鍵的一步,獲取調用api接口的token,token可以理解為我們的客戶端程序與社交平台api交互的令牌。
二、官方教程
學習東西,當然是官方的東西最好了。下面我們就簡單說說接入新浪,騰訊微博以及人人網的官方教程吧。同學們也可以自行去看官方的教程,那麼我下面寫的東西就可以忽略不計了。
新浪微博 api:http://open.weibo.com/wiki/%E9%A6%96%E9%A1%B5
首先,當然你得有一個新浪微博的賬號啦。然後就是注冊應用啊等等一代堆東西,自己去 開發指南--新手引導 中看吧。然後,就要填寫我們的信息了。這裡要注意授權頁的填寫,在 我的應用--應用信息--高級信息--授權回調頁 這裡,如下圖1。最後,就是如何對我們的客戶端程序進行授權了。新浪微博的授權機制說明如下:大部分API的訪問如發表微博、獲取私信,關注都需要用戶身份。目前新浪微博開放平台用戶身份鑒權有OAuth2.0和Basic Auth(僅用於應用所屬開發者調試接口)兩種方式。我們這裡采用的是OAuth2.0的授權機制。
如何獲取token,我們將在下面的文章中結合代碼進行講解。
圖1 設置授權回調頁
騰訊微博 api:http://dev.t.qq.com/
騰訊微博的應用申請和新浪微博的類似,它也有一個授權回調頁。不過它不是這個名字,它的名字是:應用網址。為簡單起見,我們這裡也使用默認的地址,即:http://www.tencent.com/zh-cn/index.shtml 其他的和騰訊微博差不多,就不贅述了。騰訊微博提供了OAuth 1.0a,OAuth2.0和OpenId&OpenKey授權方式,本人中采用的是OAuth2.0授權方式,其他的方式同學們自行研究。
人人網 api:http://dev.renren.com/ 人人網使用OAuth 2.0作為驗證與授權協議。
人人網目前有一個移動開發者平台,專門為移動應用和HTML5應用提供更簡潔的授權接口,簡單易用,稱為人人移動開發者中心。地址如下:
http://dev.mobile.renren.com/home/show
三、關於OAuth2.0
本人比較懶,就不把概念貼在這裡了。自己百度去吧,百度百科中有這樣的一句概括:OAuth 2.0關注客戶端開發者的簡易性,同時為Web應用,桌面應用和手機,和起居室設備提供專門的認證流程。這裡我們稍微說一下OAuth2.0的認證流程。
在OAuth2.0的處理流程,主要分為以下四個步驟:
1、得到授權碼code
2、獲取access token
3、通過access token,獲取OpenID
4、通過access token及OpenID調用API,獲取用戶授權信息
上面是流程的大概四個步驟,在下面的流程示意圖中會得到體現,這是我制作的一個幻燈片的流程圖(文章最後會附上制作的OAuth幻燈片分享給大家),這裡就直接截圖下來進行講解:
圖2 OAuth2.0 認證流程
第一步:首先直接跳轉至用戶授權地址,即圖示 Request User Url ,提示用戶進行登錄,並給予相關資源授權,得到唯一的Auth code,這裡注意的是code只有10分鐘的有效期,對於安全考慮,相對於OAuth1.0省了一步獲取臨時的Token,並且有效期也進行了控制,比1.0認證簡化了很多,並安全一些;
第二步:得到授權code後,這一步就是請求access token,通過 圖示 Request access url ,生成得到數據Token;
第三步:通過Access Token請求OpenID,openid是用戶在此平台的唯一標識,通過 圖示 Request info url 請求,然後得到OpenID;
第四步:通過第二步得到的數據Token、第三步得到的OpenID及相關API,進行請求,獲取用戶授權資源信息。(我喜歡簡潔明了,OAuth2.0認證流程就是這樣,相信通過圖示及講解都能明白,若有不明白之處請留言)
四、讓你的應用程序訪問社交平台api(獲取token)
注意要加相應的權限,文章後面會貼出清單文件中所需的權限。
新浪微博授權:
詳細內容請參見新浪微博SDK中自帶的文檔:Android平台微博SDK說明文檔.docx。它在SDK的工程目錄下,所以你要導入SDK工程的時候主要把它去了。由於新浪微博給我們提供的SDK是以工程的方式給出的,所以我們需要引用它的工程才可以使用它提供的api。當然啦,這樣放到實際環境中是不行的。所以我們可以將SDK下的相關文件拷貝到我們的自己的工程目錄下即可,注意修改相應的東西。本人是這樣做的,不知道還有沒有更好的方式。具體的請看文章後面給出的demo。值得一提的是,人人網也是以這種方式提供SDK的,但是騰訊微博給我們提供的是JAR,這個看起來就簡潔多了。這裡順便說一下覆蓋的方法吧,就是將新浪微博給我們提供的SDK工程文件下的src和res文件夾拷貝到你自己的工程中覆蓋原有的文件即可。
環境准備好了,我們就可以開始進行代碼的編寫了。還是那句話,有官方教程在呢,同學們可以對著教程來練習,我下面只是結合代碼簡單的說一下。
Java代碼
- //sina CONSUMER_KEY、CONSUMER_SECRET替換成你自己應用的key和secret
- private static final String CONSUMER_KEY = "913917729";
- private static final String CONSUMER_SECRET = "32c47f37e4727ce9c0db1ceee12bf765";
- private String redirectUriSina="http://www.sina.com"; //授權回調頁 與 我的應用--應用信息--高級信息 中一致
- OAuthV2 authV2 = null;//騰訊微博Oauth
- Renren renren = null;//renren
- File file = null;
- String basepath = "";
-
- //tencent CLINETID、CLIENTSECRET替換成你自己應用的key和secret
- private static final String CLINETID = "801208558";
- private static final String CLIENTSECRET = "da6d09bb537559c37cb36561fd825346";
- //認證成功後浏覽器會被重定向到這個url中 必須與注冊時填寫的一致
- private String redirectUriTen="http://www.tencent.com/zh-cn/index.shtml";
-
- //renren API_KEY、SECRET_KEY替換成你自己應用的key和secret
- private static final String API_KEY = "b6f9602ab3714023b794d34b51639a99";
- private static final String SECRET_KEY = "f51ce08118014784a0579ba88730b6bd";
- private static final String APP_ID = "206681";//人人網還需要app_id
- private static final int RENREN_REQUEST_CODE = 123;
- String accessToken;
Java代碼
- //新浪微博認證
- Weibo weibo = Weibo.getInstance();
- if(!isOauthSina(weibo)){
- weibo.setupConsumerConfig(CONSUMER_KEY, CONSUMER_SECRET);//設置你的key和secret
- weibo.setRedirectUrl(redirectUriSina);
- weibo.authorize(this, new OauthDialogListener());
- }else{
- tv.setText("access_token : " + accessToken);
- Toast.makeText(getApplicationContext(), "該用戶已經授權", Toast.LENGTH_SHORT).show();
- Intent intent = new Intent();
- intent.putExtra("accessToken", accessToken);
- intent.putExtra("flag", MyContent.SINA);
- intent.setClass(InsertWeiboActivity.this, TestActivity.class);
- startActivity(intent);
- }
isOauthSina方法如下,它的作用是判斷用戶是否已經對我們的應用進行了授權。
Java代碼
- /**
- * 新浪微博 用戶是否已經授權
- * @param weibo
- * @return
- */
- private boolean isOauthSina(Weibo weibo){
- boolean b = false;
- accessToken = getSharedPreferences("token", Context.MODE_PRIVATE).getString("access_token", "");
- if(weibo != null && !accessToken.equals("")){
- b = true;
- }
- return b;
- }
上面的兩段代碼中, 值得注意的地方是redirectUriSina,它是一個String類型的參數,值就是我們在之前提到過得那個授權回調頁的地址。接著新浪微博將彈出一個授權頁面的對話框,供用戶輸入用戶名和密碼(此處就不截圖了)。在用戶輸入用戶名和密碼之後,它將被我們的OauthDialogListener類監聽到,它的實現如下:
Java代碼
- /**
- * 彈出新浪微博的授權頁面
- * @author yanbin
- *
- */
- private class OauthDialogListener implements WeiboDialogListener{
- @Override
- public void onComplete(Bundle values) {
- String token = values.getString("access_token");
- String expires_in = values.getString("expires_in");
- tv.setText("access_token : " + token + " expires_in: "
- + expires_in);
- AccessToken accessToken = new AccessToken(token, CONSUMER_SECRET);
- SharedPreferences sf = getSharedPreferences("token", Context.MODE_PRIVATE);
- sf.edit().putString("access_token", accessToken != null ? accessToken.getToken() : "")
- .commit();
- // accessToken.setExpiresIn(expires_in);
- // Weibo.getInstance().setAccessToken(accessToken);
- Intent intent = new Intent();
- intent.putExtra("accessToken",accessToken.getToken());
- intent.putExtra("flag", MyContent.SINA);
- intent.setClass(InsertWeiboActivity.this, TestActivity.class);
- startActivity(intent);
- }
-
- @Override
- public void onWeiboException(WeiboException e) {
- //未作處理
- }
-
- @Override
- public void onError(DialogError e) {
- //未作處理
- }
-
- @Override
- public void onCancel() {
- Toast.makeText(InsertWeiboActivity.this, "您已經取消授權", 1).show();
- }
- }
WeiboDialogListener中有一個回調方法,它會在用戶輸入正確的用戶名和密碼之後返回給我們的客戶端程序獲取新浪微博提供的api的令牌(這個東西很重要的,你調用新浪微博api的時候每次都必須把它帶上)。接下來的工作就是保存此token供下次使用了。這裡我們存儲在SharedPreference中。至此,我們就獲取了新浪微博的token,接下來就可以操作新浪微博api了,比如發布一條微博,可以試試哦。
騰訊微博授權:
騰訊微博授權的官方實例請看這裡,http://wiki.open.t.qq.com/index.php/%E7%A7%BB%E5%8A%A8%E5%BA%94%E7%94%A8%E6%8E%A5%E5%85%A5/Android_SDK_V1.2。下面我們結合代碼說說我們的實現。 首先第一步就是得到騰訊給我們提供的授權回調頁,代碼如下,由於要彈出OAuthV2AuthorizeWebView這個頁面,所以要在清單文件中聲明。
Java代碼
- //判斷用戶是否已經授權
- if(!isOauchTen()){
- //使用回調url來創建授權頁面
- authV2 = new OAuthV2(redirectUriTen);
- authV2.setClientId(CLINETID);
- authV2.setClientSecret(CLIENTSECRET);
- //關閉OAuthV2Client中的默認開啟的QHttpClient。
- OAuthV2Client.getQHttpClient().shutdownConnection();
- Intent intent = new Intent(InsertWeiboActivity.this, OAuthV2AuthorizeWebView.class);//創建Intent,使用WebView讓用戶授權
- intent.putExtra("oauth", authV2);
- startActivityForResult(intent,2);
- }else{
- Toast.makeText(getApplicationContext(), "該用戶已經授權", Toast.LENGTH_SHORT).show();
- Intent intent = new Intent();
- intent.putExtra("oauth", authV2);
- intent.putExtra("flag", MyContent.TENCENT);
- intent.setClass(InsertWeiboActivity.this, TestActivity.class);
- startActivity(intent);
- }
這裡我們看到了startActivityForResult,它會在用戶輸入完用戶名和密碼後執行OnActivityResult方法(包括了人人的實現),isOauthTen()方法的作用是判斷用戶是否已經授權過該應用。它們的代碼如下:
OnActivityResult()方法:
Java代碼
- /**
- * 新浪微博和人人網的授權
- * 通過讀取OAuthV2AuthorizeWebView返回的Intent,獲取用戶授權信息
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode==2) {
- //騰訊微博授權
- if (resultCode==OAuthV2AuthorizeWebView.RESULT_CODE) {
- OAuthV2 oAuth=(OAuthV2) data.getExtras().getSerializable("oauth");
- if(oAuth != null && oAuth.getStatus()==0){
- Toast.makeText(getApplicationContext(), "登陸成功", Toast.LENGTH_SHORT).show();
- //跳轉到發微博的界面
- Intent intent = new Intent();
- intent.putExtra("accessToken", oAuth.getAccessToken());
- intent.putExtra("oauth", oAuth);
- intent.putExtra("flag", MyContent.TENCENT);
- //將認證保存起來,使用對象流
- FileOutputStream fos = null;
- ObjectOutputStream oos = null;
- try {
- fos = new FileOutputStream(file);
- oos = new ObjectOutputStream(fos);
- oos.writeObject(oAuth);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally{
- if(oos != null){
- try {
- oos.close();
- oos = null;
- } catch (IOException e) {
- e.printStackTrace();
- }
- if(fos != null){
- try {
- fos.close();
- fos = null;
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- intent.setClass(InsertWeiboActivity.this, TestActivity.class);
- startActivity(intent);
- }
- else
- Toast.makeText(getApplicationContext(), "登陸失敗", Toast.LENGTH_SHORT).show();
- }else{
- Toast.makeText(getApplicationContext(), "授權失敗", Toast.LENGTH_SHORT).show();
- }
- }else if(requestCode == RENREN_REQUEST_CODE){
- //人人網授權
- if (renren != null) {
- renren.authorizeCallback(requestCode, resultCode, data);
- }else{
- Toast.makeText(getApplicationContext(), "授權失敗", Toast.LENGTH_SHORT).show();
- }
- }
- }
isOauchTen()方法:
Java代碼
- /**
- * 騰訊微博接入是否已經驗證
- * @return
- */
- private boolean isOauchTen() {
- boolean b = false;
- FileInputStream fis = null;
- ObjectInputStream ois = null;
- try {
- openFileOutput("aaa", Context.MODE_PRIVATE);
- persistTenOauth();
- fis = new FileInputStream(file);
- ois = new ObjectInputStream(fis);//此處拋出EOFException,原因是獨到了流的末尾還是返回空,我們這裡直接在異常中將標志位記為false即可。
- authV2 = (OAuthV2) ois.readObject();
- if(authV2 != null){
- b = true;
- }
- } catch (Exception e) {
- b = false;
- } finally{
- if(ois != null){
- try {
- ois.close();
- ois = null;
- } catch (Exception e2) {
- e2.printStackTrace();
- }
- }
- if(fis != null){
- try {
- fis.close();
- fis = null;
- } catch (Exception e2) {
- e2.printStackTrace();
- }
- }
-
- }
- return b;
- }
persistTenOauth()方法的作用是將我們獲取到的token存儲起來,由於調用騰訊微博api需要Token對象,沒有辦法所以我們只有將Token對象存起來,這裡使用對象流的方式,具體實現如下:
Java代碼
- /**
- * 將騰訊微博的oauth持久化到文件中
- */
- private void persistTenOauth(){
- //加這一句的作用是防止 /data/data/package-name這個目錄不存在
- String s = getFileStreamPath("aaa").getAbsolutePath();
- for(String ss : fileList()){
- System.out.println("ss==" + ss);
- }
- String x = "";
- try{
- x = s.substring(0,s.lastIndexOf("/"));
- }catch(Exception e){
- e.printStackTrace();
- x = "/data/data/yanbin.insertweibo";
- }
- //將文件存放在 /data/data/package-name目錄下,當然你也可以存儲在別的地方
- try {
- file = new File(x + "/oauth_ten.data");
- if(!file.exists()){
- new File(x).mkdirs();
- file.createNewFile();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
至此,我們已經成功的獲取到了訪問騰訊微博必須的令牌,接下來我們就可以使用這個令牌操縱api了。
人人網授權:
人人網並沒有實現發心情的功能,只是簡單的獲取了它的token。
實現的代碼如下:
Java代碼
- if(!isOauthRenren()){
- renren = new Renren(API_KEY, SECRET_KEY, APP_ID, this);
- renren.authorize(this, null, new MyRenrenListener(), RENREN_REQUEST_CODE);
- }else{
- Toast.makeText(getApplicationContext(), "該用戶已經授權", Toast.LENGTH_SHORT).show();
- }
isOauthRenren用來判斷用戶是否對本客戶端進行了授權。MyRenrenListener類會在用戶在輸入框中輸入用戶名和密碼後回調。它們的具體實現如下:
isOauthRenren()方法:
Java代碼
- /**
- * renren 判斷用戶是否已經授權
- * @return
- */
- private boolean isOauthRenren(){
- boolean b = false;
- String token = getSharedPreferences("oauth_renren", Context.MODE_PRIVATE).getString("access_token", "");
- if(!"".equals(token)){
- b = true;
- }
- return b;
- }
MyRenrenListener類的實現:
Java代碼
- /**
- * 人人請求用戶授權返回界面
- * @author yanbin
- *
- */
- private class MyRenrenListener implements RenrenAuthListener{
-
- @Override
- public void onComplete(Bundle values) {
- //服務器端返回的數據
- // {
- // "access_token": "10000|5.a6b7dbd428f731035f771b8d15063f61.86400.1292922000-222209506",
- // "expires_in": 87063,
- // "refresh_token": "10000|0.385d55f8615fdfd9edb7c4b5ebdc3e39-222209506",
- // "scope": "read_user_album read_user_feed"
- // }
- Bundle bundle = values;
- String access_token = bundle.getString("access_token");
- int expires_in = bundle.getInt("expires_in");
- String refresh_token = bundle.getString("refresh_token");
- String scope = bundle.getString("scope");
- System.out.println("驗證服務器端返回的數據-->" + "access_token-->" + access_token
- + ",expires_in-->" + expires_in
- + ",refresh_token-->" + refresh_token
- + ",scope-->" + scope);
- SharedPreferences sp = getSharedPreferences("oauth_renren", Context.MODE_PRIVATE);
- sp.edit().putString("access_token", access_token).commit();
- Toast.makeText(getApplicationContext(), "用戶授權成功", Toast.LENGTH_SHORT).show();
- //結果:
- //驗證服務器端返回的數據-->access_token-->206681|6.725b8c8b3457a7d2953868d63aaf4486.2592000.1346828400-473945629
- //,expires_in-->0,refresh_token-->null,
- //scope-->publish_feed create_album photo_upload read_user_album status_update
- }
-
- @Override
- public void onRenrenAuthError(RenrenAuthError renrenAuthError) {
- Toast.makeText(getApplicationContext(), "異常", Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void onCancelLogin() {
- //未作處理
- }
-
- @Override
- public void onCancelAuth(Bundle values) {
- //未作處理
- }
-
- }
至此,我們也成功的獲取到了訪問人人網api的token,接下來就是對api的操作了。下面是一些相關的類(TestActivity.java)和AndroidManifest.xml文件。
TestActivity.java:
Java代碼
- package yanbin.insertWeibo;
-
- import java.net.InetAddress;
- import java.net.NetworkInterface;
- import java.net.SocketException;
- import java.util.Enumeration;
-
-
- import com.tencent.weibo.api.TAPI;
- import com.tencent.weibo.constants.OAuthConstants;
- import com.tencent.weibo.oauthv2.OAuthV2;
- import com.weibo.net.Utility;
- import com.weibo.net.Weibo;
- import com.weibo.net.WeiboException;
- import com.weibo.net.WeiboParameters;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.Toast;
-
- /**
- * 騰訊微博,新浪微博 簡單的發送一條微博,可以進你自己的微博查看是否發送成功
- * @author yanbin
- */
- public class TestActivity extends Activity {
- Button btnSend ;
- EditText et;
- String accessToken;
- OAuthV2 oAuthV2,oAuthV2_2;
- int flag ;//flag用來標記是來自新浪,騰訊微博還是人人
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.send_weibo);
- accessToken = getIntent().getStringExtra("accessToken");
- flag = getIntent().getIntExtra("flag", -1);
- oAuthV2 = (OAuthV2) getIntent().getSerializableExtra("oauth");
- btnSend = (Button) findViewById(R.id.btnSend);
- et = (EditText) findViewById(R.id.et);
- }
-
-
- public void click(View view){
- int id = view.getId();
- switch (id) {
- case R.id.btnSend:
- switch (flag) {
- case MyContent.SINA:
- Weibo weibo = Weibo.getInstance();
- WeiboParameters parameters = new WeiboParameters();
- parameters.add("access_token", accessToken);
- parameters.add("status", et.getText().toString());
- //發送一條微博 url https://api.weibo.com/2/statuses/update.json
- try {
- String flag = weibo.request(this, Weibo.SERVER + "statuses/update.json", parameters, Utility.HTTPMETHOD_POST, weibo.getAccessToken());
- System.out.println("flag==" + flag);
- Toast.makeText(this, "發送微博成功", 1).show();
- } catch (WeiboException e) {
- e.printStackTrace();
- Toast.makeText(this, "發送微博失敗", 1).show();
- }
- finish();
- break;
- case MyContent.TENCENT:
- TAPI tapi = new TAPI(OAuthConstants.OAUTH_VERSION_2_A);
- try {
- String response=tapi.add(oAuthV2, "json", et.getText().toString(), getHostIp());
- System.out.println("response==" + response);
- tapi.shutdownConnection();
- System.out.println("發送微博成功");
- Toast.makeText(this, "發送微博成功", 1).show();
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(this, "發送微博失敗", 1).show();
- }
- finish();
- break;
- }
- break;
- }
- }
- /**
- * 獲取用戶ip
- * @return
- */
- public static String getHostIp() {
- try {
- for (Enumeration<NetworkInterface> en = NetworkInterface
- .getNetworkInterfaces(); en.hasMoreElements();) {
- NetworkInterface intf = en.nextElement();
- for (Enumeration<InetAddress> ipAddr = intf.getInetAddresses(); ipAddr
- .hasMoreElements();) {
- InetAddress inetAddress = ipAddr.nextElement();
- if (!inetAddress.isLoopbackAddress()) {
- return inetAddress.getHostAddress();
- }
- }
- }
- } catch (SocketException ex) {
- } catch (Exception e) {
- }
- return null;
- }
- }
AndroidManifest.xml:
XML/HTML代碼
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="yanbin.insertWeibo"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk android:minSdkVersion="7" />
-
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name=".InsertWeiboActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity android:name=".TestActivity"></activity>
- <!-- OAuth Version 2. 使用 WebView 輔助進行ImplicitGrant方式授權必須 -->
- <activity
- android:name="com.tencent.weibo.webview.OAuthV2AuthorizeWebView"
- android:label="@string/app_name" >
- </activity>
- </application>
- <uses-permission android:name="android.permission.INTERNET"></uses-permission>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
- <uses-permission android:name="android.permission.WRITE_APN_SETTINGS"></uses-permission>
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
- <!-- 在SDCard中創建與刪除文件權限 -->
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
- <!-- 往SDCard寫入數據權限 -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- </manifest>
接入社交平台本就是一個比較簡單的功能,想跟大家說詳細些,所以文章篇幅有點長,望耐心看完。