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方法中抛出了异常。但它没有。因此,问题出在适配器上。如果我错了就纠正我。