Консоль Google Play сообщает о сбое приложения, не могу воспроизвести - PullRequest
0 голосов
/ 28 октября 2018

Консоль Google Play в отчетах Android Vitals предоставляет мне эту информацию

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 адаптера сетки возвращает нулевое значение.Hense, сеточный адаптер не работает.Hense, вся остроту не создается.Такая проблема есть только у устройств Samsung.

Во-первых, я попытался создать такое же эмулированное устройство с той же версией 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 и запустил свои тесты на реальном устройстве Galaxy S9 с Android 8. Все тесты пройдены.

Код адаптера.Да, я перезаписал метод 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владелец запускает приложение?Поскольку некоторые устройства Samsung сохраняются в статистике, я полагаю, нет.Во-вторых, что я должен сделать, чтобы как-то воспроизвести эту ошибку и в конечном итоге исправить ошибку.

Я понимаю, что вопрос слишком широкий, но я действительно буду признателен за любую помощь, потому что я абсолютно не знаю, что делатьследующий.

Вступительный класс.Использует адаптер

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;
    }

@ Ник Фортескью, @Ranjan, спасибо вам обоим за советы, но trackFolders никогда не бывает NULL.Я отредактировал класс (вы можете увидеть его ниже «Обновленный класс:») и разделил создание экземпляров trackFolders в другом методе с аннотацией @NonNull.Я обновил приложение с этими изменениями и позже получил тот же NPE с тем же журналом.Я знаю, что это худший способ протестировать приложение на пользователях, но у меня не было другого выбора.Если бы вы были правы, журнал сказал бы, что Exception генерируется в методе getTracksFoldersAndInitService.Но это не так.Следовательно, проблема в адаптере.Поправь меня, если я ошибаюсь.

1 Ответ

0 голосов
/ 29 октября 2018

Исключение NullPointerException, встречающееся в GridAdapter.getCount(), не означает, что getCount () возвращает ноль.Это означает, что он обращается к нулю.Поскольку вы загрузили свой код (Отлично!), Мы видим, что есть только две возможные проблемы:

  • trackFolders равно null, поэтому при доступе к нему вы получаете NPE
  • trackFolders возвращает null целое число, поэтому при преобразовании в int вы получаете NPE

Второй вариант невозможен, поскольку .size() на ArrayList также возвращает int, поэтому это должен быть первый параметр.

trackFolders устанавливается в конструкторе, который устанавливается в вашем методе onCreate() из пакета, из сохраненного состояния или из getTracksFolders().Похоже, что getTracksFolders() не может вернуть ноль, поэтому я предполагаю, что ваша деятельность начинается с намерения с этим значением ноль.Я бы дважды проверил это в вашем методе onCreate (), и если это произойдет, предпримите соответствующие действия, возможно, замените его на new ArrayList()?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...