Android ListView活动在长时间重新启动后引发NullPointerException

时间:2013-05-14 17:17:49

标签: android android-listview android-arrayadapter android-lifecycle android-logcat

问题

我制作一个简单的应用程序,以JSON格式从Web服务器检索数据集,将其解析为ArrayList中包含的对象。然后以列表视图的形式输出该数组列表。它开始时工作得非常好。然后,当我离开应用程序一段时间并回到它时,我得到一个ANR并且必须强制关闭。然而,只需离开应用程序并返回它就完全没问题了。 (请参阅下面的完整LogCat)我已在下面包含了所有相关代码(删除了我的网络服务器网址)。任何帮助解决这个问题都将不胜感激。

我认为它与应用程序正在恢复onResume()onRestart()有关,所以我尝试用一​​些isEmpty()!null检查覆盖这两种方法无济于事

TL; DR:应用由于NullPointerException而无法响应,可能是因为某些生命周期事件但无法找出原因。

另一个可能的混乱是我尝试单身人士课程。 (之前从未真正使用过基于静态的实例引用)

应用程序的LogCat输出无法重新启动

此LogCat是在进入主屏幕后打开应用程序失败,打开许多其他应用程序,然后返回到我的应用程序。只需离开并返回应用程序就不会发生这种情况。

05-14 17:21:45.140: W/dalvikvm(22985): threadid=1: thread exiting with uncaught exception (group=0x4103a2a0)
05-14 17:21:45.150: E/AndroidRuntime(22985): FATAL EXCEPTION: main
05-14 17:21:45.150: E/AndroidRuntime(22985): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.liamjpeters.retrievedata/com.liamjpeters.activities.ListActivity}: java.lang.NullPointerException
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2100)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2125)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread.access$600(ActivityThread.java:140)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1227)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.os.Handler.dispatchMessage(Handler.java:99)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.os.Looper.loop(Looper.java:137)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread.main(ActivityThread.java:4898)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at java.lang.reflect.Method.invokeNative(Native Method)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at java.lang.reflect.Method.invoke(Method.java:511)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at dalvik.system.NativeStart.main(Native Method)
05-14 17:21:45.150: E/AndroidRuntime(22985): Caused by: java.lang.NullPointerException
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.widget.ArrayAdapter.getCount(ArrayAdapter.java:330)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.widget.ListView.setAdapter(ListView.java:466)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at com.liamjpeters.activities.ListActivity.onCreate(ListActivity.java:48)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.Activity.performCreate(Activity.java:5206)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1083)
05-14 17:21:45.150: E/AndroidRuntime(22985):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2064)
05-14 17:21:45.150: E/AndroidRuntime(22985):    ... 11 more

主要活动

代码的要点(它意味着什么): 只是你的沼泽标准主要活动。有一个按钮,按下时执行从Web服务器检索数据的异步任务。完成后,它会启动ListActivity活动。重要的是这里是我的Singletons实例的设置

package com.liamjpeters.activities;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;

import com.liamjpeters.content.Ticket;
import com.liamjpeters.content.Tickets;
import com.liamjpeters.getData.GetTickets;
import com.liamjpeters.retrievedata.R;

public class MainActivity extends Activity {

    Button btn;
    ProgressBar pb;
    ArrayList<Ticket> tickets = new ArrayList<Ticket>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btnConnect);
        pb = (ProgressBar) findViewById(R.id.pbconnect);
        pb.setVisibility(View.INVISIBLE);
        btn.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                getServerData();
                pb.setVisibility(View.VISIBLE);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }


    private void getServerData(){
        GetTickets gsd = new GetTickets(this);
        gsd.execute();
    }

    public void gotServerData(ArrayList<Ticket> tickets){
        pb.setVisibility(View.INVISIBLE);
        this.tickets = tickets;
        Tickets.setInstance(tickets);
        Intent intent = new Intent(this, ListActivity.class);
        startActivity(intent); 
    }

}

GetTickets.java

代码的要点(它意味着什么): 从服务器获取数据并将其解析为名为票。这就像一个魅力。

package com.liamjpeters.getData;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;

import com.liamjpeters.activities.MainActivity;
import com.liamjpeters.content.Ticket;

