Android技术问题:AsyncTask获取HTTP数据失败,RecyclerView关联空指针报错
先看你最直接的崩溃:NullPointerException是因为你把AtomicReference<MyCustomAdapter> adapter初始化为null,然后直接调用adapter.set(...)——空对象当然不能调用方法啦!这是最容易解决的问题,但背后还有几个更关键的概念误解需要理清,不然就算修复这个崩溃,你的App还是会有问题(比如主线程阻塞、数据加载异常)。
一、先修复直接崩溃的问题
把AtomicReference<MyCustomAdapter> adapter = null;改成:
AtomicReference<MyCustomAdapter> adapter = new AtomicReference<>();
不过说实话,这里完全没必要用AtomicReference,直接用普通的MyCustomAdapter变量就行,因为你是在主线程同步操作的,原子类是给多线程并发场景用的。
二、解决核心问题:AsyncTask的错误用法
你用task.execute(...).get()直接阻塞了主线程!这完全违背了AsyncTask设计的初衷——异步任务就是要在后台线程干活,主线程继续响应用户操作,等后台任务完成后再更新UI。get()会强制主线程等待后台任务完成,不仅可能导致ANR(应用无响应),还会让你的界面卡住。
正确的做法是重写AsyncTask的onPostExecute方法,在这个方法里处理数据并更新RecyclerView。
三、完整修正后的代码
public class MainActivity extends AppCompatActivity { private List<Message> list; private RecyclerView recyclerView; private MyCustomAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化RecyclerView相关组件 recyclerView = findViewById(R.id.recyclerView); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); recyclerView.setHasFixedSize(true); // 初始化数据列表 list = new ArrayList<>(); // 先设置空适配器,避免界面空白 adapter = new MyCustomAdapter(list); recyclerView.setAdapter(adapter); // 启动异步下载任务 DownloadTask task = new DownloadTask(); task.execute("https://newsapi.org/v2/top-headlines?country=us&apiKey=(MyApi)"); } private void representData(String myJSONDataString){ if (myJSONDataString == null || myJSONDataString.equals("Failed")) { // 数据加载失败,做提示或者其他处理 Log.e("MainActivity", "数据加载失败"); return; } JsonParser parser = new JsonParser(); JsonObject rootObj = parser.parse(myJSONDataString).getAsJsonObject(); JsonArray articlesArray = rootObj.getAsJsonArray("articles"); for (JsonElement aa : articlesArray) { JsonObject articleObj = aa.getAsJsonObject(); // 这里最好加非空判断,避免JSON字段缺失导致崩溃 String title = articleObj.has("title") ? articleObj.get("title").getAsString() : "无标题"; String description = articleObj.has("description") ? articleObj.get("description").getAsString() : "无描述"; list.add(new Message(title,description)); } // 数据更新后通知适配器刷新UI adapter.notifyDataSetChanged(); } public class DownloadTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... urls) { String result = ""; URL url; HttpURLConnection urlConnection = null; try { url = new URL(urls[0]); urlConnection = (HttpURLConnection)url.openConnection(); InputStream in = urlConnection.getInputStream(); InputStreamReader reader = new InputStreamReader(in); // 用StringBuilder拼接字符串,比直接用result+=current效率高很多 StringBuilder sb = new StringBuilder(); int data = reader.read(); while (data != -1) { char current = (char) data; sb.append(current); data = reader.read(); } return sb.toString(); } catch(Exception e) { e.printStackTrace(); return "Failed"; } finally { // 别忘了关闭连接,避免资源泄漏 if (urlConnection != null) { urlConnection.disconnect(); } } } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // 后台任务完成后,在主线程处理数据并更新UI representData(result); Log.i("result","Heh "+result); } } }
四、你需要理清的核心概念
- Android主线程不能阻塞:任何耗时操作(比如网络请求)都不能放在主线程,
AsyncTask的doInBackground是后台线程,onPostExecute是主线程,要在这两个方法之间分工。 - RecyclerView的初始化时机:应该先把RecyclerView的布局管理器、适配器都设置好,哪怕是空数据的适配器,这样界面一开始就有正确的布局,等数据加载完成后再调用
notifyDataSetChanged刷新。 - JSON数据的安全性处理:永远不要假设返回的JSON字段一定存在,要加
has()判断,避免字段缺失导致崩溃。 - 资源泄漏问题:网络连接用完一定要关闭,不然会浪费系统资源。
另外,你提到尝试过OkHttp、Retrofit但问题没解决,其实核心不是网络库的问题,而是你对Android异步操作和UI更新的逻辑理解有偏差。Retrofit本身就是异步设计的,用它的话可以避免自己写AsyncTask的繁琐,建议你后续可以深入学习Retrofit的异步回调或者Coroutine(协程),这是现在Android开发更推荐的异步方式。
内容的提问来源于stack exchange,提问作者Grigory Shimichev

