【Android 安卓】網絡編程demo(二) Android客戶端

彙總:本系列的彙總

Android Studio配置

  1. 新建Android Studio工程。

  2. 最重要的一步:添加網絡權限!添加網絡權限!!。還記得當時debug的時候,後面的我都寫好了…然後因爲沒有添加網絡權限怎麼都是錯…說起來都是淚…
    在AndoidManifest中添加如下代碼:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
  3. 添加下面的依賴,點擊sync now。okhttp用於發送http請求,fastjson用於解析JSON串和對象的轉化。

    	implementation 'com.squareup.okhttp:okhttp:2.4.0'
        implementation 'com.alibaba:fastjson:1.2.62'
    

前期準備

  1. 新建RegisterActivity、LoginActivity、SuccessActivity和其對應的xml,分別用於註冊、登錄、登錄成功之後跳轉的頁面。自行發揮吧,我這因爲登錄和註冊界面xml基本一樣就只貼activity_register.xml和activity_success.xml了。非常非常簡單的xml。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:orientation="vertical"
        tools:context=".RegisterActivity">
    
        <EditText
            android:id="@+id/reg_uid"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:hint="請設置賬號" />
    
        <EditText
            android:id="@+id/reg_password"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:hint="請設置密碼" />
    
        <Button
            android:id="@+id/reg_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="注 冊"
            android:textSize="20dp" />
    
    </LinearLayout>
    
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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"
        tools:context=".SuccessActivity">
    
        <TextView
            android:id="@+id/welcome"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Congratulations!"
            android:textSize="30sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <TextView
            android:id="@+id/userId"
            android:layout_width="wrap_content"
            android:layout_height="28sp"
            android:text="UserId"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@+id/welcome"
            app:layout_constraintStart_toStartOf="@+id/welcome"
            app:layout_constraintTop_toBottomOf="@+id/welcome"
            app:layout_constraintVertical_bias="0.05" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. 添加Entity包,在其中加入User類。因爲我們在服務端loginCheck時會返回一個User對象,我們客戶端也需要一個User對象來容納返回的數據。
    下面是我編寫的User類,我特意將User類編寫得與服務端不一樣:服務端沒有age屬性。但是這並不會影響其對服務器對象的接收,因爲服務端的User裏面也有userId和password。

    public class User {
    
        String userId;          //id
        String password;        //密碼
        String age;             //age 故意多的一個和服務器不同的變量
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getAge() {
            return age;
        }
    
        public void setAge(String age) {
            this.age = age;
        }
    
    }
    
  3. (非必需)這裏面我們可以加入一個小工具,檢測網絡是否開啓,以改善用戶體驗。在uitl包下加入NetStateUtil:

    public class NetStateUtil {
    
        static String NETWORK_STATE_ERROR = "網絡連接錯誤";
        public static boolean checkNetworkState(Context context){
    
            ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if(networkInfo == null){        //網絡連接異常
                Toast.makeText(context,NETWORK_STATE_ERROR,Toast.LENGTH_SHORT).show();
                return false;
            }
            return true;
        }
    }
    

