【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服务端,在手机注册和登录,就可以实现我们最开始所说的效果了。

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