Google Play控制台报告应用程序崩溃,我无法复制

时间:2018-10-28 02:43:05

标签: android firebase testing gridview google-play-console

Android Vitals报告中的Google Play控制台为我提供了此信息

java.lang.RuntimeException: 
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2955)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3030)
  at android.app.ActivityThread.-wrap11 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1696)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6938)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)
Caused by: java.lang.NullPointerException: 
  at com.golendukhin.whitenoise.GridAdapter.getCount (GridAdapter.java:52)
  at android.widget.GridView.setAdapter (GridView.java:243)
  at com.golendukhin.whitenoise.FoldersGridActivity.onCreate (FoldersGridActivity.java:60)
  at android.app.Activity.performCreate (Activity.java:7183)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1220)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2908)

据我所知,这是因为网格适配器的getCount方法返回空值。提示,网格适配器不起作用。诚然,没有创造出全部的狂热。只有三星设备才有这样的问题。

首先,我尝试创建具有相同Android版本,RAM和ARM的相同仿真设备。仿真器的工作原理绝对出色。没错。

然后我添加了一些测试,以确保getCount可以正常工作并创建活动。

@RunWith(AndroidJUnit4.class)
public class GridViewTest {

    @Rule
    public ActivityTestRule<FoldersGridActivity> foldersGridActivityTestRule  = new  ActivityTestRule<>(FoldersGridActivity.class);

    @Test
    public void ensureGridAdapterIsNotNull() {
        FoldersGridActivity foldersGridActivity = foldersGridActivityTestRule.getActivity();
        View view = foldersGridActivity.findViewById(R.id.grid_view);
        GridView gridView = (GridView) view;
        GridAdapter gridAdapter = (GridAdapter) gridView.getAdapter();
        assertThat(gridAdapter, notNullValue());
    }

    @Test
    public void ensureArrayAdapterInGridActivityIsNotEmpty() {
        FoldersGridActivity foldersGridActivity = foldersGridActivityTestRule.getActivity();
        View view = foldersGridActivity.findViewById(R.id.grid_view);
        GridView gridView = (GridView) view;
        GridAdapter gridAdapter = (GridAdapter) gridView.getAdapter();
        assertTrue(gridAdapter.getCount() > 0 );
    }

    @Test
    public void gridViewIsVisibleAndClickable() {
        onData(anything()).inAdapterView(withId(R.id.grid_view)).atPosition(0).perform(click());
        onView(withId(R.id.toolbar_title_text_view)).check(matches(withText("rain")));
    }
}

所有仿真设备再次成功通过测试。 然后,我添加了FireBase TestLab,并在装有Android 8的Galaxy S9真实设备上启动了测试。

适配器的代码。是的,即使它没有意义,我也重写了getCount()方法。

public class GridAdapter extends ArrayAdapter<TracksFolder> {
    @BindView(R.id.folder_text_view) TextView textView;
    @BindView(R.id.grid_item_image_view) ImageView imageView;

    private ArrayList<TracksFolder> tracksFolders;
    private Context context;

    GridAdapter(Context context, int resource, ArrayList<TracksFolder> tracksFolders) {
        super(context, resource, tracksFolders);
        this.tracksFolders = tracksFolders;
        this.context = context;
    }

    /**
     * @return grid item, customized via TracksFolder class instance
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.grid_item, parent, false);
        }
        ButterKnife.bind(this, convertView);

        String folderName = tracksFolders.get(position).getFolderName();
        int imageResource = tracksFolders.get(position).getImageResource();
        textView.setText(folderName);
        imageView.setImageResource(imageResource);

        return convertView;
    }

    /**
     * Added to possibly avoid NPE on multiple devices
     * Could not reproduce this error
     * @return size of tracksFolders
     */
    @Override
    public int getCount() {
        return tracksFolders.size();
    }
}

所以,我有几个问题: 首先,此崩溃报告是否意味着每次Sumsung所有者启动应用程序时都会发生此崩溃?由于某些三星设备仍在统计中,因此我认为没有。 其次,我应该怎么做才能以某种方式重现此错误并最终修复该错误。

我知道问题太广泛了,但是我将非常感谢您的帮助,因为我绝对不知道下一步该怎么做。

进入类别。使用适配器

import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.GridView;

import com.google.firebase.crash.FirebaseCrash;

import java.util.ArrayList;
import butterknife.BindArray;
import butterknife.BindView;
import butterknife.ButterKnife;

import static com.golendukhin.whitenoise.SharedPreferencesUtils.writeToSharedPreferences;

public class FoldersGridActivity extends AppCompatActivity {
    @BindView(R.id.grid_view) GridView gridView;
    @BindArray(R.array.labels) String[] foldersName;
    @BindView(R.id.toolbar_like_button) Button toolbarLikeButton;
    @BindView(R.id.toolbar_back_button) Button toolbarBackButton;

    private ArrayList<TracksFolder> tracksFolders;

