尝试在空对象引用错误上调用虚拟方法'java.lang.String android.content.Context.getPackageName()'



我正在尝试在长头低于某个数字时触发通知。但是,每当调用 sendNotification() 时,它都会抛出上述错误。

我是安卓新手。

下面是问题所在的代码部分。

我不确定是什么导致了此错误。我假设我需要更改发送通知(视图视图)的方法,但在这种情况下,我应该发送什么作为视图?

如果需要,我可以提供完整的代码。

package com.mple.seriestracker;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import com.mple.seriestracker.activity.HomeScreenActivity;
import com.mple.seriestracker.api.episodate.entities.show.Episode;
import com.mple.seriestracker.util.NotificationGenerator;
import org.threeten.bp.Duration;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.DateTimeFormatter;
import java.util.Locale;
public class Countdown extends AppCompatActivity{

private int season;
private int episode;
private String name;
private ZonedDateTime airDate;
private Context context;
public Countdown(String name, int episode,int season,ZonedDateTime airDate, Context context){
this.airDate = airDate;
this.name = name;
this.episode = episode;
this.season = season;
this.context = context;
}
public Countdown(com.mple.seriestracker.api.episodate.entities.show.Countdown countdown){
this.airDate = parseToLocal(countdown.air_date);
this.name = countdown.name;
this.episode = countdown.episode;
this.season = countdown.season;
}
public void getSecondsTillAiring(){
Duration duration = Duration.between(LocalDateTime.now(),airDate);
long days = duration.toDays();
//No idea why this returns an absurd number, possibly something wrong with the time conversion
//So the simple fix is to convert the days into hours, subtract the total hours with the days.
//This returns the real value, and makes it accurate.
long hours = duration.toHours()-(days*24);
long minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60);
long seconds = (int) (duration.getSeconds() % 60);
if(days > 0){
hours += days * 24;
}
if(hours > 0){
minutes += 60* hours;
}
if(minutes > 0){
seconds  += 60 * minutes;
}
if (seconds < 432000){
sendNotification();
}
}
public void sendNotification()
{
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "M_CH_ID");
//Create the intent that’ll fire when the user taps the notification//
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.androidauthority.com/"));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
notificationBuilder.setContentIntent(pendingIntent);

NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel =
new NotificationChannel("M_CH_ID", "M_CH_ID", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription("Test");
nm.createNotificationChannel(notificationChannel);
notificationBuilder.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher)
.setTicker("Hearty365")
.setContentTitle("Default notification")
.setContentText("Random words")
.setContentInfo("Info");
nm.notify(1, notificationBuilder.build());
}
public String getCountdownFormat(){
getSecondsTillAiring();
Duration duration = Duration.between(LocalDateTime.now(),airDate);
long days = duration.toDays();
//No idea why this returns an absurd number, possibly something wrong with the time conversion
//So the simple fix is to convert the days into hours, subtract the total hours with the days.
//This returns the real value, and makes it accurate.
long hours = duration.toHours()-(days*24);
int minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60);
int seconds = (int) (duration.getSeconds() % 60);
String timeString = "";
if(days > 0){
timeString+=formatDay(days);
}
if(hours > 0){
timeString+=formatHour(hours);
}
if(minutes > 0){
timeString+= formatMinutes(minutes);
}
if(seconds > 0){
timeString += formatSeconds(seconds);
}
return timeString;
}
public String getName() {
return name;
}
public int getEpisode() {
return episode;
}
public int getSeason() {
return season;
}
private String formatDay(long days){
return format(days,"day");
}
private String formatHour(long hours){
return format(hours,"hour");
}
private String formatMinutes(long minutes){
return format(minutes,"minute");
}
private String formatSeconds(long seconds){
return format(seconds,"second");
}
private String format(long x,String nonPlural){
//Checks whether or not a plural should be added
String string = nonPlural;
if(x > 1)
string+="s";
return String.format("%s %s ",x,string);
}
//All air dates are formatted in this format
static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
public static ZonedDateTime parseToLocal(String s){
if(s == null) return null;
return LocalDateTime.parse(s, DATE_TIME_FORMATTER)
.atOffset(ZoneOffset.UTC)
.atZoneSameInstant(ZoneId.systemDefault());
}
public static boolean isOlderEpisode(LocalDateTime airDate, LocalDateTime currEpDate){
return currEpDate.isBefore(airDate);
}
public static boolean isOlderEpisode(OffsetDateTime airDate, OffsetDateTime currEpDate){
return currEpDate.toLocalDate().isBefore(airDate.toLocalDate());
}
//Responsible for finding a certain episode
public Countdown getUpcomingAiringEp(Episode[] episodes, int episode, int season) {
if (episodes == null) {
return null;
}
//Loop in reverse, since the episodes are ordered from start to finish
//So looping from reverse will start with the newer shows first
for (int i = (episodes.length - 1); i >= 0; i--) {
Episode newEpisode = episodes[i];

if (newEpisode.air_date != null && newEpisode.season == season && newEpisode.episode == episode) {
return new Countdown(newEpisode.name,newEpisode.episode, newEpisode.season, parseToLocal(newEpisode.air_date),this);
}
if(newEpisode.season <= (newEpisode.season - 1)) {
break;
}
}
return null;
}
}

全栈跟踪

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mple.seriestracker, PID: 2348
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.content.ContextWrapper.getPackageName(ContextWrapper.java:145)
at android.app.PendingIntent.getActivity(PendingIntent.java:344)
at android.app.PendingIntent.getActivity(PendingIntent.java:311)
at com.mple.seriestracker.Countdown.sendNotification(Countdown.java:76)
at com.mple.seriestracker.Countdown.getSecondsTillAiring(Countdown.java:65)
at com.mple.seriestracker.Countdown.getCountdownFormat(Countdown.java:100)
at com.mple.seriestracker.fragments.CountdownFragment$RecyclerViewAdapter$1.run(CountdownFragment.java:95)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

和主要类

package com.mple.seriestracker.activity;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.jakewharton.threetenabp.AndroidThreeTen;
import com.mple.seriestracker.R;
import com.mple.seriestracker.ShowInfo;
import com.mple.seriestracker.ShowTracker;
import com.mple.seriestracker.TvShow;
import com.mple.seriestracker.api.episodate.Episodate;
import com.mple.seriestracker.api.episodate.entities.show.TvShowResult;
import com.mple.seriestracker.database.EpisodeTrackDatabase;
import com.mple.seriestracker.fragments.CountdownFragment;
import com.mple.seriestracker.fragments.SectionsPagerAdapter;
import com.mple.seriestracker.fragments.MyShowsFragment;
import com.mple.seriestracker.util.NotificationGenerator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Response;

public class HomeScreenActivity extends AppCompatActivity {
static final int NEW_SHOW_REQUEST_CODE = 1;
static final int FILE_PERMISSION_RREQUEST_CODE = 1;
static final int NEW_SHOW_REQUEST_RESULT_CODE = 1;
Context context = this;
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
TabLayout mTabs;
boolean started = false;
//TODO allow more than 3 shows to display on countdown page
//TODO sort the countdown tab based on time
//TODO notify the user when a show is airing
//TODO re-obtain the next countdown (if any new episodes) otherwise remove the countdown from the tab
//TODO add delete button to delete shows (holding on image already has checkboxes implemented)
//All done after that
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EpisodeTrackDatabase.setInstance(new EpisodeTrackDatabase(getApplicationContext()));
//Sets all date time stuff to correct sync
AndroidThreeTen.init(this);
//Initialize fragments
mSectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
mViewPager = findViewById(R.id.view_pager);
setupViewPager(mViewPager);
mTabs = findViewById(R.id.tabs);
mTabs.setupWithViewPager(mViewPager);
//Initialize floating menu button
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startSearchIntent();
}
});
}
@Override
protected void onStart() {
super.onStart();
//On start is called when the search intent is destroyed
//This prevents it from being used more than once.
//As it's intended for loading settings only (after all UI elements are initialized)
if(started) return;
loadSettings();
started = true;
}
//Responsible for setting up the fragments for each tab
private void setupViewPager(ViewPager viewPager){
mSectionsPagerAdapter.addFragment(new MyShowsFragment(),"My Shows");
mSectionsPagerAdapter.addFragment(new CountdownFragment(),"Countdowns");
viewPager.setAdapter(mSectionsPagerAdapter);
}
private void loadSettings(){
//Loads settings from database
new LoadShowsTask().execute();
}
//Adds a show to the "my shows" tab
public void addShow(ShowInfo showInfo){
new TvShowTask().execute(showInfo); //Background task to get info from the api
EpisodeTrackDatabase.INSTANCE.addShow(showInfo.name,showInfo.imagePath,showInfo.id); //Add the show to the database
((MyShowsFragment)mSectionsPagerAdapter.getItem(0)).addShow(showInfo); //Adds it to the fragment, fragment will then automatically update it
}
public void addCountdown(long showID){
if(ShowTracker.INSTANCE.calenderCache.contains(showID))return;
((CountdownFragment)mSectionsPagerAdapter.getItem(1)).addCountdown(showID);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == NEW_SHOW_REQUEST_CODE && resultCode == NEW_SHOW_REQUEST_RESULT_CODE) {
//Create a new show, add it to the list and populate it
ShowInfo showInfo = new ShowInfo(); //Creates a new object
showInfo.id = data.getLongExtra("showID",0);
showInfo.imagePath = data.getStringExtra("showImage");
showInfo.name = data.getStringExtra("showName");
ShowTracker.INSTANCE.addedShowsCache.add(showInfo.id);
addShow(showInfo);
}
}
class LoadShowsTask extends AsyncTask<String,Void,String>{
@Override
protected String doInBackground(String... strings) {
ShowInfo[] showData =  EpisodeTrackDatabase.INSTANCE.getAllShows();
runOnUiThread(() ->{
new TvShowTask().execute(showData);
for (ShowInfo show : showData) {
runOnUiThread(() ->addShow(show));
}
});
return null;
}
}
class TvShowTask extends AsyncTask<ShowInfo,Void, List<TvShowResult>> {
//Responsible for obtaining info about each show
//Automatically prompts on each show add/app initialization
@Override
protected List<TvShowResult> doInBackground(ShowInfo ... shows) {
List<TvShowResult> tvShowResults = new ArrayList<>();
for (ShowInfo show: shows) {
try {
Response<com.mple.seriestracker.api.episodate.entities.show.TvShow> response = Episodate.INSTANCE
.show()
.textQuery(show.id + "")
.execute();
if(response.isSuccessful()){
tvShowResults.add(response.body().tvShow);
}
} catch (IOException e) {}
}
return tvShowResults;
}
@Override
protected void onPostExecute(List<TvShowResult> result) {
for (TvShowResult tvShowResult : result) {
TvShow tvShow = new TvShow(tvShowResult);
ShowTracker.INSTANCE.addTvShow(tvShow);
if(tvShow.getCountdown() != null){
addCountdown(tvShow.getId());
}
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case FILE_PERMISSION_RREQUEST_CODE:
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
//Todo Finish this, if we will be implementing saving
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
//Prevents the app opening multiple search intents, if spammed
void startSearchIntent(){
if(!ShowSearchActivity.destroyed) return;
Intent intent = new Intent(getApplicationContext(),ShowSearchActivity.class);
startActivityForResult(intent,NEW_SHOW_REQUEST_CODE);
}
//Will be used for writing saved data, later on to keep track of what shows are saved
boolean hasFilePermissions(){
return (Build.VERSION.SDK_INT > 22 && ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED);
}
void askForPermission(){
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_PERMISSION_RREQUEST_CODE);
}
}

我想知道在这一点上链接到 github 是否更容易,因为除了这两个类之外还有更多的类。

您必须从活动类中提供正确的上下文。 首先在你的YOUR_ACTIVITY.java声明这一点

Context context = this;

然后在倒计时方法中添加上下文参数

private int season;
private int episode;
private String name;
private ZonedDateTime airDate;
private Context context;
public Countdown(String name, int episode,int season,ZonedDateTime airDate, Context context){
this.airDate = airDate;
this.name = name;
this.episode = episode;
this.season = season;
this.context = context;
}

只需在调用 Countdown 方法时添加上下文。

new Countdown(name, episode, season, context).getSecondsTillAiring();

然后像这样向通知管理器提供上下文。

NotificationManager nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

只需删除"extend AppCompatActivity"可能适合您,因为显然您的类不会从AppCompatActivity继承任何内容。

编辑:

NotificationManagernm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

您可以考虑将这部分代码移动到 Activity 类中, 或将活动引用传递给该方法并像这样调用它:

NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);

相关内容