活动恢复时 ExpandableListAdapter getChildrenCount 空指针异常

Posted

技术标签:

【中文标题】活动恢复时 ExpandableListAdapter getChildrenCount 空指针异常【英文标题】:ExpandableListAdapter getChildrenCount null pointer exception when activity restored 【发布时间】:2015-01-31 10:06:12 【问题描述】:

这个例外让我抓狂。每当我的主要活动的进程被杀死然后恢复我的应用程序强制关闭由于我的 expandableListView 中的空指针异常。这是我的第一个 android 应用程序,也是第一次编写 java,所以我有点 n00b。它是一个时间表应用程序,它使用光标加载器将数据从内容提供者加载到列表中。我在这里做错了什么?提前致谢。

Logcat:

12-03 00:21:42.235: E/AndroidRuntime(2341): FATAL EXCEPTION: main
12-03 00:21:42.235: E/AndroidRuntime(2341): Process: com.crevitus.timetable, PID: 2341
12-03 00:21:42.235: E/AndroidRuntime(2341): java.lang.RuntimeException: Unable to start activity ComponentInfocom.crevitus.timetable/com.crevitus.timetable.MainActivity: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread.access$800(ActivityThread.java:144)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.os.Handler.dispatchMessage(Handler.java:102)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.os.Looper.loop(Looper.java:135)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread.main(ActivityThread.java:5221)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at java.lang.reflect.Method.invoke(Native Method)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at java.lang.reflect.Method.invoke(Method.java:372)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
12-03 00:21:42.235: E/AndroidRuntime(2341): Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
12-03 00:21:42.235: E/AndroidRuntime(2341):     at com.crevitus.timetable.adapter.ExpandableListAdapter.getChildrenCount(ExpandableListAdapter.java:67)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:563)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.widget.ExpandableListConnector.setExpandedGroupMetadataList(ExpandableListConnector.java:758)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.widget.ExpandableListView.onRestoreInstanceState(ExpandableListView.java:1340)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.view.View.dispatchRestoreInstanceState(View.java:13621)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2907)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:795)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2893)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.view.View.restoreHierarchyState(View.java:13599)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:1980)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.Activity.onRestoreInstanceState(Activity.java:1022)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.Activity.performRestoreInstanceState(Activity.java:977)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1161)
12-03 00:21:42.235: E/AndroidRuntime(2341):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2271)
12-03 00:21:42.235: E/AndroidRuntime(2341):     ... 10 more

MainActivity 代码:

package com.crevitus.timetable;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

import android.app.AlertDialog;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import android.widget.Toast;

import com.crevitus.timetable.adapter.ExpandableListAdapter;
import com.crevitus.timetable.provider.TimetableContentProvider;
import com.crevitus.timetable.service.ReminderHandler;

public class MainActivity extends BaseActivity implements
    LoaderManager.LoaderCallbacks<Cursor> 

    private ExpandableListAdapter listAdapter;
    private ExpandableListView expListView;
    private List<String> listDataHeader;
    private HashMap<String, List<List<String>>> listDataChild;
    private int lastExpandedPosition = -1;
    private int gPosition, cPosition;
    private static final int MONDAY = 0, TUESDAY = 1, WEDNESDAY = 2, THURSDAY = 3, FRIDAY = 4;

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

        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

        //get data from content provider
        getLoaderManager().initLoader(MONDAY, null, this);
        getLoaderManager().initLoader(TUESDAY, null, this);
        getLoaderManager().initLoader(WEDNESDAY, null, this);
        getLoaderManager().initLoader(THURSDAY, null, this);
        getLoaderManager().initLoader(FRIDAY, null, this);

        // get the listview
        expListView = (ExpandableListView) findViewById(R.id.lvExp);
        expListView.setLongClickable(true);

        //initialise data collections
        listDataHeader = new ArrayList<String>();
        listDataChild = new HashMap<String, List<List<String>>>();

        // Add header data
        listDataHeader.add("Monday");
        listDataHeader.add("Tuesday");
        listDataHeader.add("Wednesday");
        listDataHeader.add("Thursday");
        listDataHeader.add("Friday");

        //get adapter
        listAdapter = new ExpandableListAdapter(this, listDataHeader, listDataChild);   

        // set list adapter
        expListView.setAdapter(listAdapter);

        // Listview on child click listener
        expListView.setOnChildClickListener(new OnChildClickListener() 

            @Override
            public boolean onChildClick(ExpandableListView parent, View v,
                    int groupPosition, int childPosition, long id) 
                //get data by position
                String group = (String) listAdapter.getGroup(groupPosition);
                List<String> childList = listDataChild.get(group).get(childPosition);

                //launch view activity and pass data
                Intent view = new Intent(getApplicationContext(), ViewActivity.class);
                Bundle bundle = new Bundle();
                bundle.putStringArray("Class", new String[] childList.get(0), childList.get(1), childList.get(2), childList.get(3), childList.get(4), childList.get(5));
                view.putExtras(bundle);
                startActivity(view);
                return false;
            
        );

        expListView.setOnItemLongClickListener(new OnItemLongClickListener() 

            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) 
                if (ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_CHILD) 
                 
                    int groupPosition = ExpandableListView.getPackedPositionGroup(id);
                    int childPosition = ExpandableListView.getPackedPositionChild(id);
                    longClick( expListView, groupPosition, childPosition);

                    return true;
                
                return false;
            

            private void longClick(ExpandableListView expListView, int groupPosition, int childPosition) 
                gPosition = groupPosition;
                cPosition = childPosition;
                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
                // set title
                alertDialogBuilder.setTitle("Delete Class?");

                // set dialog message
                alertDialogBuilder
                    .setMessage("Are you sure you want to delete the class?")
                    .setCancelable(false)
                    .setPositiveButton("Yes",new DialogInterface.OnClickListener() 
                        public void onClick(DialogInterface dialog,int id) 
                            String group = (String) listAdapter.getGroup(gPosition);
                            List<String> childList = listDataChild.get(group).get(cPosition);
                            ContentResolver cr = getContentResolver();
                            cr.delete(TimetableContentProvider.CONTENT_URI, TimetableContentProvider.KEY_ID + " = ?", new String[] childList.get(0));
                            listAdapter.deleteChild(gPosition, cPosition);
                            listAdapter.notifyDataSetChanged();
                            ReminderHandler.cancelAlarm(Integer.parseInt(childList.get(0)), getApplicationContext());
                            Toast.makeText(getApplicationContext(), "Class Deleted", Toast.LENGTH_SHORT).show();
                        
                      )
                    .setNegativeButton("No",new DialogInterface.OnClickListener() 
                        public void onClick(DialogInterface dialog,int id) 
                            // if this button is clicked, just close
                            // the dialog box and do nothing
                            dialog.cancel();
                        
                    );

                    // create alert dialog
                    AlertDialog alertDialog = alertDialogBuilder.create();

                    // show it
                    alertDialog.show();
                    
                );//end longclick listener


        expListView.setOnGroupExpandListener(new OnGroupExpandListener() 
            @Override
            public void onGroupExpand(int groupPosition) 
                    if (lastExpandedPosition != -1 && groupPosition != lastExpandedPosition) 
                    
                        expListView.collapseGroup(lastExpandedPosition);
                    
                    lastExpandedPosition = groupPosition;
            
        );

    
    @Override
    public void onResume()
    
        super.onResume();
        getLoaderManager().restartLoader(MONDAY, null, this);
        getLoaderManager().restartLoader(TUESDAY, null, this);
        getLoaderManager().restartLoader(WEDNESDAY, null, this);
        getLoaderManager().restartLoader(THURSDAY, null, this);
        getLoaderManager().restartLoader(FRIDAY, null, this);
    

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) 
        CursorLoader loader = null;
        switch(id)
        
            case MONDAY:
                loader = new CursorLoader(this,
                        TimetableContentProvider.CONTENT_URI,
                        null,
                        TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] "Monday",
                        null);
                break;
            case TUESDAY:
                loader = new CursorLoader(this,
                        TimetableContentProvider.CONTENT_URI,
                        null,
                        TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] "Tuesday",
                        null);              
                break;
            case WEDNESDAY:
                loader = new CursorLoader(this,
                        TimetableContentProvider.CONTENT_URI,
                        null,
                        TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] "Wednesday",
                        null);              
                break;
            case THURSDAY:
                loader = new CursorLoader(this,
                        TimetableContentProvider.CONTENT_URI,
                        null,
                        TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] "Thursday",
                        null);              
                break;
            case FRIDAY:
                loader = new CursorLoader(this,
                        TimetableContentProvider.CONTENT_URI,
                        null,
                        TimetableContentProvider.KEY_DAY_COLUMN + "=?", new String[] "Friday",
                        null);              
                break;  
        
        return loader;
    

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) 

        List<String> splitMon;
        List<String> splitTues;
        List<String> splitWed;
        List<String> splitThurs;
        List<String> splitFri;

        List<List<String>> monday = new ArrayList<List<String>>();
        List<List<String>> tuesday = new ArrayList<List<String>>();
        List<List<String>> wednesday = new ArrayList<List<String>>();
        List<List<String>> thursday = new ArrayList<List<String>>();
        List<List<String>> friday = new ArrayList<List<String>>();

        switch(loader.getId())
        
            case MONDAY:
                while(cursor.moveToNext())
                
                    splitMon = new ArrayList<String>();
                    splitMon.add(cursor.getString(0));
                    splitMon.add(cursor.getString(1));
                    splitMon.add(cursor.getString(2));
                    splitMon.add(cursor.getString(3));
                    splitMon.add(cursor.getString(4));
                    splitMon.add(cursor.getString(5));
                    monday.add(splitMon);
                
                listDataChild.put(listDataHeader.get(0), monday);
                break;
            case TUESDAY:
                while(cursor.moveToNext())
                
                    splitTues = new ArrayList<String>();
                    splitTues.add(cursor.getString(0));
                    splitTues.add(cursor.getString(1));
                    splitTues.add(cursor.getString(2));
                    splitTues.add(cursor.getString(3));
                    splitTues.add(cursor.getString(4));
                    splitTues.add(cursor.getString(5));
                    tuesday.add(splitTues);
                
                listDataChild.put(listDataHeader.get(1), tuesday);
                break;
            case WEDNESDAY:
                 while(cursor.moveToNext())
                    
                        splitWed = new ArrayList<String>();
                        splitWed.add(cursor.getString(0));
                        splitWed.add(cursor.getString(1));
                        splitWed.add(cursor.getString(2));
                        splitWed.add(cursor.getString(3));
                        splitWed.add(cursor.getString(4));
                        splitWed.add(cursor.getString(5));
                        wednesday.add(splitWed);
                    
                 listDataChild.put(listDataHeader.get(2), wednesday);
                break;
            case THURSDAY:
                while(cursor.moveToNext())
                
                    splitThurs = new ArrayList<String>();
                    splitThurs.add(cursor.getString(0));
                    splitThurs.add(cursor.getString(1));
                    splitThurs.add(cursor.getString(2));
                    splitThurs.add(cursor.getString(3));
                    splitThurs.add(cursor.getString(4));
                    splitThurs.add(cursor.getString(5));
                    thursday.add(splitThurs);
                
                listDataChild.put(listDataHeader.get(3), thursday); 
                break;
            case FRIDAY:
                 while(cursor.moveToNext())
                    
                        splitFri = new ArrayList<String>();
                        splitFri.add(cursor.getString(0));
                        splitFri.add(cursor.getString(1));
                        splitFri.add(cursor.getString(2));
                        splitFri.add(cursor.getString(3));
                        splitFri.add(cursor.getString(4));
                        splitFri.add(cursor.getString(5));
                        friday.add(splitFri);

                    
                    listDataChild.put(listDataHeader.get(4), friday);
                break;
        
        listAdapter.notifyDataSetChanged();
        expListView.post( new Runnable() 
            @Override
            public void run() 
                 switch(Calendar.getInstance().get(Calendar.DAY_OF_WEEK))
                case Calendar.MONDAY:
                    expListView.expandGroup(0);
                    break;
                case Calendar.TUESDAY:
                    expListView.expandGroup(1);
                    break;
                case Calendar.WEDNESDAY:
                    expListView.expandGroup(2);
                    break;
                case Calendar.THURSDAY:
                    expListView.expandGroup(3);
                    break;
                case Calendar.FRIDAY:
                    expListView.expandGroup(4);
                    break;
                case Calendar.SATURDAY:
                    expListView.expandGroup(0);
                    break;
                case Calendar.SUNDAY:
                    expListView.expandGroup(0);
                    break;
              
            
          );


    
    @Override
    public void onLoaderReset(Loader<Cursor> arg0) 
        // TODO Auto-generated method stub

      

ExpandableListAdapter:

package com.crevitus.timetable.adapter;

import java.util.HashMap;
import java.util.List;

import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import com.crevitus.timetable.R;

public class ExpandableListAdapter extends BaseExpandableListAdapter 

    private Context _context;
    private List<String> listGroupData;
    private HashMap<String, List<List<String>>> listItemData;

    public ExpandableListAdapter(Context context, List<String> listGroupData,
            HashMap<String, List<List<String>>> listDataChild) 
        this._context = context;
        this.listGroupData = listGroupData;
        this.listItemData = listDataChild;
    

    @Override
    public Object getChild(int groupPosition, int childPosition) 
        return listItemData
                .get(listGroupData
                        .get(groupPosition))
                        .get(childPosition);
    

    @Override
    public long getChildId(int groupPosition, int childPosition) 
        return childPosition;
    

    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) 

        List<String> childText = (List<String>) getChild(groupPosition, childPosition);

        if (convertView == null) 
            LayoutInflater infalInflater = (LayoutInflater) this._context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.exp_list_item, null);
        

        TextView sTime = (TextView) convertView.findViewById(R.id.sTime);
        TextView modName = (TextView) convertView.findViewById(R.id.modName);

        if(childText != null)
        
            modName.setText(childText.get(1) + " - Room: " + childText.get(2));
            sTime.setText(childText.get(4));
        
        return convertView;
    

    @Override
    public int getChildrenCount(int groupPosition) 
        return this.listItemData.get(this.listGroupData.get(groupPosition))
                .size();
    

    @Override
    public Object getGroup(int groupPosition) 
        return this.listGroupData.get(groupPosition);
    

    @Override
    public int getGroupCount() 
        return this.listGroupData.size();
    

    @Override
    public long getGroupId(int groupPosition) 
        return groupPosition;
    

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) 
        String headerTitle = (String) getGroup(groupPosition);
        if (convertView == null) 
            LayoutInflater infalInflater = (LayoutInflater) this._context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_group, null);
        

        TextView lblListHeader = (TextView) convertView.findViewById(R.id.listGroup);
        lblListHeader.setTypeface(null, Typeface.BOLD);
        lblListHeader.setText(headerTitle);

        return convertView;
    

    @Override
    public boolean hasStableIds() 
        return true;
    

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) 
        return true;
    

    public void deleteChild(int groupPosition, int childPosition)
    
        listItemData.get(listGroupData.get(groupPosition)).remove(childPosition);
    

【问题讨论】:

【参考方案1】:

所以我对 LoaderManager 不太熟悉,但我想说重新启动加载程序的 onResume() 方法很可疑。我想你可以完全删除它。我强烈建议阅读此SO answer,其中讨论了 initLoader 和 restartLoader 之间的区别以及在重新创建 Activity 时何时使用它们。从那里摘录一小段:

Activity/Fragment生命周期与决策无关 使用一种或另一种方法...此决定仅基于 新装载机的“需求”。如果我们想运行我们使用的相同查询 initLoader,如果我们想运行不同的查询,我们使用 restartLoader。 我们总是可以使用restartLoader,但这会效率低下。后 屏幕旋转,或者如果用户离开应用程序,并且 稍后返回到我们通常想要显示的相同活动 查询结果,因此 restartLoader 将不必要地重新创建 加载程序并关闭底层(可能很昂贵)查询 结果。

这可能会增加您崩溃的原因,因为它看起来确实是时间问题。您的组计数基于填充在 onCreate() 中的 listGroupData。但是,您从 listItemData 检索您的孩子计数,直到光标完成才会加载。这意味着在 onCreate() 和 onLoadFinished() 之间,您的 listItemData 将不包含任何数据,但您的适配器认为有可用的项目,因此会因 NPE 而崩溃。

在编写适配器时,listGroupData 反映 listItemData 绝对至关重要。修改一个意味着您需要修改另一个。此外,如果您保证地图中的 List 值永远不会为空,则无需担心到处进行空值检查。例如,在 onCreate 中这样做:

    listDataChild = new HashMap<String, List<List<String>>>();
    listDataChild.put("Monday", new ArrayList<List<String>>());
    listDataChild.put("Tuesday", new ArrayList<List<String>>());
    listDataChild.put("Wednesday", new ArrayList<List<String>>());
    listDataChild.put("Thursday", new ArrayList<List<String>>());
    listDataChild.put("Friday", new ArrayList<List<String>>());

    listDataHeader = new ArrayList<String>(listDataChild.keySet());

这确保listDataHeader 中的每个项目都可以在listDataChild 中找到,并且listDataChild 中的每个值都将返回一个空列表,直到加载器完成它并实际填充数据。

【讨论】:

优秀的答案谢谢!我的 android 理论缺乏,因为我在一个月内从没有 Java 知识到这个应用程序以及其他课程:/ 无论如何,要添加一些东西。出于某种原因,需要在 onResume() 中调用 restartLoader 以便在方向更改时重新加载数据。尽管我对那篇文章有所了解。最后,listDataHeader 仍然需要硬编码它的日期列表,因为哈希图没有排序。 明白了。请注意,您始终可以使用 LinkedHashMap 代替。

以上是关于活动恢复时 ExpandableListAdapter getChildrenCount 空指针异常的主要内容,如果未能解决你的问题,请参考以下文章

Android - 恢复上次查看的活动

“不要保留活动” - 当应用程序恢复时,片段仅可见一秒钟

获得用户对权限的响应时无法恢复活动

java Onesignal:打开通知时恢复上次活动

Android阻止恢复活动

Android - 恢复上次查看的活动