JSON对象上的空对象引用

时间:2018-06-26 14:40:20

标签: android nullpointerexception

我正在构建一个应用程序,该应用程序可以解析来自api的JSON数据,但是由于某些原因,它一直崩溃。我收到由以下原因引起的错误:java.lang.NullPointerException:尝试在null对象引用上调用虚拟方法'int java.lang.String.length()',它指向我定义JSON对象的区域在MainActivity中:

//Create the network request to download the JSON data from the url database.
        URL moviesUrl = NetworkUtils.buildUrl(SEARCH_TERM);
        try {
            //The response we get is in the form of JSON.
            String jsonMoviesResponse = NetworkUtils.getReponseFromHttpUrl(moviesUrl);

            //A new JSON object created from the JSON response.
            JSONObject moviesJson = new JSONObject(jsonMoviesResponse);

            //Read the movie results array from the JSON object.
            JSONArray moviesArray = moviesJson.getJSONArray(MOVIES_RESULTS);

            //A loop is created to read the array and add the data we need to a list.
            for (int i = 0; i < moviesArray.length(); i++) {

错误消息:

Process: com.example.thodzic.movies, PID: 8776
java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:353)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
    at java.util.concurrent.FutureTask.run(FutureTask.java:271)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    at java.lang.Thread.run(Thread.java:764)
 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
    at org.json.JSONTokener.nextCleanInternal(JSONTokener.java:116)
    at org.json.JSONTokener.nextValue(JSONTokener.java:94)
    at org.json.JSONObject.<init>(JSONObject.java:159)
    at org.json.JSONObject.<init>(JSONObject.java:176)
    at com.example.thodzic.movies.MainActivity$FetchMovieTask.doInBackground(MainActivity.java:115)
    at com.example.thodzic.movies.MainActivity$FetchMovieTask.doInBackground(MainActivity.java:96)
    at android.os.AsyncTask$2.call(AsyncTask.java:333)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
    at java.lang.Thread.run(Thread.java:764) 
06-26 08:22:30.796 8776-8818/com.example.thodzic.movies D/EGL_emulation: eglMakeCurrent: 0xa8b05120: ver 3 0 (tinfo 0xa8b03380)

URL已成功构建并返回一堆JSON,但该错误使它似乎没有返回JSON。仓库可以在这里找到 https://github.com/tarik3001/Movies 这是我的应用程序的完整代码:

MainActivity:

public class MainActivity extends AppCompatActivity {

public static final String TAG = "MainActivity";

//Declare some of the objects we are using.
String SEARCH_TERM = "popularity.desc";
private RecyclerView mRecyclerView;

//GridLayoutManage will allow us to load our items in a grid.
private GridLayoutManager gridLayoutManager;

//Custoj Adapter lets us bind out data from the web server with our recylerview
private MovieAdapter mMovieAdapter;

//Need a list to store the data from the server.
private List<Movie> movieData;

/*
API KEY
https://api.themoviedb.org/3/movie/550?api_key=1f5029b7d824dee72f4d4a156dac90ed
 */

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //Reference the RecyclerView
    mRecyclerView = findViewById(R.id.recycler_view);

    //Reference the list.  This needs to be done before setting the adapter to the recycler
    //view or the app will think there is an empty list.
    movieData = new ArrayList<>();

    //To update the list with items, we create a new method to do that.
    loadMovieData();

    //Create a new grid layout manager in order to display data to a grid.
    gridLayoutManager = new GridLayoutManager(this, 3);
    mRecyclerView.setLayoutManager(gridLayoutManager);

    //Bind the data we receive from the web server to the recyclerview itself.
    mMovieAdapter = new MovieAdapter(this, movieData);

    //Apply the adapter to the recyclerview.
    mRecyclerView.setAdapter(mMovieAdapter);

}

//Inflate the menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_menu, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.sort_by_most_popular:
            SEARCH_TERM = "popularity.desc";
    }
    return super.onOptionsItemSelected(item);
}

