使用Retrofit库的NullPointerException - Android

时间:2015-08-02 18:06:31

标签: java android android-asynctask nullpointerexception retrofit

(在问题末尾滚动以查看最终解决方案)

使用Retrofit Android库。我试图将POST请求推送到一个Web服务器,该服务器应该在通过POST成功调用“/ login”方法后返回3个字段。信息:

  1. 终点:http://example.us.es:3456
  2. 执行登录的服务器端方法: / login
  3. 服务器端方法的必需参数:“user”和“password”
  4. 服务器管理员允许的HTTP方法:POST
  5. 无论应用客户端输入“user”和“password”的值,Web服务器都应发送包含三个字段的单个JSONObject:

    1. ok :值为“true”或“false”的字符串(false表示凭据无效)。
    2. msg :如果出现错误(例如,凭据无效或数据库错误),会带有字符串消息
    3. 数据:此方法中包含一个名称/值对的另一个JSONObject, id_session (包含会话标识符的字符串)。其他方法包含多个名称/值对; login 方法不是这种情况。
    4. 基于这些信息,我做的第一件事就是创建一个如下所示的POJO Java方法:

      POJO方法( LoginInfo_POJO.java

      package com.example.joselopez.prueba1;
      
      import java.util.List;
      
      public class LoginInfo_POJO {
          public String _ok;
          public String _msg;
          public Dataset _dataset;
      
          class Dataset {
              String _idsession;
          }
      }
      

      我接下来要做的是创建一个包含登录方法的界面(我可以在成功登录后添加其他方法):

      接口中的API方法( IApiMethods.java

      package com.example.joselopez.prueba1;
      
      import retrofit.http.POST;
      import retrofit.http.Query;
      
      public interface IApiMethods {
      
          // Log-in method
          @FormUrlEncoded
          @POST("/login")
          LoginInfo_POJO logIn(@Query("user") String user,
                          @Query("password") String password);
      }
      

      几乎就在那里。现在我创建一个从AsyncTask扩展的类,它将在一个单独的线程中执行网络操作。该类位于“MainActivity.java”文件中。

      主要活动( MainActivity.java

      ...
      ...
      
      
      private class BackgroundTask_LogIn extends AsyncTask<Void, Void, LoginInfo_POJO> {
          RestAdapter restAdapter;
      
          @Override
          protected LoginInfo_POJO doInBackground(Void... params) {
              IApiMethods methods = restAdapter.create(IApiMethods.class);
              LoginInfo_POJO loginInfo = methods.logIn(mUser, mPassword);
              return loginInfo;
          }
      
          @Override
          protected void onPreExecute() {
              restAdapter = new RestAdapter.Builder()
                      .setEndpoint("http://example.us.es:3456")
                      .build();
          }
      
          @Override
          protected void onPostExecute(LoginInfo_POJO loginInfo_pojo) {
              tv.setText("onPostExecute()");
              tv.setText(loginInfo_pojo._ok + "\n\n");
              tv.setText(tv.getText() + loginInfo_pojo._msg + "\n\n");
              tv.setText(tv.getText() + loginInfo_pojo.data.id_sesion);
              }
          }
      }
      

      MainActivity布局包含一个TextView(在RelativeLayout中),其id为“textView”,并在代码中实例化为“tv”,您将在下面看到。 MainActivity的完整代码是:

      主要活动( MainActivity.java

      package com.example.joselopez.prueba1;
      
      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.support.v7.app.AppCompatActivity;
      import android.util.Log;
      import android.widget.TextView;
      import retrofit.RestAdapter;
      
      public class MainActivity extends AppCompatActivity {
      
      TextView tv;
      String mUser, mPassword;
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
      
          setContentView(R.layout.activity_main);
          tv = (TextView) findViewById(R.id.textView);
          mUser = "test_user";
          mPassword = "test_password";
      
          BackgroundTask_LogIn tryLogin = new BackgroundTask_LogIn();
          tryLogin.execute();
      }
      
      private class BackgroundTask_LogIn extends AsyncTask<Void, Void, LoginInfo_POJO> { ... }
      

      一切都应该有效。但事实并非如此,经过一些调试后我发现类BackgroundTask_LogIn中的onPostExecute()方法在行中停止:

      for (LoginInfo_POJO.Dataset dataset : loginInfo_pojo._dataset) {
      

      抛出的错误是:

        

      com.example.joselopez.prueba1 E / AndroidRuntime:FATAL EXCEPTION:main       显示java.lang.NullPointerException

      所以我在这一行设置一个断点并猜测是什么?我的LoginInfo_POJO实例为其内部变量保存了这些值:

      • _ok = null
      • _msg = null
      • _dataset = null

      这意味着我的变量没有从服务器响应填充,但是连接似乎成功,因为doInBackground方法完全运行并且正在调用onPostExecute。

      那你觉得怎么样?也许我没有以正确的方式执行POST请求?

      更新

      正如@Gaëtan所说,我在POJO课上犯了一个大错误;那里的局部变量名必须与生成的JSON中的那些相等。我说我期待来自JSON的字段“ok”,“msg”,“data”和“id_session”,但我的LoginInfo_POJO中的局部变量的名称为“_ok”,“_ msg”,“_ datatt”,“_ idsession” (注意领先的下划线)。从Retrofit的角度来看,这是一个巨大的错误,因此重写POJO方法可以解决这个问题。

2 个答案:

答案 0 :(得分:1)

有关如何使用Retrofit的一些信息:

  1. POJO中字段的名称必须与JSON响应中的第一个字段相匹配。在这里,您的字段的名称为_ok_msg_dataset,而JSON响应包含okmsgdata。这里有两个选项:重命名字段以匹配JSON响应,或者在每个字段上使用@SerializedName注释来提供JSON字段的名称。
  2. public class LoginInfo_POJO {
        // If you rename the fields
        public String ok;
        public String msg;
        public List<Dataset> data;
    
        // If you use annotation
        @SerializedName("ok")
        public String _ok;
        @SerializedName("msg")
        public String _msg;
        @SerializedName("data")
        public String _dataset;
    }
    
    1. 改造提供了一种不使用AsyncTask的方法。不要使用API​​方法的返回类型(此处为LoginInfo_POJO logIn(String, String),而是使用类型为Callback<LoginInfo_POJO>的最后一个参数。请求将通过改造在后台线程上执行,并且在请求完成时将在主线程上调用回调(或者如果出现错误则会失败)。
    2. 有关详细信息,请参阅{{3>},在 SYNCHRONOUS VS.异步VS.可观察的部分。

答案 1 :(得分:1)

在onPreExecute中构建适配器时,可以尝试将logLevel选项设置为RestAdapter.LogLevel.FULL,以获得有关实际进行的更多信息。

来自Square Retrofit API Declaration

  

如果您需要仔细查看请求和响应,可以使用LogLevel属性轻松地将日志记录级别添加到RestAdapter。

将logLevel设置为FULL的示例:

Employee

那就是说,你不需要AsyncTask和Retrofit。