安卓app、微信小程序訪問webapi,將需要一時間,我們稱之為耗時操作,其它諸如密集型計算、訪問文件與設備等亦是如此。在這個期間我們應該跳出提示,告知用戶正在等待,并且很多時候,在等待時不允許用戶再對UI進行操作,直到耗時操作結束(無論是否成功,下同)
在安卓app中可以定義一個繼承自Dialog的“等待對話框 WaItDialog”,并且設置setCanceledOnTouchOutside(false),如此會設置一個“遮罩層”,這樣,當在耗時操作開始前開啟(Show)WaItDialog直到結束關閉(dismiss),用戶就不能點擊UI了。
微信小程序可以直接用wx.showLoading與wx.hideLoading,并將mask設為true,實現類似功能。
當我們只有一個耗時操作時,問題很容易,但如果是多個呢?下面便講解如何在進行多個耗時操作時,開關等待提示的問題。我們以訪問api為例,假定以下場景,訪問三個api,第一個get請求、第二個post請求、第三個故意訪問一個不存在的api。當其中遇到錯誤時,應不能影響其它api的響應。所以我們需要先分別建立一個get請求的api與post請求的api。你可以任意語言來實現接口,我這里用傳統的asp.net frameworks(visault studio 2019有asp.net core,可以跨平臺,而frameworks只能在windows部署,請注意區分) 的一般處理程序(ashx)來實現。
第一個get請求的api
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;namespace WebApi
{/// <summary>/// HellloHandler 的摘要說明/// </summary>public class HellloHandler : IHttpHandler{public void ProcessRequest(HttpContext context){context.Response.ContentType = "text/plain";if ( context.Request.QueryString["name"]!=null){context.Response.Write("你好"+ context.Request.QueryString["name"].ToString());}else{context.Response.Write("Hello World");}}public bool IsReusable{get{return false;}}}
}
第二個post請求api
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebApi
{
///
/// PostHandler 的摘要說明
///
public class PostHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context){context.Response.ContentType = "text/plain";// context.Response.Write("Hello World");if (context.Request.HttpMethod != "POST"){context.Response.Write("非法請求");return;}string name = context.Request.Form["name"];if (string.IsNullOrEmpty(name)){context.Response.Write(" hello Post");}else{context.Response.Write("post:"+name);}}public bool IsReusable{get{return false;}}
}
}
接下來,就可以實現微信小程序、安卓app的訪問了。
微信小程序,是單線程的,實現較為簡單,因為我們要訪問三個api,所以總數設為3,然后定義m=0 當wx.request complete時m+1,當m>=3時表示,所以三個api訪問結束,調用 wx.hideLoading。注意在調試階段,請勾選不校驗合法域名選項。
wxml代碼
<!--pages/index/index.wxml-->
<view class="container">
<view>{{view1}}</view>
<view>{{view2}}</view>
<view>{{view3}}</view>
</view>
js代碼,注意將接口地址替換為你自己實際的地址
Page({/*** 頁面的初始數據*/data: {view1: '',view2: '',view3: '',errors: [] // 用于存儲錯誤信息},/*** 生命周期函數--監聽頁面加載*/onLoad(options) {this.getMultipleApis();},completeCount: 3,m:0,gethelloapi() //get請求{var that=this;wx.request({url: 'http://192.168.2.102:59597/HellloHandler.ashx?name=Jim',success:function(res){var data=""if (res.statusCode==200){data=res.data}else{data="http錯誤:"+res.statusCode}console.log("get"+data)that.setData({view1:data,},)},fail:function(e){that.setData({view1:e.message})},complete:function(){that.m++;if (that.m>=that.completeCount){wx.hideLoading();}}},)},getpostoapi() //post請求{var that=this;wx.request({url: 'http://192.168.2.102:59597/PostHandler.ashx',method:"POST",header: {'content-type': 'application/x-www-form-urlencoded' // 關鍵設置},data:{name:'WangHua',},success:function(res){var data=""if (res.statusCode==200){data=res.dataconsole.log("接收"+data)}else{data="http錯誤:"+res.statusCode}that.setData({view2:data,},console.log("應答"+res.statusCode))},fail:function(e){that.setData({view2:e.message})},complete:function(){that.m++;if (that.m>=that.completeCount){wx.hideLoading();}}},)},getnoexistapi() //不存在的請求{var that=this;wx.request({url: 'http://192.168.2.102:59597/NoExistHandler.ashx?name=Lucy',success:function(res){var data=""if (res.statusCode==200){data=res.data}else{data="http錯誤:"+res.statusCode}that.setData({view3:data,},console.log("應答"+res.statusCode))},fail:function(e){that.setData({view3:e.message})},complete:function(){that.m++;if (that.m>=that.completeCount){wx.hideLoading();}}},)},// 處理多個不同類型的API請求getMultipleApis() {// 顯示加載提示wx.showLoading({title: '加載中...',mask: true});this.gethelloapi();this.getpostoapi();this.getnoexistapi();}})
接下來安卓app代碼,安卓不允許在主線程,要用分線程來調用,分線程又不能操控UI,另外分線程還有考慮共享資源的安全訪問,所以情況要比微信小程序復雜多,在編寫代碼前我們要做一些準備設置。
第一步要在AndroidManifest添加相關網絡權限。
并且因為是調試下進行,還要application節點下,設置android:usesCleartextTraffic=“true”(默認網絡訪問必須https,ftps等有ssl的接口,設置此選項后可以解除限定)。
第二步,在Build.gradle引入okthhp(用來訪問網絡)、我封裝好的WaitDialog.aar的包以及Glide (我在WaitDialog引用了Glide ,一個圖片加載庫)。
做完準備設置,就是代碼了
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="get api"android:id="@+id/BtnApi"/>
<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/TV1"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/TV2"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/TV3"/></LinearLayout>
Java代碼,這里有兩個要注意的地方,前面也提及過。一是分線程不能操作UI,要在runOnUiThread中操作,二是,分線程安全訪問共享資源的問題,我們不能直接像微信小程序一樣m++來增加“計數器”。我們可以通過AtomicInteger類實現安全訪問共享資源,實現類似m++的功能,以上兩點代碼都會體現。
package person.yin.mutiapi;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import org.json.JSONObject;import java.io.IOException;
import java.io.StringWriter;
import java.util.concurrent.atomic.AtomicInteger;import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import swaddle.yinzhenwei.waitdialog.WaitDialog;public class MainActivity extends AppCompatActivity implements View.OnClickListener{private WaitDialog waitDialog;private Button btnapi;private String TAG="APITest";private TextView tv1;private TextView tv2;private TextView tv3;private AtomicInteger requestCounter = new AtomicInteger(0);private static final int apicount=3; //最大三個api訪問@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv1=findViewById(R.id.TV1);tv2=findViewById(R.id.TV2);tv3=findViewById(R.id.TV3);findViewById(R.id.BtnApi).setOnClickListener(this);}//***************private void test(){waitDialog=new WaitDialog(this);waitDialog.setText("查詢中,稍候");waitDialog.show();waitDialog.show();requestCounter.set(0);gethelloapi("Bill",tv1);getpostapi("Bob",tv2);getnoexistapi("Liu",tv3);}private void gethelloapi(String name,TextView tv) //訪問helloapi get請求{OkHttpClient client = new OkHttpClient.Builder().build();RequestBody rbody = new FormBody.Builder().add("username", "admin").add("password", "12345").build();Request request = new Request.Builder().url("http://192.168.2.102:59597/HellloHandler.ashx?name="+name).post(rbody)
// .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36")
// .header("Accept", "text/plain") // 匹配服務器響應類型
// .header("Connection", "close") // 避免長連接問題.build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.e(TAG, "請求失敗: " + e);// e.printStackTrace();// 獲取完整的堆棧信息StringWriter sw = new StringWriter();//e.printStackTrace(new PrintWriter(sw));Log.e(TAG, "完整異常信息: " + sw.toString());runOnUiThread(() ->{tv.setText("網絡出現錯誤: " + e.getMessage());requestCounter.incrementAndGet();checkAllRequestsCompleted();});}@Overridepublic void onResponse(Call call, Response response) throws IOException {// 打印完整的響應信息用于調試Log.d(TAG, "收到響應: " + response.code());if (!response.isSuccessful()) {String errorBody = response.body() != null ? response.body().string() : "無響應體";Log.e(TAG, "HTTP錯誤 " + response.code() + ": " + errorBody);runOnUiThread(() ->{tv.setText("HTTP錯誤: " + response.code() );requestCounter.incrementAndGet();checkAllRequestsCompleted();});return;}String responseData = response.body().string();Log.d(TAG, responseData);runOnUiThread(() ->{//textViewResult.setText( responseData);tv.setText(responseData);requestCounter.incrementAndGet();checkAllRequestsCompleted();});}});}private void getnoexistapi(String name,TextView tv) //故意訪問一個不存在api{OkHttpClient client = new OkHttpClient.Builder().build();RequestBody rbody = new FormBody.Builder().add("username", "admin").add("password", "12345").build();Request request = new Request.Builder().url("http://192.168.2.102:59597/NoExistHandler.ashx?name="+name).post(rbody)
// .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36")
// .header("Accept", "text/plain") // 匹配服務器響應類型
// .header("Connection", "close") // 避免長連接問題.build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.e(TAG, "請求失敗: " + e);// e.printStackTrace();// 獲取完整的堆棧信息StringWriter sw = new StringWriter();//e.printStackTrace(new PrintWriter(sw));Log.e(TAG, "完整異常信息: " + sw.toString());runOnUiThread(() ->{tv.setText("網絡出現錯誤: " + e.getMessage());requestCounter.incrementAndGet();checkAllRequestsCompleted();});}@Overridepublic void onResponse(Call call, Response response) throws IOException {// 打印完整的響應信息用于調試Log.d(TAG, "收到響應: " + response.code());Log.v(TAG,"ok");if (!response.isSuccessful()) {String errorBody = response.body() != null ? response.body().string() : "無響應體";Log.e(TAG, "HTTP錯誤 " + response.code() + ": " + errorBody);runOnUiThread(() ->{tv.setText("HTTP錯誤: " + response.code() );requestCounter.incrementAndGet();checkAllRequestsCompleted();});return;}String responseData = response.body().string();Log.d(TAG, responseData);runOnUiThread(() ->{//textViewResult.setText( responseData);tv.setText(responseData);requestCounter.incrementAndGet();checkAllRequestsCompleted();});}});}private void getpostapi(String name, TextView tv) //訪問postapi post請求{// 創建帶調試信息的OkHttpClientOkHttpClient client = new OkHttpClient.Builder().build();RequestBody rbody = new FormBody.Builder().add("name", name).build();Request request = new Request.Builder().url("http://192.168.2.102:59597/PostHandler.ashx").post(rbody).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.e(TAG, "請求失敗: " + e);// e.printStackTrace();// 獲取完整的堆棧信息StringWriter sw = new StringWriter();//e.printStackTrace(new PrintWriter(sw));Log.e(TAG, "完整異常信息: " + sw.toString());runOnUiThread(() ->{tv.setText("網絡出現錯誤: " + e.getMessage());requestCounter.incrementAndGet();checkAllRequestsCompleted();});}@Overridepublic void onResponse(Call call, Response response) throws IOException {// 打印完整的響應信息用于調試Log.d(TAG, "收到響應: " + response.code());if (!response.isSuccessful()) {String errorBody = response.body() != null ? response.body().string() : "無響應體";Log.e(TAG, "HTTP錯誤 " + response.code() + ": " + errorBody);runOnUiThread(() ->{tv.setText("HTTP錯誤: " + response.code() );requestCounter.incrementAndGet();checkAllRequestsCompleted();});return;}String responseData = response.body().string();Log.d(TAG, responseData);runOnUiThread(() ->{tv.setText( responseData);requestCounter.incrementAndGet();checkAllRequestsCompleted();});}});}private void checkAllRequestsCompleted() {if (requestCounter.get() >= apicount) {//runOnUiThread(this::dismissLoading);//runOnUiThread(()->waitDialog.dismiss());waitDialog.dismiss();}}@Overridepublic void onClick(View v){switch (v.getId()){case R.id.BtnApi:test();break;}}
}
至此所有代碼均已完成,我打包了微信小程序和安卓app的代碼,因為api接口可以用多種語言實現,就不打包了,如果需要復制黏貼吧。代碼地址 https://download.csdn.net/download/wstcl/91725186?spm=1001.2014.3001.5503