代码之家  ›  专栏  ›  技术社区  ›  Maksim Golendukhin

谷歌游戏控制台报告应用程序崩溃,我无法复制

  •  0
  • Maksim Golendukhin  · 技术社区  · 6 年前

    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方法返回空值。Hense,网格适配器不工作。母鸡,整片土地都不是创造出来的。只有三星的设备才有这样的问题。

    首先,我尝试用相同的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 real设备上启动了测试。所有测试都通过了。

    适配器代码。是的,我已经重写了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;
        }
    

    @尼克·福特斯库,@ranjan,谢谢你们的建议,但tracksfolders永远不会为空。我已经编辑了类(您可以在下面的“updated class:”中看到它),并使用@nonnull注释将tracks文件夹实例化分离到另一个方法中。我已经用这些更改更新了应用程序,后来用相同的日志得到了相同的npe。我知道这是在用户身上测试应用程序最糟糕的方法,但我别无选择。如果您是对的,日志会说getTracksFoldersAndInitService方法中抛出了异常。但它没有。因此,问题出在适配器上。如果我错了就纠正我。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Nick Fortescue    6 年前

    发生在 GridAdapter.getCount() 并不意味着getCount()返回空值。这意味着它正在访问一个空值。自从你上传了你的代码(太棒了!)我们可以看到只有两个可能的问题:

    • trackFolders null ,所以当你访问它时,你会得到一个npe
    • 跟踪文件夹 返回A 无效的 整数,所以当它转换为 int 你得到了NPE

    第二种选择是不可能的,因为 .size() 关于一个 ArrayList 也返回一个int,所以它必须是第一个选项。

    跟踪文件夹 在构造函数中设置,在 onCreate() 方法,从bundle、从saved状态或从 getTracksFolders() . 看起来像 getTracksFolders() 无法返回空值,因此我猜您的活动是以设置为空的意图启动的。我会在oncreate()方法中再次检查这个,如果它发生了,请采取适当的操作,或者用 new ArrayList() ?