    /**
     * Binds all UI views
     * Defines variables to feed adapter and listeners
     * Initializes grid adapter, sets number of columns via phone in portrait or landscape orientation
     * Sets onclick listener for every grid item
     * Sets onclick for toolbar "like" button
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.folders_grid_view);
        ButterKnife.bind(this);

        if (getIntent().getExtras() != null) { //activity is triggered via on back pressed
            Bundle bundle = getIntent().getExtras();
            tracksFolders = (ArrayList<TracksFolder>) bundle.getSerializable("tracksFolders");
        } else if (savedInstanceState != null) { //this happens after screen is rotated
            tracksFolders = (ArrayList<TracksFolder>) savedInstanceState.getSerializable("tracksFolders");
        } else { //this happens if app is launched by user
            tracksFolders = getTracksFolders();
            initService();
        }

        toolbarBackButton.setVisibility(View.GONE);

        GridAdapter gridAdapter;
        gridAdapter = new GridAdapter(this, R.id.grid_view, tracksFolders);
        gridView.setNumColumns(getResources().getInteger(R.integer.columns_number));
        gridView.setAdapter(gridAdapter);
        FirebaseCrash.log("Activity created");

        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Bundle bundle = new Bundle();
                bundle.putSerializable("tracksFolders", tracksFolders);
                bundle.putInt("folderPosition", position);
                Intent intent = new Intent(FoldersGridActivity.this, TracksListActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });

        toolbarLikeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Bundle bundle = new Bundle();
                bundle.putSerializable("tracksFolders", tracksFolders);
                bundle.putInt("activityFrom", Constants.FROM_FOLDERS_GRID_ACTIVITY);
                Intent intent = new Intent(FoldersGridActivity.this, LikedTracksListActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }

    /**
     * Saves tracksFolders array list to rebuild activity after rotation
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putSerializable("tracksFolders", tracksFolders);
        writeToSharedPreferences(this, tracksFolders);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        moveTaskToBack(true);
    }

    /**
     * After app is launched need to initialize audio player service
     */
    private void initService() {
        Intent intent = new Intent(FoldersGridActivity.this, AudioPlayerService.class);
        startService(intent);
    }

    /**
     * If tracksFolders is not stored in shared preferences, it is initialized from resources.
     * @return arrayList of folders with arrayList of tracks belonging to every folder
     */
    private ArrayList<TracksFolder> getTracksFolders() {
        Resources resources = getResources();
        TypedArray pictures = resources.obtainTypedArray(R.array.pictures);

        ArrayList<TracksFolder> tracksFolders = new ArrayList<>();
        for (int i = 0; i < foldersName.length; i++) {
            String folderName = foldersName[i];
            int imageResource = pictures.getResourceId(i, -1);
            ArrayList<Track> tracks = getArrayListOfTracks(resources, i, imageResource);
            tracksFolders.add(new TracksFolder(folderName, imageResource, tracks));
        }
        pictures.recycle();
        return tracksFolders;
    }

    /**
     * @param resources      need to get typedArray to get array of audio files names
     * @param folderPosition passed to Track constructor
     * @param imageResource  passed to Track constructor
     *                       Liked tracks are obtained from shared preferences
     * @return array list of tracks for every folder
     */
    private ArrayList<Track> getArrayListOfTracks(Resources resources, int folderPosition, int imageResource) {
        TypedArray audioTracksTypedArray = resources.obtainTypedArray(R.array.audio_tracks);
        int audioTracksId = audioTracksTypedArray.getResourceId(folderPosition, 0);
        String[] audio = resources.getStringArray(audioTracksId);
        audioTracksTypedArray.recycle();

        TypedArray trackNamesTypedArray = resources.obtainTypedArray(R.array.track_names);
        int trackNamesId = trackNamesTypedArray.getResourceId(folderPosition, 0);
        String[] trackNames = resources.getStringArray(trackNamesId);
        trackNamesTypedArray.recycle();

        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

        ArrayList<Track> tracksInFolder = new ArrayList<>();
         for (int i = 0; i < trackNames.length; i++) {
            int audioResource = resources.getIdentifier(audio[i], "raw", this.getPackageName());
            String trackName = trackNames[i].replace("_", " ");
            boolean isLiked = sharedPreferences.getBoolean(String.valueOf(audioResource), false);
            tracksInFolder.add(new Track(audioResource, trackName, imageResource, isLiked));
        }
        return tracksInFolder;
    }
    //todo убрать полосы в списках треков
}

更新的课程:

@NonNull
    private  ArrayList<TracksFolder> getTracksFoldersAndInitService(Bundle savedInstanceState) {
        if (getIntent().getExtras() != null) { //activity is triggered via on back pressed
            Bundle bundle = getIntent().getExtras();
            tracksFolders = (ArrayList<TracksFolder>) bundle.getSerializable("tracksFolders");
        } else if (savedInstanceState != null) { //this happens after screen is rotated
            tracksFolders = (ArrayList<TracksFolder>) savedInstanceState.getSerializable("tracksFolders");
        } else { //this happens if app is launched by user
            tracksFolders = getTracksFoldersFromResources();
            initService();
        }
        return tracksFolders;
    }

@Nick Fortescue,@ Ranjan,谢谢你们两个的建议,但是tracksFolders绝不能为NULL。我已经编辑了类(您可以在“更新的类:”下面看到它),并将tracksFolders实例化为带有@NonNull批注的另一个方法。我已经使用此更改更新了应用程序,后来又获得了具有相同日志的相同NPE。我知道这是在用户上测试应用程序的最糟糕的方法,但是我别无选择。如果您是对的,则日志会说getTracksFoldersAndInitService方法中引发了Exception。但事实并非如此。因此,适配器存在问题。如果我错了请纠正我。

1 个答案:

答案 0 :(得分:0)

GridAdapter.getCount()中发生的NullPointerException并不意味着getCount()返回null。这意味着它正在访问一个空值。由于您上传了代码(太好了!),我们可以看到只有两个可能的问题:

  • trackFoldersnull,因此访问它会得到NPE
  • trackFolders返回一个null整数,因此当它转换为int时,您会得到一个NPE

第二个选项是不可能的,因为.size()上的ArrayList也会返回一个int,因此它必须是第一个选项。

trackFolders是在构造函数中设置的,该构造函数是在onCreate()方法中从捆绑软件,已保存状态或getTracksFolders()中设置的。看来getTracksFolders()不能返回null,所以我猜测您的活动是从将其设置为null的意图开始的。我会在您的onCreate()方法中再次检查,如果发生这种情况,请采取适当的措施,也许将其替换为new ArrayList()