//Tell the new method to get the dat abased on the search term within the url.
private void loadMovieData() {
    new FetchMovieTask().execute(SEARCH_TERM);
}

//We need to use an AsyncTask to perform the request to get the data.  The first argument
//we use a String because this will allow us to pass the url.
public class FetchMovieTask extends AsyncTask<String, Void, Void> {

    @Override
    protected Void doInBackground(String... strings) {

        final String MOVIES_RESULTS = "results";
        final String MOVIES_POSTER_IMAGE = "poster_path";
        final String MOVIES_TITLE = "title";
        final String RELEASE_DATE = "release_date";
        final String VOTE_AVERAGE = "vote_average";
        final String PLOT = "overview";

        //Create the network request to download the JSON data from the url database.
        URL moviesUrl = NetworkUtils.buildUrl(SEARCH_TERM);
        try {
            //The response we get is in the form of JSON.
            String jsonMoviesResponse = NetworkUtils.getReponseFromHttpUrl(moviesUrl);

            //A new JSON object created from the JSON response.
            JSONObject moviesJson = new JSONObject(jsonMoviesResponse);

            //Read the movie results array from the JSON object.
            JSONArray moviesArray = moviesJson.getJSONArray(MOVIES_RESULTS);

            //A loop is created to read the array and add the data we need to a list.
            for (int i = 0; i < moviesArray.length(); i++) {

                String moviePoster;
                String movieTitle;
                String movieReleaseDate;
                String voteAverage;
                String plot;
                JSONObject movie = moviesArray.getJSONObject(i);

                moviePoster = ("http://image.tmdb.org/t/p/w185/" + movie.getString(MOVIES_POSTER_IMAGE));
                movieTitle = movie.getString(MOVIES_TITLE);
                movieReleaseDate = movie.getString(RELEASE_DATE);
                voteAverage = movie.getString(VOTE_AVERAGE);
                plot = movie.getString(PLOT);

                Movie data = new Movie(movieTitle, movieReleaseDate, moviePoster, voteAverage, plot);

                //Add the data items to our movieData list.
                movieData.add(data);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //This is called when the network request is done.  We use this method to tell our
    //custom adapter that there is a change in the data list so that it can load new cardview
    //widgets in the list.
    @Override
    protected void onPostExecute(Void aVoid) {
        mMovieAdapter.notifyDataSetChanged();
    }
}
};

MovieAdapter:

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MovieAdapterViewHolder>{

//Define two fields which are referenced from the main activity.
private List<Movie> mMovieData;
private Context context;

//Create the constructor for the class
public MovieAdapter(Context context, List<Movie> movieData) {
    this.context = context;
    this.mMovieData = movieData;

}

//onBindViewHolder is method where we can manipulate the views.
@Override
public void onBindViewHolder(@NonNull MovieAdapterViewHolder holder, final int position) {
    holder.mTitleTextView.setText(mMovieData.get(position).getTitle());
    holder.mReleaseDateTextView.setText(mMovieData.get(position).getDate());
    //Use Picasso library to quickly convert image url to image.
    Picasso.with(context).load(mMovieData.get(position).getMoviePoster()).into(holder.mImageView);

    //Create the new onclick listener to handle click events.
    holder.mCardView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            //Une intent to tell the app where to navigate too once clicking on item.
            Intent intent = new Intent(context, DetailsActivity.class);
            //Attach extras to the intent because we needDetailsActivity to know which
            //item we clicked on.
            intent.putExtra("movie_title", mMovieData.get(position).getTitle());
            intent.putExtra("poster", mMovieData.get(position).getMoviePoster());
            intent.putExtra("release_date", mMovieData.get(position).getDate());
            intent.putExtra("vote_average", mMovieData.get(position).getVoteAverage());
            intent.putExtra("plot", mMovieData.get(position).getPlot());
            //Start the activity and send it over. We need to use context here because
            //we are in an adapter class and it doesn't know what the context is.
            context.startActivity(intent);
        }
    });
}

//The onCreateViewHolder class is used in order to reference the cardview layout inflater to
//inflate the layout.
@NonNull
@Override
public MovieAdapterViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    //Create a new view and use the layout inflater to
    Context context = parent.getContext();
    View view = LayoutInflater.from(context).inflate(R.layout.cardview_item, parent, false);
    //We need to return a new instance of the viewholder with the referenced view.
    return new MovieAdapterViewHolder(view);
}

//One of the methods we must override for the custom adapter class.  Need to return the size
//of the list that we provide.
@Override
public int getItemCount() {
    if (null == mMovieData)
        return 0;
    return mMovieData.size();
}

//Create the MovieAdapterViewHolder class from where we can update the cardview with data.
public class MovieAdapterViewHolder extends RecyclerView.ViewHolder {

    //Our cardview has two text views and an image view.
    public final TextView mTitleTextView;
    public final TextView mReleaseDateTextView;
    public final ImageView mImageView;
    public final CardView mCardView;

    public MovieAdapterViewHolder(View itemView) {
        super(itemView);

        mTitleTextView = itemView.findViewById(R.id.title_text_view);
        mReleaseDateTextView = itemView.findViewById(R.id.release_date_text_view);
        mImageView = itemView.findViewById(R.id.image_data);
        mCardView = itemView.findViewById(R.id.card_view);
    }
}

NetworkUtils

public class NetworkUtils {

//These utilities will be used to communicate with the servers.
private static final String TAG = NetworkUtils.class.getSimpleName();

private static final String BASE_URL = "https://api.themoviedb.org/3/discover/movie/";
private static final String API_KEY = "1f5029b7d824dee72f4d4a156dac90ed";

//This builds the URL used to talk to movie database.
public static URL buildUrl(String SEARCH_TERM) {
    Uri builtUri = Uri.parse(BASE_URL).buildUpon()
            .appendQueryParameter("api_key",API_KEY)
            .appendQueryParameter("sort_by", SEARCH_TERM)
            .build();

    URL url = null;
    try {
        url = new URL(builtUri.toString());
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }

    Log.v(TAG, "Built URL " + url);

    return url;
}

//This method returns the entire result from the HTTP response.
public static String getReponseFromHttpUrl(URL url) throws IOException {
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
    try {
        InputStream inputStream = urlConnection.getInputStream();

        Scanner scanner = new Scanner(inputStream);
        scanner.useDelimiter("\\A");

        boolean hasInput = scanner.hasNext();
        if (hasInput) {
            return scanner.next();
        } else {
            return null;
        }
    } finally {
        urlConnection.disconnect();

    }
}
}

数据类

public class Movie {

//Create the fields we need for this class
private String Title;
private String Date;
private String MoviePoster;
private String VoteAverage;
private String Plot;

//Generate the class constructor (Command + N shortcut)
public Movie(String title, String date, String moviePoster, String voteAverage, String plot) {
    Title = title;
    Date = date;
    MoviePoster = moviePoster;
    VoteAverage = voteAverage;
    Plot = plot;
}

//Getters allow us to get the information we want from the list.
public String getTitle() {
    return Title;
}

public void setTitle(String title) {
    Title = title;
}

public String getDate() {
    return Date;
}

//Setters allow us to set the information from the list into the item
public void setDate(String date) {
    Date = date;
}

public String getVoteAverage() {
    return VoteAverage;
}

public void setVoteAverage(String voteAverage) {
    VoteAverage = voteAverage;
}

public String getPlot() {
    return Plot;
}

public void setPlot(String plot) {
    Plot = plot;
}

public String getMoviePoster() {
    return MoviePoster;
}

public void setMoviePoster(String moviePoster) {
    MoviePoster = moviePoster;
}
}

1 个答案:

答案 0 :(得分:1)

如果您在NetworkUtils中查看方法 getReponseFromHttpUrl ,则会返回null:

if (hasInput) {
        return scanner.next();
    } else {
        return null;
    }

这可能是空指针异常的原因。