[英]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 trackFolders
为null
,因此在访问它时会得到一个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.