简体   繁体   English

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

[英]Google Play console reports app crash, I can not reproduce

Google Play Console in Android Vitals reports provides me with this information 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)

As I have understood this happens because getCount method of grid adapter returns null value. 据我了解,发生这种情况是因为网格适配器的getCount方法返回空值。 Hense, grid adapter does not work. 提示,网格适配器不起作用。 Hense, whole acrivity is not created. 诚然,没有创造出全部的狂热。 Only Samsung devices has such a problem. 仅三星设备有此问题。

First, I've tryed to create same emulated device with same Android version, RAM and ARM. 首先,我尝试创建具有相同Android版本,RAM和ARM的相同仿真设备。 Emulator works absolutely great. 仿真器的工作原理绝对出色。 No mistakes. 没错

Then I have added some tests to be sure getCount works and activity is created. 然后,我添加了一些测试,以确保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")));
    }
}

And again all emulated devices successfully pass tests. 所有仿真设备再次成功通过测试。 Then I have added FireBase TestLab and launched my tests on Galaxy S9 real device with Android 8. All tests are passed. 然后,我添加了FireBase TestLab,并在装有Android 8的Galaxy S9真实设备上启动了我的测试。

Code of adapter. 适配器代码。 Yes, I have overriten getCount() method even though it does not make sence. 是的,即使它没有意义,我也重写了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();
    }
}

So, I have got a couple of question: First, does this crash reports mean that this crash happens every time Sumsung owner launches app? 因此,我有两个问题:首先,此崩溃报告是否意味着每次Sumsung所有者启动应用程序时都会发生此崩溃? Since some Samsung devices persist in statistic, I assume no. 由于某些三星设备仍在统计中,因此我认为没有。 Second, what am I supposed to do to somehow reproduce this mistake and eventually fix the bug. 其次,我应该怎么做才能以某种方式重现此错误并最终修复该错误。

I understand question is too broad, but I will really appreciate any help, because I absolutely don't know what to do next. 我知道问题太笼统了,但是我将不胜感激,因为我绝对不知道下一步该怎么做。

Entry class. 入门班。 Uses adapter 使用适配器

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 убрать полосы в списках треков
}

Updated class: 更新的课程:

@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, thank you both for advices, but tracksFolders is never NULL. @Nick Fortescue,@ Ranjan,感谢你们两个的建议,但是tracksFolders绝不会为NULL。 I have edited class (you can see it below "Updated class:") and separated tracksFolders instantiation into another method with @NonNull annotation. 我已经编辑了类(您可以在“更新的类:”下面看到它),并将tracksFolders实例化为带有@NonNull批注的另一个方法。 I have updated app with this changes and later got same NPE with same log. 我已经使用此更改更新了应用程序,后来又获得了具有相同日志的相同NPE。 I know it's the worst way to test app on users, but I had no other option. 我知道这是在用户上测试应用程序的最糟糕的方法,但是我别无选择。 If you were right, log would say that Exception is thrown in getTracksFoldersAndInitService method. 如果您是对的,则日志会说getTracksFoldersAndInitService方法中引发了Exception。 But it didn't. 但事实并非如此。 Consequently, problem is in adapter. 因此,适配器存在问题。 Correct me if I'm wrong. 如果我错了纠正我。

The NullPointerException occurring in GridAdapter.getCount() doesn't mean getCount() returns null. 发生在GridAdapter.getCount()中的NullPointerException并不意味着getCount()返回null。 It means it is accessing a null. 这意味着它正在访问一个空值。 Since you uploaded your code (Great!) we can see there are only two possible problems: 由于您上传了代码(太好了!),我们可以看到只有两个可能的问题:

  • trackFolders is null , so when you access it you get an NPE trackFoldersnull ,因此在访问它时会得到一个NPE
  • trackFolders returns a null Integer, so when it gets converted to an int you get an NPE trackFolders返回一个null Integer,因此当将其转换为int您会得到一个NPE

The second option isn't possible, because .size() on an ArrayList returns an int too, so it must be the first option. 第二个选项是不可能的,因为ArrayList上的.size()返回一个int,因此它必须是第一个选项。

trackFolders is set in the constructor, which is set in your onCreate() method from the bundle, from the saved state, or from getTracksFolders() . trackFolders是在构造函数中设置的,该构造函数是在bundle,保存的状态或getTracksFolders() onCreate()方法中设置的。 It looks like getTracksFolders() can't return null, so my guess is your activity is being started with an intent with this set to null. 看起来getTracksFolders()无法返回null,因此我的猜测是,您的活动是从将其设置为null的意图开始的。 I would double check this in your onCreate() method, and if it happens take appropriate action, maybe replace it with a new ArrayList() ? 我会在您的onCreate()方法中再次检查,如果发生这种情况,请采取适当的措施,也许将其替换为new ArrayList()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Google Play 控制台崩溃报告 - 多个 OR - Google Play Console Crash Reports - multiple ORs 谷歌玩开发者控制台崩溃报告 - Google play developer console crash reports Google Play开发者控制台中没有崩溃报告? - No crash-reports in Google Play Developer Console? 如何从 Google Play 控制台获取应用程序崩溃日志 - How can I get app crash log from Google play console Google Play控制台中的java.lang.IllegalStateException崩溃报告 - java.lang.IllegalStateException crash reports from Google Play Console Google Play 开发者控制台崩溃报告中缺少重要信息 - Vital information missing from Google Play Developer console crash reports Play控制台崩溃报告(OutOfMemory) - Play Console Crash Reports (OutOfMemory) 我如何从谷歌播放控制台发布的 android 应用程序收集错误报告 - How do i collect bug reports from published android app from google play console 即使不是使用Play应用下载的,Android也会将崩溃/冻结报告发送到Google的Play商店吗? - Does Android send crash/freeze reports to Google's Play Store even if the app was not downloaded using the Play app? Google Play控制台中的java.lang.RuntimeException应用程序崩溃 - java.lang.RuntimeException App Crash from Google Play Console
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM