Android应用在尝试打开JPG文件时崩溃(Android 4.1,API 16,FileNotFoundException)
编辑: 应用崩溃是因为缺少 "All files access" 权限。正如CommonsWare在下面的评论中指出的,可以通过设置页面名为“All files access”的方式授予该权限。
注:本应用仅供个人使用。参见CommonsWare关于在何种有限情形下可能适用“All files access”权限的回答。
由于无需修改程序代码,以下内容可以忽略。
我有一个应用(仅供个人使用),在安装后会立即崩溃。该应用多年来在一系列旧设备上运行良好,但在我在一部搭载Android 16的手机上安装时却崩溃。
乍一看,问题似乎在于我的应用尚未获得访问外部存储所需的权限。然而,下面的Stack Overflow线索建议我开一个新的Stack Overflow问题,展示我当前的manifest、与崩溃相关的完整堆栈跟踪,以及触发该崩溃的代码。
Requesting storage permission (in my own app for my own personal use)
我希望得到关于我可能哪里出了问题的任何建议
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="@string/app_name"
android:allowBackup="false"
android:requestLegacyExternalStorage="true">
<activity android:name=".MyMap"
android:label="@string/app_name"
android:configChanges ="orientation"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.prepbgg.mymap.action.latlong"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="latlong"/>
</intent-filter>
</activity>
<activity android:name=".PickFromList">
</activity>
<activity android:name=".TextList">
</activity>
<activity android:name=".DisplayText"
android:configChanges="orientation">
</activity>
<activity android:name=".DisplayJpg"
android:configChanges="orientation">
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.prepbgg.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
从应用的主活动调用onCreate() 的Logcat如下:
2026-06-01 11:04:33.113 32186-32186 Hi com.prepbgg.mymap I Starting onCreate
2026-06-01 11:04:33.127 32186-32186 CompatChangeReporter com.prepbgg.mymap D Compat change id reported: 279646685; UID 10350; state: ENABLED
2026-06-01 11:04:33.144 32186-32186 MapView com.prepbgg.mymap I com.prepbgg.mymap.MyMap@849c3f5
2026-06-01 11:04:33.155 32186-32186 Model com.prepbgg.mymap I SM-A176B
2026-06-01 11:04:33.156 32186-32186 Info com.prepbgg.mymap I recycling bitmap
2026-06-01 11:04:33.168 32186-32186 m.prepbgg.mymap com.prepbgg.mymap I Explicit concurrent mark compact GC freed 1461KB AllocSpace bytes, 0(0B) LOS objects, 73% free, 2194KB/8338KB, paused 410us,1.121ms total 12.052ms
2026-06-01 11:04:33.197 32186-32186 Pixels per cell com.prepbgg.mymap I 600
2026-06-01 11:04:33.197 32186-32186 Model com.prepbgg.mymap I not known
2026-06-01 11:04:33.197 32186-32186 Pixels per cell com.prepbgg.mymap I 600
2026-06-01 11:04:33.279 32186-32186 BitmapFactory com.prepbgg.mymap E Unable to decode file: java.io.FileNotFoundException: /storage/emulated/0/maps/LR/0450E0090N/LR05100150.jpg: open failed: EACCES (Permission denied)
2026-06-01 11:04:33.283 32186-32186 TransactionExecutor com.prepbgg.mymap E Failed to execute the transaction: tId:1599214240 ClientTransaction{
tId:1599214240 transactionItems=[
tId:1599214240 LaunchActivityItem{activityToken=android.os.BinderProxy@914ef85,intent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 xflg=0x4 cmp=com.prepbgg.mymap/.MyMap },ident=134182143,info=ActivityInfo{7de2bcf com.prepbgg.mymap.MyMap},curConfig={1.15 234mcc20mnc [en_GB] ldltr sw384dp w384dp h832dp 450dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2340) mAppBounds=Rect(0, 0 - 1080, 2340) mMaxBounds=Rect(0, 0 - 1080, 2340) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0 mStageConfig=undefined mPopOver=off mOverlappingWithCutout=false} s.2023 fontWeightAdjustment=0 ff=0 bf=-1 bts=0 cst=1.0 nightDim=0 themeSeq=0},overrideConfig={1.15 234mcc20mnc [en_GB] ldltr sw384dp w384dp h832dp 450dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2340) mAppBounds=Rect(0, 0 - 1080, 2340) mMaxBounds=Rect(0, 0 - 1080, 2340) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0 mStageConfig=undefined mPopOver=off mOverlappingWithCutout=false} s.2 fontWeightAdjustment=0 ff=0 bf=-1 bts=0 cst=1.0 nightDim=0 themeSeq=0},deviceId=0,referrer=com.android.shell,procState=2,state=null,persistentState=null,pendingResults=null,pendingNewIntents=null,sceneTransitionInfo=null,profilerInfo=null,assistToken=android.os.BinderProxy@5935a30,shareableActivityToken=android.os.BinderProxy@65836a9,activityWindowInfo=ActivityWindowInfo{isEmbedded=false, taskBounds=Rect(0, 0 - 1080, 2340), taskFragmentBounds=Rect(0, 0 - 1080, 2340)}}
tId:1599214240 ResumeActivityItem{mActivityToken=android.os.BinderProxy@914ef85,procState=-1,isForward=true,shouldSendCompatFakeFocus=false}
tId:1599214240 Target activity: com.prepbgg.mymap.MyMap
tId:1599214240 ]
tId:1599214240 }
2026-06-01 11:04:33.283 32186-32186 AndroidRuntime com.prepbgg.mymap D Shutting down VM
2026-06-01 11:04:33.287 32186-32186 AndroidRuntime com.prepbgg.mymap E FATAL EXCEPTION: main
Process: com.prepbgg.mymap, PID: 32186
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.prepbgg.mymap/com.prepbgg.mymap.MyMap}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.recycle()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4710)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4941)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:222)
at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:133)
at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:103)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:80)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:3150)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loopOnce(Looper.java:273)
at android.os.Looper.loop(Looper.java:363)
at android.app.ActivityThread.main(ActivityThread.java:10060)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.recycle()' on a null object reference
at com.prepbgg.mymap.MapView.drawTile(MapView.java:495)
at com.prepbgg.mymap.MapView.showTiles(MapView.java:407)
at com.prepbgg.mymap.MapView.doDrawing(MapView.java:281)
at com.prepbgg.mymap.MapView.redrawBuffer(MapView.java:241)
at com.prepbgg.mymap.MyMap.setupMapLayout(MyMap.java:540)
at com.prepbgg.mymap.MyMap.onCreate(MyMap.java:182)
at android.app.Activity.performCreate(Activity.java:9519)
at android.app.Activity.performCreate(Activity.java:9488)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1524)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4692)
... 13 more
2026-06-01 11:04:33.306 32186-32186 Process com.prepbgg.mymap I Sending signal. PID: 32186 SIG: 9
2026-06-01 11:05:25.087 32633-32633 nativeloader com.prepbgg.mymap D Load libframework-connectivity-tiramisu-jni.so using APEX ns com_android_tethering for caller /apex/com.android.tethering/javalib/framework-connectivity-t.jar: ok
2026-06-01 11:05:25.091 32633-32633 ActivityThread com.prepbgg.mymap D setConscryptValidator
2026-06-01 11:05:25.092 32633-32633 ActivityThread com.prepbgg.mymap D setConscryptValidator - put
---------------------------- PROCESS STARTED (32633) for package com.prepbgg.mymap ----------------------------
2026-06-01 11:05:25.144 32633-32633 nativeloader com.prepbgg.mymap D Configuring clns-9 for other apk /data/app/~~M8gbulRwXA9n-355k7ejBg==/com.prepbgg.mymap-fVWWYzNT8l4gNkOC3qcLUw==/base.apk. target_sdk_version=35, uses_libraries=, library_path=/data/app/~~M8gbulRwXA9n-355k7ejBg==/com.prepbgg.mymap-fVWWYzNT8l4gNkOC3qcLUw==/lib/arm64, permitted_path=/data:/mnt/expand:/data/user/0/com.prepbgg.mymap
2026-06-01 11:05:25.146 32633-32633 CompatChangeReporter com.prepbgg.mymap D Compat change id reported: 202956589; UID 10350; state: ENABLED
2026-06-01 11:05:25.157 32633-32633 GraphicsEnvironment com.prepbgg.mymap V Currently set values for:
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V angle_gl_driver_selection_pkgs=[]
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V angle_gl_driver_selection_values=[]
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V com.prepbgg.mymap is not listed in per-application setting
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V ANGLE allowlist from config:
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V com.prepbgg.mymap is not listed in ANGLE allowlist or settings, returning default
2026-06-01 11:05:25.158 32633-32633 GraphicsEnvironment com.prepbgg.mymap V Neither updatable production driver nor prerelease driver is supported.
2026-06-01 11:05:25.208 32633-32633 CompatChangeReporter com.prepbgg.mymap D Compat change id reported: 312399441; UID 10350; state: ENABLED
---------------------------- PROCESS ENDED (32633) for package com.prepbgg.mymap ----------------------------
导致崩溃的drawFile() 方法的代码如下。
Logcat中提到的第495行是两行中第一行,读取tile1.recycle()。
private void drawTile(String strFilename, Rect rect, Canvas bufferCanvas, Paint paint){
File f = new File(strFilename);
if ( f.exists() && f!=null)
{
Bitmap tile1 = BitmapFactory.decodeFile(strFilename);
if (tile1!=null) {
bufferCanvas.drawBitmap(tile1, null, rect, paint);
}
tile1.recycle();
}
else {
Log.i("Hi","Using blue");
Bitmap tile1 = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory().getPath() + "/maps/blue.gif");
bufferCanvas.drawBitmap(tile1, null, rect, paint);
tile1.recycle();
}
}
解决方案
大致来说,我们在清单中请求的权限分为三类:
- 那些
protectionLevel为normal的权限,例如INTERNET。在大多数设备上,我们只需通过请求就能获得它们;清单中的声明是为了强化我们使用受保护内容的意图,并让用户知道我们确实有这样的意图。 - 那些
protectionLevel为dangerous的权限,例如READ_CONTACTS。这些是所谓的“运行时权限”,需要在清单中请求,并且要有专门的代码在运行时弹出对话框,要求用户授权权限。 - 其余的权限,在获取方式上各不相同。很多在设置应用的“特殊访问”区域(Apps > Special app access),由用户通过这种方式手动授予。
在你的情况下,MANAGE_EXTERNAL_STORAGE 属于第三类。如果这是一个计划在Play商店及其他地方分发的应用,目标应尽量避免 MANAGE_EXTERNAL_STORAGE。这不仅从用户体验的角度让人恼火,Google也可能不允许你分发该应用。就你的情况而言,该应用仅供个人使用,只要你愿意在“特殊访问”中为你的应用开启相应开关,你就没问题,而且你对外部存储的完整文件系统拥有访问权限。