public class GetTickets extends AsyncTask<String, Void, String> {

    MainActivity mAct;
    ArrayList<Ticket> tickets = new ArrayList<Ticket>();

    public GetTickets(Activity activ){
        mAct = (MainActivity) activ;
    }

    protected void onPostExecute(String result) {
        mAct.gotServerData(tickets);
    }

    protected String doInBackground(String... params) {
        InputStream is = null;
        String result = "";
        // Connect to the server and obtain the data from the Server via the PHP Script.
        // This script returns the MYSQL results encoded in JSON format
        try {
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost("REMOVED BUT WORKS, TRUST ME");
            HttpResponse response = httpclient.execute(httppost);
            HttpEntity entity = response.getEntity();
            is = entity.getContent();
        } catch (Exception e) {
            Log.e("log_tag", "Error in http connection " + e.toString());
        }

        // Converts the returned HTTPEntity into a string that can be parsed
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            is.close();
            result = sb.toString();
        } catch (Exception e) {
            Log.e("log_tag", "Error converting result " + e.toString());
        }

        // Parses the JSON data
        try {
            JSONArray jArray = new JSONArray(result);
            result = "";
            for (int i = 0; i < jArray.length(); i++) {
                JSONObject json_data = jArray.getJSONObject(i);
                tickets.add(new Ticket(json_data.getInt("ticket_id"),json_data.getString("ticket_name"),json_data.getString("ticket_location"), json_data.getString("ticket_problem_area"),json_data.getString("ticket_description"),json_data.getInt("ticket_priority"),json_data.getInt("ticket_time")));
            }
        } catch (JSONException e) {
            Log.e("log_tag", "Error parsing data " + e.toString());
        }
        return null;
    }
}

Ticket.java

代码的要点(它意味着什么): 只是一个带有一些便利方法的数据对象。

package com.liamjpeters.content;

public class Ticket {

    private String name;
    private String location;
    private String problemArea;
    private String description;
    private int priority;
    private int timeInt;
    private int id;
    private String timeString;
    private boolean isNew = false;;

    public Ticket(int id,String name, String location, String problemArea, String description, int priority, int time){
        this.id = id;
        this.name = name;
        this.location = location;
        this.problemArea = problemArea;
        this.description = description;
        this.priority = priority;
        this.timeInt = time;
        if (((System.currentTimeMillis() / 1000L)-time) <84600){
            isNew = true;
        }
        timeString = new java.util.Date((long) time * 1000).toString();
    }

    public Ticket clone(){
        return new Ticket(Integer.valueOf(id), new String(name), new String(location), new String(problemArea), new String(description), Integer.valueOf(priority), Integer.valueOf(timeInt));
    }

    public int getID(){
        return id;
    }

    public String getName(){
        return name;
    }
    public String getLocation(){
        return location;
    }
    public String getProblemArea(){
        return problemArea;
    }
    public String getDescription(){
        return description;
    }
    public int getPriority(){
        return priority;
    }
    public int getTimeInt(){
        return timeInt;
    }

    public String getTimeString(){
        return timeString;
    }

    public Boolean getIsNew(){
        return isNew;
    }
}

ListActivity.java

代码的要点(它的意图): 使用我的自定义列表项显示列表视图的活动。可能这个活动的生命周期是我的问题

package com.liamjpeters.activities;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ListView;

import com.liamjpeters.content.Ticket;
import com.liamjpeters.content.TicketListAdapter;
import com.liamjpeters.content.Tickets;
import com.liamjpeters.retrievedata.R;

public class ListActivity extends Activity {

    ListView lstTest;
    TicketListAdapter listAdapter;
    ArrayList<Ticket> tickets;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tickets = Tickets.getInstance();
            //Ony of my attempts at a fix :S
        if (tickets == null || tickets.isEmpty()){
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent); 
        }
        setContentView(R.layout.listlayout);
        lstTest = (ListView)findViewById(R.id.lstText);
        listAdapter = new TicketListAdapter(ListActivity.this,R.layout.listitemrel , tickets);
        lstTest.setAdapter(listAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

Tickets.java

有助于在活动之间传递我的ArrayList的单例类。它最初只是存储了它收到的引用。然后我有一个概念,即原始的ArrayList是在一个必须在某个时候处理掉的线程上创建的,因此引用将变为null。这没有解决问题

package com.liamjpeters.content;

import java.util.ArrayList;

public class Tickets {

private static ArrayList<Ticket> instance;

    public static void setInstance(ArrayList<Ticket> instance){
        Tickets.instance = new ArrayList<Ticket>(instance.size());
        for (int i = 0; i<instance.size(); i++){
            Tickets.instance.add(instance.get(i).clone());
        }
    }

    public static ArrayList<Ticket> getInstance(){
        return instance;
    }

}

TicketListAdapter

Bog Standard ArrayAdapter。这些在线百万个例子

package com.liamjpeters.content;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.liamjpeters.retrievedata.R;

public class TicketListAdapter extends ArrayAdapter<Ticket> {

    int textViewResourceId;

    public TicketListAdapter(Context context, int textViewResourceId, List<Ticket> objects) {
        super(context, textViewResourceId, objects);
        this.textViewResourceId = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //The linear layout that the listview is contained within
        LinearLayout ticketView;

        //Get the current ticket object
        Ticket ticket = getItem(position);

        //Inflate the view
        if(convertView==null){
            ticketView = new LinearLayout(getContext());
            String inflater = Context.LAYOUT_INFLATER_SERVICE;
            LayoutInflater vi;
            vi = (LayoutInflater)getContext().getSystemService(inflater);
            vi.inflate(textViewResourceId, ticketView, true);
        }else{
            ticketView = (LinearLayout) convertView;
        }
        //Get the text boxes from the list item XML file
        TextView isNew = (TextView)ticketView.findViewById(R.id.tvIsNew);
        TextView idText =(TextView)ticketView.findViewById(R.id.tvTicketID);
        TextView nameText =(TextView)ticketView.findViewById(R.id.tvName);
        TextView locationText =(TextView)ticketView.findViewById(R.id.tvLocation);
        TextView problemText =(TextView)ticketView.findViewById(R.id.tvProblem);
        TextView dateText =(TextView)ticketView.findViewById(R.id.tvDate);
        ImageView priorityImage = (ImageView)ticketView.findViewById(R.id.ivPriority);

        //Assign the appropriate data from our alert object above
        if (ticket.getIsNew()){
            isNew.setText("NEW!");
        }
        idText.setText(""+ticket.getID());
        nameText.setText("Who: " + ticket.getName());
        locationText.setText("Where: "+ ticket.getLocation());
        problemText.setText("What: "+ticket.getProblemArea());
        dateText.setText(ticket.getTimeString());

        switch (ticket.getPriority()){
        case 0:
            priorityImage.setImageResource(R.drawable.low);
            break;
        case 1:
            priorityImage.setImageResource(R.drawable.med);
            break;
        case 2:
            priorityImage.setImageResource(R.drawable.high);
            break;
        default:
            priorityImage.setImageResource(R.drawable.low);
            break;
        }
        return ticketView;  
    }
}

所以你有。任何帮助,无论大小,都值得赞赏。看一些可以做得更好的代码然后请告诉我。我不仅要解决我的问题,还要尽可能多地学习。

谢谢:)

2 个答案:

答案 0 :(得分:1)

tickets = Tickets.getInstance();是你的罪犯。

您不应将数据存储在单例/静态变量中。当过程被杀死时,它们会被擦除。确保在内存中没有数据时保留数据并读取数据。在onSaveInstanceState启动时,也可以使用Intent和/或通过Activity发送数据。

答案 1 :(得分:1)

问题的根源在于,经过一段时间后,Android框架会终止您的申请流程。重新启动时,所有静态数据(包括单个Tickets对象)都将设置为默认值。您需要检测这种情况并将事物重新初始化为您需要运行的状态。

这样做的一个结果是,在ListActivity中,当您发现Tickets.getInstance()返回null时,您应该在调用finish() * 之后从MainActivity开始。否则,它会继续尝试初始化列表活动。

* 并立即返回,正如David Wasser在评论中指出的那样。