HostApduService访问文件抛出NullPointerException问题求助
核心问题:如何在HostApduService中访问文件并避免NullPointerException?
问题概述
实现信用卡模拟应用时,已成功搭建HostApduService,但尝试在服务中访问内部/外部存储文件加载卡片数据时,触发java.lang.NullPointerException——调用getFilesDir()时Context为空,getBaseContext()和getApplicationContext()也返回null。
错误堆栈日志
E/AndroidRuntime: FATAL EXCEPTION: main Process: de.androidcrypto.hcecreditcardemulator, PID: 26574 java.lang.RuntimeException: Unable to instantiate service de.androidcrypto.hcecreditcardemulator.HceService: java.lang.NullPointerException: Attempt to invoke virtual method 'java.io.File android.content.Context.getFilesDir()' on a null object reference at android.app.ActivityThread.handleCreateService(ActivityThread.java:3529) at android.app.ActivityThread.-wrap4(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1786) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6944) 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: Attempt to invoke virtual method 'java.io.File android.content.Context.getFilesDir()' on a null object reference at android.content.ContextWrapper.getFilesDir(ContextWrapper.java:237) at de.androidcrypto.hcecreditcardemulator.HceService.<init>(HceService.java:13) at java.lang.Class.newInstance(Native Method) at android.app.ActivityThread.handleCreateService(ActivityThread.java:3526) at android.app.ActivityThread.-wrap4(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1786) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6944) 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)
问题代码片段
HceService.java(原代码)
package de.androidcrypto.hcecreditcardemulator; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; import java.io.File; public class HceService extends HostApduService { public HceService() { // check for a specific file in internal storage System.out.println("start HceService"); File file = getFilesDir(); //File file2 = getExternalFilesDir(null); System.out.println("file exists: " + file.exists()); } @Override public byte[] processCommandApdu(byte[] bytes, Bundle bundle) { return new byte[0]; } @Override public void onDeactivated(int i) { } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- Card emulation was introduced in API 19. --> <!-- Min/target SDK versions (<uses-sdk>) managed by build.gradle --> <uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> <uses-permission android:name="android.permission.NFC" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.HceCreditCardEmulator" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- BEGIN_INCLUDE(CardEmulationManifest) --> <!-- Service for handling communication with NFC terminal. --> <service android:name=".HceService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"> <!-- Intent filter indicating that we support card emulation. --> <intent-filter> <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <!-- Required XML configuration file, listing the AIDs that we are emulating cards for. This defines what protocols our card emulation service supports. --> <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/aid_list"/> </service> <!-- END_INCLUDE(CardEmulationManifest) --> </application> </manifest>
aid_list.xml
<?xml version="1.0" encoding="utf-8"?> <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"> <aid-group android:description="@string/card_title" android:category="payment"> <aid-filter android:name="F222222222"/> <aid-filter android:name="325041592E5359532E4444463031"/> <aid-filter android:name="A0000000031010"/> </aid-group> <!-- END_INCLUDE(CardEmulationXML) --> </host-apdu-service>
解决方案
核心原因
Android Service的Context在构造函数阶段尚未完成初始化。系统创建Service的流程是:先调用无参构造函数实例化对象,之后才会将Context关联到Service并调用onCreate()生命周期方法。因此在构造函数中调用getFilesDir()、getApplicationContext()等依赖Context的方法时,Context仍为null,必然触发NullPointerException。
修改后的代码
将文件访问逻辑移到onCreate()方法中,此时Context已经初始化完成:
package de.androidcrypto.hcecreditcardemulator; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; import android.util.Log; import java.io.File; public class HceService extends HostApduService { public HceService() { Log.d("HceService", "Service constructor called"); // 这里不要调用任何依赖Context的方法 } @Override public void onCreate() { super.onCreate(); Log.d("HceService", "Service onCreate called"); // 此时Context已初始化,可以安全访问文件 File internalFilesDir = getFilesDir(); Log.d("HceService", "Internal files dir exists: " + internalFilesDir.exists()); // 访问外部存储示例(需注意权限,Android 10+需适配分区存储) // File externalFilesDir = getExternalFilesDir(null); // Log.d("HceService", "External files dir exists: " + externalFilesDir.exists()); } @Override public byte[] processCommandApdu(byte[] bytes, Bundle bundle) { return new byte[0]; } @Override public void onDeactivated(int i) { } }
补充说明
- 如果需要在
processCommandApdu()中访问文件,可以在onCreate()中提前加载文件数据并缓存到成员变量中,避免每次处理APDU时重复读取文件。 - 若访问外部存储,需根据Android版本申请对应权限(Android 6+需动态申请
WRITE_EXTERNAL_STORAGE,Android 10+建议使用Scoped Storage)。
内容的提问来源于stack exchange,提问作者Michael Fehr
相关产品推荐
相关产品推荐