發送網絡請求

  1. 以RegisterActivity爲例,我們設置在點擊“登錄”之後發起http請求,將userId和password送去服務器判斷是否能夠註冊。點擊事件和網絡相關的代碼如下:

    //註冊對應的點擊事件
       registerButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
    
               //1. 獲取控件中所填寫的值, 注意一定要寫在listener內!
               String userId = uidText.getText().toString();
               String password = passwordText.getText().toString();
    
               //2. 網絡爲連接則直接返回
               if(!NetStateUtil.checkNetworkState(RegisterActivity.this))
                   return;
                   
               //3. 發起http請求
               OkHttpClient okHttpClient = new OkHttpClient();     //創建OkHttpClient實例
               
               FormEncodingBuilder builder = new FormEncodingBuilder();
               RequestBody requestBody = builder.add("userId", userId)
                       .add("password",password)
                       .build();                                          //需要傳輸的數據存入requestBody
                       
               Request request = new Request.Builder()
                       .url(registerUrl)							 //需要的url
                       .post(requestBody)                      //註冊時需要向服務端上傳數據,所以使用post
                       .build();
    
               Call call = okHttpClient.newCall(request);      
               call.enqueue(new Callback() {						//開啓異步進程
                   @Override
                   public void onFailure(Request request, IOException e) {
                       runOnUiThread(new Runnable() {
                           @Override
                           public void run() {
                               //連接超時時的提醒
                               Toast.makeText(RegisterActivity.this,"Fail",Toast.LENGTH_SHORT).show();
                           }
                       });
                   }
    
                   @Override
                   public void onResponse(Response response) throws IOException {
                   	//成功獲取響應時
                   		//不要toString  
                       String responseData = response.body().string();        
                        //解析,responseData,獲取響應數據
                       final boolean data = JSON.parseObject(responseData,boolean.class);      //將所接受的json數據轉換成boolean對象
    
                       runOnUiThread(new Runnable() {
                           @Override
                           public void run() {
                               //顯示回調函數
                               Toast.makeText(RegisterActivity.this,"data"+data,Toast.LENGTH_SHORT).show();
                               if(data == true)
                                   LoginActivity.actionStart(RegisterActivity.this);
                               else
                                   Toast.makeText(RegisterActivity.this,"賬號已被註冊",Toast.LENGTH_SHORT).show();
                           }
                       });
                   }
               });
           }
       });
    

    其中三個主要的邏輯我們已經寫在註釋中了,這裏就直接解析一下發送http請求時的操作。
    大致分爲這幾個步驟:

    • 創建OkHttpClient對象。
    • 將所需要向服務器傳輸的數據放入requestBody。
    • 使用requestBody和要訪問的Url創建request對象。
    • 開啓異步進程進行網絡請求。

    步驟很簡單,也很固定。需要注意的幾個地方:

    1 關於requestBody:.add(“userId”,userId)其實就是在需要傳輸的JSON數據中加入了一個鍵值對。和服務端這裏相對應:
    在這裏插入圖片描述
    2 關於Request對象: 不需要向服務端傳requestBody的時候,.post那部分不要。
    3 關於異步請求:

    1. 網絡連接屬於耗時操作,直接在UI線程操作會阻塞、閃退。使用上面的寫法其實是開啓了一個異步進程,不會卡。
    2. 在異步進程中進行UI操作時需要用runOnUiThread進行UI操作,否則會閃退。當然也還有使用Handler的方法,這裏我們暫時留個坑。
    3. 其實okhttp裏面也有同步方式的寫法(excute)。但是我看有人說已經棄用了,這裏就不說了。

    4 關於JSON解析:獲取了responseData(String類型)之後,我們可以將其解析爲Java對象,上面是解析爲簡單對象,我們也可以解析爲自定義對象、List。例子:

    	boolean data = JSON.parseObject(responseData,boolean.class)	    //簡單
    	User user = JSON.parseObject(responseData, User.class)			//自定義
    	List<User> users = JSON.parseArray(responseData, User.class)	//Array
    

    5 非常重要的一點是,response.body().string()不要寫成toString(),特別細小的bug,犯了涼涼。

  2. LoginActivity的編寫。實際上和RegisterActivity基本一模一樣,該注意的地方也說了,就不貼代碼了。主要是onResponse裏面有點不一樣,需要解析對象。

    @Override
          public void onResponse(Response response) throws IOException {
              final String responseData = response.body().string();                         //響應數據
              final User user = JSON.parseObject(responseData,User.class);      //將所接受的json數據轉換成boolean對象
    
              runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                      //顯示回調函數
                      if(user!=null)      //找到這個user的時候頁面跳轉
                          SuccessActivity.actionStart(LoginActivity.this,user.getUserId());
                      else                //錯誤提示
                          Toast.makeText(LoginActivity.this,"賬號或密碼錯誤",Toast.LENGTH_SHORT).show();
                  }
              });
          }
    
  3. SuccessActivity:與網絡通信沒什麼關係了,主要是模擬登錄成功之後的頁面:

    public class SuccessActivity extends AppCompatActivity {
    
        String uid;
    
        TextView uidText;
    
        /**
         * 啓動本activity
         * @param context   上一個activity的運行環境
         * @param uid   用戶id,頁面間傳遞數據的用
         */
        public static void actionStart(Context context,String uid){
            Intent intent = new Intent(context,SuccessActivity.class);
            intent.putExtra("uid",uid);
            context.startActivity(intent);
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_success);
    
            //獲取數據
            Intent intent = getIntent();
            uid = intent.getStringExtra("userId");
    
            //跳轉到本頁面之後在上面顯示user的id
            uidText = findViewById(R.id.userId);
            uidText.setText(uid);
        }
    }
    
    

    安卓客戶端目前已經編寫完畢了,可以完成我們一開始展示的效果了。但是我們注意到,其實每一次網絡請求步驟都是一樣的,不一樣的只有url和requestBody,故我們何不將通用的網絡操作提取到一個類中,每次需要的時候調用那個類就好了。

將重複代碼提取爲HttpPostUtil類

直接貼代碼吧,比較簡單。放在util包下:

public class HttpPostUtil {
    /**
     * 發起post的http請求
   * @param address url地址
     * @param requestBody   需要傳輸的數據
     * @param callback  回調接口,請求的最終結果會回調到其中
     */
    public static void sendOkHttpRequest(String address, RequestBody requestBody, Callback callback){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(address)
                .post(requestBody)
                .build();
        client.newCall(request).enqueue(callback);
    }
}

以RegisterActivity爲例,我們可以在其中將網絡通信相關的代碼改寫爲:

	 FormEncodingBuilder builder = new FormEncodingBuilder();
     RequestBody requestBody = builder.add("userId", userId)
             .add("password",password)
             .build();                                   //需要傳輸的數據存入requestBody
     HttpPostUtil.sendOkHttpRequest(loginUrl, requestBody, new Callback() {
         
         @Override
         public void onFailure(Request request, IOException e) {
             ...
         }

         @Override
         public void onResponse(Response response) throws IOException {
             ...
         }
     });

這樣就會少點代碼了。
好啦,客戶端也編寫完成了。在你的電腦上運行Springboot服務端,在手機註冊和登錄,就可以實現我們最開始所說的效果了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章