【问题标题】:NullPointerException using Retrofit library - AndroidNullPointerException 使用改造库 - Android
【发布时间】:2015-10-24 19:58:54
【问题描述】:

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

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

  1. 端点:http://example.us.es:3456
  2. 执行登录的服务器端方法:/login
  3. 服务器端方法的必需参数:“用户”和“密码”
  4. 服务器管理员允许的 HTTP 方法:POST

无论应用客户端为“用户”和“密码”输入什么值,Web 服务器都应该发送一个包含三个字段的 JSONObject:

  1. ok:值为“true”或“false”的字符串(false 表示凭据无效)。
  2. msg:如果出现错误(例如,无效凭据或数据库错误),将携带字符串消息
  3. data:另一个 JSONObject,在此方法中包含一个名称/值对,id_session(包含会话标识符的字符串)。其他方法包含几个名称/值对; login 方法并非如此。

根据这些信息,我做的第一件事是创建一个 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 布局包含一个 ID 为“textView”的 TextView(在 RelativeLayout 内),并在代码中实例化为“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”、“_dataset”、“_idsession” (注意前面的下划线)。从 Retrofit 的角度来看,这是一个巨大的错误,因此重写 POJO 方法来解决这个问题最终会解决问题。

【问题讨论】:

    标签: java android android-asynctask nullpointerexception retrofit


    【解决方案1】:

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

    1. POJO 中的字段名称必须与 JSON 响应中的第一个匹配。在这里,您的字段名为 _ok_msg_dataset,而 JSON 响应包含 okmsgdata。您有两个选择:重命名字段以匹配 JSON 响应,或在每个字段上使用 @SerializedName 注释来指定 JSON 字段的名称。
    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&lt;LoginInfo_POJO&gt; 类型的最后一个参数。请求将通过改造在后台线程上执行,并在请求完成时在主线程上调用回调(如果出错则失败)。

    SYNCHRONOUS VS 中查看documentation 了解更多信息。异步VS。可观察的部分。

    【讨论】:

    • 您的建议 #1 解决了我的问题,但现在我得到了另一个问题:“”原因:retrofit.RetrofitError: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY但是 BEGIN_OBJECT "" 有什么建议吗?
    • 您的 POJO 与 JSON 响应不匹配。它期待一个数组,但得到一个对象。可能是data 周围的问题?
    • 是的,这是data 周围的问题。我期待这样的事情:{"msg":"XXXX","data":{"id_sesion":"XXX"},"ok":XXX},我怎样才能告诉 Retrofit 从这个语法中读取(即期望一个 JSONObject)?再次感谢。
    • 从您的示例中,dataDataset,而不是 List&lt;Dataset&gt;
    • 天哪,真是个错误。非常感谢 Gaëtan,救星! :)
    【解决方案2】:

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

    来自Square Retrofit API Declaration

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

    logLevel 设置为 FULL 的示例:

        RestAdapter restAdapter = new RestAdapter.Builder()
    .setLogLevel(RestAdapter.LogLevel.FULL)
    .setEndpoint("https://api.github.com")
    .build();
    

    也就是说,你确实不需要带 Retrofit 的 AsyncTask。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-02
      • 1970-01-01
      • 1970-01-01
      • 2016-08-11
      • 2014-12-17
      • 1970-01-01
      • 2016-08-22
      • 2019-04-25
      相关资源
      最近更新 更多