Android应用启动崩溃:java.lang.NullPointerException(铃声相关)
解决方案:Activity启动崩溃(Ringtone初始化导致空指针)
嘿,我仔细看了你的代码和错误日志,问题的根源很清晰——你在类成员变量位置直接初始化Ringtone对象的时候,Activity还没完成创建流程,此时调用getApplicationContext()会返回null,直接触发了空指针异常,导致App启动就崩溃。
错误原因拆解
从错误日志里的Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference可以看出,问题出在MainActivity.java:17行,也就是你初始化Ringtone的那行代码。
Android组件的Context是在onCreate()生命周期方法执行后才完全可用的,而成员变量的初始化是在Activity构造函数执行阶段完成的,这时候Activity还没有依附到系统的Context环境中,所以getApplicationContext()返回null。
修复方案及优化后的代码
我给你调整了代码,解决了空指针问题,同时还优化了线程安全和内存泄漏的问题:
package com.example.analarm; import androidx.appcompat.app.AppCompatActivity; import android.media.Ringtone; import android.media.RingtoneManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.widget.TextClock; import android.widget.TimePicker; import java.util.Timer; import java.util.TimerTask; public class MainActivity extends AppCompatActivity { TimePicker alarmtime; TextClock time; // 1. 先只声明Ringtone变量,不初始化 Ringtone ring; Timer t; // 用于将子线程的操作切换到主线程 Handler mainHandler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); alarmtime=findViewById(R.id.timepicker); time=findViewById(R.id.textclock); // 2. 在onCreate中初始化Ringtone,此时Context已经可用 ring = RingtoneManager.getRingtone(getApplicationContext(), RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)); t= new Timer(); t.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // 3. 媒体操作建议在主线程执行,用Handler切换 mainHandler.post(() -> { if(time.getText().toString().equals(Alarmtime())) { if(!ring.isPlaying()) { // 避免重复播放 ring.play(); } } else { if(ring.isPlaying()) { // 避免重复停止 ring.stop(); } } }); } },0,1000); } public String Alarmtime() { Integer alarmHours=alarmtime.getCurrentHour(); Integer alarmMin=alarmtime.getCurrentMinute(); String stringalarmmin; if(alarmMin<10) { stringalarmmin="0"+alarmMin; } else{ stringalarmmin=alarmMin.toString(); } String stringalarmtime; if(alarmHours>12) { alarmHours= alarmHours-12; stringalarmtime=alarmHours.toString()+":"+stringalarmmin+" PM"; } else { stringalarmtime=alarmHours.toString()+":"+stringalarmmin+" AM"; } return stringalarmtime; } // 4. 在Activity销毁时取消Timer,避免内存泄漏 @Override protected void onDestroy() { super.onDestroy(); if(t != null) { t.cancel(); t.purge(); } if(ring != null && ring.isPlaying()) { ring.stop(); } } }
关键改动说明
- 将Ringtone的初始化从成员变量位置移到了
onCreate()方法中,确保Context可用 - 添加了
Handler来将TimerTask子线程中的媒体操作切换到主线程,避免线程安全问题 - 增加了
isPlaying()判断,避免重复调用play/stop方法 - 在
onDestroy()中取消Timer并停止铃声,防止内存泄漏
额外优化建议
用字符串对比时间的方式容易出现格式不匹配的问题(比如TextClock的格式和你拼接的格式不一致),建议改用Calendar来获取时间并对比,这样更可靠:
// 示例:用Calendar对比时间 Calendar currentCal = Calendar.getInstance(); Calendar alarmCal = Calendar.getInstance(); alarmCal.set(Calendar.HOUR_OF_DAY, alarmtime.getCurrentHour()); alarmCal.set(Calendar.MINUTE, alarmtime.getCurrentMinute()); alarmCal.set(Calendar.SECOND, 0); // 对比当前时间和闹钟时间(忽略秒数) if(currentCal.get(Calendar.HOUR_OF_DAY) == alarmCal.get(Calendar.HOUR_OF_DAY) && currentCal.get(Calendar.MINUTE) == alarmCal.get(Calendar.MINUTE)) { // 触发闹钟 }
内容的提问来源于stack exchange,提问作者W For Wonder
相关产品推荐
相关产品推荐

