如何在OnCreate/Activity外启动Service?GPS服务按钮控制报错求助
安卓GPS定位服务启停时触发NullPointerException问题解决
问题描述
开发安卓应用GPS定位功能,通过按钮启动/停止LocationService时触发NullPointerException,错误日志如下:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference at android.content.ComponentName.<init>(ComponentName.java:131) at android.content.Intent.<init>(Intent.java:6858) at red.button.flipper_navigator.ui.MainScreen.start(MainScreen.kt:58) at red.button.flipper_navigator.ui_logic.MainScreenLogic.StartNavBt(Main_screen_logic.kt:47) at red.button.flipper_navigator.databinding.ScreenMainBindingImpl$OnClickListenerImpl1.onClick(ScreenMainBindingImpl.java:189) at android.view.View.performClick(View.java:7448) at android.view.View.performClickInternal(View.java:7425) at android.view.View.access$3600(View.java:810) at android.view.View$PerformClick.run(View.java:28305) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
相关代码
MainScreen Activity
package red.button.flipper_navigator.ui import android.Manifest import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log import android.view.View import androidx.lifecycle.ViewModel import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import red.button.flipper_navigator.ui_logic.MainScreenLogic import red.button.flipper_navigator.R import red.button.flipper_navigator.databinding.ScreenMainBinding import red.button.flipper_navigator.databinding.TextFieldBinding import red.button.flipper_navigator.gps.LocationService import red.button.flipper_navigator.gps.gps_activation @Suppress("DEPRECATION") class MainScreen : AppCompatActivity(), gps_activation { private lateinit var bindingMain: ScreenMainBinding private lateinit var bindingField: TextFieldBinding private var context: Context? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.POST_NOTIFICATIONS ), 0 ) bindingMain = DataBindingUtil.setContentView(this, R.layout.screen_main) bindingField = bindingMain.textField val viewModel = ViewModelProvider(this)[MainScreenLogic::class.java] bindingMain.mainview = viewModel bindingField.textfieldview = viewModel context = getApplicationContext() // Intent(context, LocationService::class.java).apply { // action = LocationService.ACTION_START // startService(this) // } } override fun start() { Intent(context, LocationService::class.java).apply { action = LocationService.ACTION_START startService(this) } } override fun stop() { Intent(context, LocationService::class.java).apply { action = LocationService.ACTION_STOP startService(this) } } }
gps_activation接口
package red.button.flipper_navigator.gps import android.content.Context import red.button.flipper_navigator.ui.MainScreen interface gps_activation { fun start() fun stop() companion object { fun create(): gps_activation { return MainScreen() } } }
MainScreenLogic ViewModel
package red.button.flipper_navigator.ui_logic import android.app.Application import android.content.Context import android.content.Intent import android.util.Log import android.view.View import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import red.button.flipper_navigator.core.impl.link_logic.TextRef import red.button.flipper_navigator.data.remote.GetLink import red.button.flipper_navigator.gps.LocationService import red.button.flipper_navigator.gps.gps_activation @OptIn(ExperimentalCoroutinesApi::class) @Suppress("UNUSED_PARAMETER") class MainScreenLogic() : ViewModel() { var link: String = "" val gpsSet = gps_activation.create() fun generateBt(view: View) { val linkUnzip = TextRef.create() val service = GetLink.create() val glink = viewModelScope.async { service.getPosts(linkUnzip.linkUnfolder(link)) } glink.invokeOnCompletion { if (it == null) { Log.d("link111", linkUnzip.placeFind(glink.getCompleted())[1]) } } Log.d("link111", "ok") } fun connectBt(view: View) { } // This Button i'm trying to use to start gps // fun StartNavBt(view: View) { Log.d("gps", "ok") gpsSet.start() } }
问题根源
- 手动实例化Activity导致Context为空:
gps_activation接口的create()方法直接return MainScreen(),这是手动创建Activity实例,而非系统通过生命周期流程生成的实例。手动创建的Activity不会执行onCreate(),导致context变量始终为null,调用Intent(context, ...)时触发空指针。 - ViewModel持有Activity实例存在内存泄漏风险:ViewModel的生命周期长于Activity,直接持有Activity实例会导致内存泄漏。
修复方案
1. 重构接口定义
移除接口中手动创建Activity的逻辑,仅保留行为定义:
interface gps_activation { fun start() fun stop() }
2. 修改ViewModel,用LiveData传递指令
ViewModel通过LiveData向Activity发送服务启停指令,避免直接持有Activity实例:
@OptIn(ExperimentalCoroutinesApi::class) @Suppress("UNUSED_PARAMETER") class MainScreenLogic : ViewModel() { var link: String = "" // 用于发送服务启停指令的LiveData private val _serviceAction = MutableLiveData<String?>() val serviceAction: LiveData<String?> = _serviceAction fun generateBt(view: View) { // 原有逻辑保持不变 val linkUnzip = TextRef.create() val service = GetLink.create() val glink = viewModelScope.async { service.getPosts(linkUnzip.linkUnfolder(link)) } glink.invokeOnCompletion { if (it == null) { Log.d("link111", linkUnzip.placeFind(glink.getCompleted())[1]) } } Log.d("link111", "ok") } fun connectBt(view: View) { } fun StartNavBt(view: View) { Log.d("gps", "ok") // 发送启动服务指令 _serviceAction.value = LocationService.ACTION_START } fun StopNavBt(view: View) { Log.d("gps", "stop") // 发送停止服务指令 _serviceAction.value = LocationService.ACTION_STOP } }
3. 修改Activity,响应ViewModel指令
- 移除手动维护的
context变量,直接用Activity本身(Activity继承自Context) - 观察ViewModel的LiveData,执行服务启停操作:
@Suppress("DEPRECATION") class MainScreen : AppCompatActivity(), gps_activation { private lateinit var bindingMain: ScreenMainBinding private lateinit var bindingField: TextFieldBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.POST_NOTIFICATIONS ), 0 ) bindingMain = DataBindingUtil.setContentView(this, R.layout.screen_main) bindingField = bindingMain.textField val viewModel = ViewModelProvider(this)[MainScreenLogic::class.java] bindingMain.mainview = viewModel bindingField.textfieldview = viewModel // 监听ViewModel的服务指令 viewModel.serviceAction.observe(this) { action -> action?.let { when(it) { LocationService.ACTION_START -> start() LocationService.ACTION_STOP -> stop() } // 重置指令,避免重复触发 viewModel._serviceAction.value = null } } } override fun start() { Intent(this, LocationService::class.java).apply { action = LocationService.ACTION_START // Android 8.0+建议使用startForegroundService if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(this) } else { startService(this) } } } override fun stop() { Intent(this, LocationService::class.java).apply { action = LocationService.ACTION_STOP startService(this) } } }
4. 额外注意事项
- 确保LocationService已在
AndroidManifest.xml中注册:
<service android:name=".gps.LocationService" />
- Android 8.0及以上版本,后台服务需调用
startForeground()显示通知,避免被系统杀死。
内容的提问来源于stack exchange,提问作者Red_button
相关产品推荐
相关产品推荐

