android遇到的错误集

dialog.show()引起的android.view.WindowManager$BadTokenException错误

 

很多都是遇到过的问题,这里集合一下,答案都是百度来的。

解决Content的startActivity方法报错

BootBroadcastReceiver继承自android.content.BroadcastReceiver,处理广播事件,部分代码如下:

    public void onReceive(Context context, Intent intent) {  
 
    Intent startIPhone = new Intent(context, Iphone.class);  
    context.startActivity(startIPhone);   }  

错误日志:

02-10 13:26:11.017: DEBUG/AndroidRuntime(17173): Shutting down VM
02-10 13:26:11.017: WARN/dalvikvm(17173): threadid=1: thread exiting with uncaught exception (group=0x40015560)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): FATAL EXCEPTION: main
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): java.lang.RuntimeException: Unable to start receiver com.tmall.htc.BootBroadcastReceiver: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ActivityThread.handleReceiver(ActivityThread.java:1805)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ActivityThread.access$2400(ActivityThread.java:117)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:981)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.os.Handler.dispatchMessage(Handler.java:99)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.os.Looper.loop(Looper.java:123)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ActivityThread.main(ActivityThread.java:3683)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at java.lang.reflect.Method.invokeNative(Native Method)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at java.lang.reflect.Method.invoke(Method.java:507)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at dalvik.system.NativeStart.main(Native Method)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ContextImpl.startActivity(ContextImpl.java:621)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.content.ContextWrapper.startActivity(ContextWrapper.java:258)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.content.ContextWrapper.startActivity(ContextWrapper.java:258)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at com.tmall.htc.BootBroadcastReceiver.onReceive(BootBroadcastReceiver.java:64)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): at android.app.ActivityThread.handleReceiver(ActivityThread.java:1794)
02-10 13:26:11.048: ERROR/AndroidRuntime(17173): ... 10 more
02-10 13:26:11.078: WARN/ActivityManager(68): Force finishing activity com.tmall.htc/.Iphone

发生错误原因分析:

主要原因是

02-10 13:26:11.048: ERROR/AndroidRuntime(17173): Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

Content的startActivity方法,需要开启一个新的task。

如果使用 Activity的startActivity方法,不会有任何限制,因为Activity继承自Context,已重载了startActivity方法。

 

解决办法:

public void onReceive(Context context, Intent intent) {
		Intent startiPhone = new Intent(context, Iphone.class);
		startiPhone.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		context.startActivity(startiPhone);
		
}

按照错误提示,添加一个FLAG_ACTIVITY_NEW_TASK flag

dialog.show()引起的android.view.WindowManager$BadTokenException错误

错误日志

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@427b7270is not valid; is your activity running?
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:653)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:326)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:224)
    at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:149)
    at android.view.Window$LocalWindowManager.addView(Window.java:558)
    at android.app.Dialog.show(Dialog.java:316)

错误原因
错误原因是Dialog在show的时候必须要有一个activity作为窗口载体,上面的日志的意思是承载Dialog的activity已经被销毁了,不存在了

解决办法
1、在show之前加判断activity是否被销毁了
if(!isFinishing()){
dialog.show();
}
2、直接try catch(不推荐)
错误日志
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
错误原因
先说说上下文的使用
对话框它是我们的Activity的一部分,对话框它挂载在我们的Activity上;
getApplicationContext()这个方法得到的是Context
Activity.this 得到Context的一个子类
也就是说 Activity.this 相当于是getApplicationContext()的子类
父类有的子类一定有 - 没有 token
子类有的父类不一定有 --有 token
this 还有Activity.this和我们的getApplicationContext();
大多数情况推荐:Activity.this
解决办法
上下文大多数情况推荐:Activity.this

dialog.dismiss()引起的java.lang.IllegalArgumentException错误

错误日志

java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:383)
    at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:285)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:104)
    at android.app.Dialog.dismissDialog(Dialog.java:332)
    at android.app.Dialog.dismiss(Dialog.java:315)

错误原因
这个错误测试是测不出来的,我是加了第三方的错误统计才得以发现的,原因是由于某种原因导致Activity被杀死后又重新创建
常发生这类Exception的情形都是,有一个费时的线程操作,需要在显示一个ProgressDialog,在任务开始的时候显示一个对话框,然后当任务完成了再Dismiss对话框,如果在此期间如果Activity因为某种原因被杀掉且又重新启动了,那么当Dismiss的时候WindowManager检查发现Dialog所属的Activity已经不存在了,所以会报IllegalArgumentException: View not attached to window manager.
解决办法
从网上找了好些解决方案都不是太理想,然后就尝试着自己解决, 我是这么解决的,反正加上之后这个错误就没有再出现过,如有不对还请赐教。
重写Activity的onDestroy,将dialog置为空。

@Override
    publicvoidonDestroy() {
        super.onDestroy();
        dialog=null;
    }

读取通讯录时,用户选择拒绝,未能获取权限导致的java.lang.SecurityException: Permission Denial错误
错误日志

java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=27697, uid=10194 requires android.permission.READ_CONTACTS, or grantUriPermission()
    at android.os.Parcel.readException(Parcel.java:1465)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:185)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:137)
    at android.content.ContentProviderProxy.query(ContentProviderNative.java:413)
    at android.content.ContentResolver.query(ContentResolver.java:470)
    at android.content.ContentResolver.query(ContentResolver.java:413)

错误原因
读取通讯录时,用户选择拒绝,未能获取权限
解决办法
直接try catch 如果捕获到异常,提示用户未授于权限。

适配之图片裁剪

http://www.jianshu.com/p/c73b959b6bcf

Android 7.0系统发布后,拿到能升级的nexus 6P,就开始了7.0的适配。发现Android7.0在修改头像时候进行拍照并裁剪图片时会出现photos app崩溃。仔细分析操作步骤和流程,发现照片拍照是成功的,SD卡也能保存相关的图片信息,但是在对拍照的图片进行裁剪时候出现了photos app崩溃;如下图:

同时发现通过选择相册进行选中图片后才进行裁剪就没有问题。看一下代码:

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(Uri.fromFile(inputfile), IMAGE_UNSPECIFIED);//主要问题就在这个File Uri上面 ————代码语句A
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 180);
intent.putExtra("outputY", 180);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputfile));//定义输出的File Uri,之后根据这个Uri去拿裁剪好的图片信息 ————代码B
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, RequestCode);

上网查找了一些资料都是说Android7.0后,发现Android7.0在这个方面最大变化就在于以前适用的File Uri需要更改为Content Uri,详情可以看这里。但是这个更改是需要编译的targetSdkVersion是24的时候才有效,我们的apptargetSdkVersion是23。所以问题上面的链接解决办法应该不是这个原因(这个解决在后文解决targetSdkVersion=24的时候需要用到);

但是从文章中知道了File Uri和Content Uri的区别,然后再去分析一下为什么从相册选择图片进行裁剪是生效的?通过代码分析和debug后发现,从相册选取图片得到的Uri是Content Uri而拍照后使用的是文件路径生成的File Uri,看来问题就是出在这里,并不是说我们app的targetSDKVersion不是24就可以使用File Uri,但是photos app的targetSdkVersion可能是24导致了它接受了File Uri而崩溃,那么我们需要做的就是把File Uri换成Content Uri。这里需要提的是,直接按照这里的做法去更换Content Uri并不能生效,会提示“Can not edit image under 50*50 pixels”的错误toast提示,其实是photos app找不到Content Uri传进去的图片文件。那么我们需要换一种方式去更换Content Uri,我们在stackoverflow上面找到更换Content Uri的方法,需要注意的是不是所有的File Uri都可以转换成Content Uri,应该是多媒体相关的文件才可以。下面是代码:

public static Uri getImageContentUri(Context context, File imageFile) {
   String filePath = imageFile.getAbsolutePath();
   Cursor cursor = context.getContentResolver().query(
       MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
       new String[] { MediaStore.Images.Media._ID },
       MediaStore.Images.Media.DATA + "=? ",
       new String[] { filePath }, null);

   if (cursor != null && cursor.moveToFirst()) {
      int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
      Uri baseUri = Uri.parse("content://media/external/images/media");
      return Uri.withAppendedPath(baseUri, "" + id);
   } else {
      if (imageFile.exists()) {
      ContentValues values = new ContentValues();
  
 values.put(MediaStore.Images.Media.DATA,filePath);returncontext.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
      } else {
       return null;
      }
   }
}

 

我们需要把上文第一处代码的Uri.fromFile(inputfile)更换成getImageContentUri生成的Uri。替换上去后运行程序试试,这样修改就可以了。

更进一步:要是我们把上文的代码,把app的targetSdkVersion改成24之后,在启动相机进行拍照时候,会发现这样的错误

android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/Camera/TEMP_IMAGE1474468182889.jpg exposed beyond app through ClipData.Item.getUri();

不能拍照成功;这个时候我们就要用上文介绍的文章file:// scheme is now not allowed to be attached with Intent on targetSdkVersion 24 (Android Nougat).使用FileProvider来产生Content Uri代替File Uri,按照上面网址介绍方法替换掉就可以;

然而我们在用FileProvider.getUriForFile替换掉所有的Uri.fromFile时候,可以拍照成功了,但是在剪裁图片时候还是会出现之前的“Can not edit image under 50*50 pixels”没有办法,只能把上文的代码语句A重新更改为getImageContentUri生成Content Uri,重新运行程序;这个时候可以拍照成功,进入图片裁剪photos app里面,但是裁剪完成Save的时候photos app又崩溃了:

应该是FileProvider的属性为android:exported="false"的原因,但是这里不能改为true(会报另一个错误:java.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.SecurityException: Provider must not be exported)。

这个时候我们需要把代码语句B里面outputUri改为File Uri就可以了,最终的代码如下,调用相机拍照的代码自己替换成FileProvider就可以了:

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(getImageContentUri(context , inputfile), IMAGE_UNSPECIFIED);//自己使用Content Uri替换File Uri
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 180);
intent.putExtra("outputY", 180);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputfile));//定义输出的File Uri
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, RequestCode);

关于 Android 7.0 适配中 FileProvider 部分的总结

http://yifeng.studio/2017/05/03/android-7-0-compat-fileprovider/

常见使用场景

前面介绍的内容都是理论部分,在 开发者官方 FileProvider 部分 都有所介绍。接下来我们看看,实际开发一款应用的过程中,会经常遇见哪些 FileProvider 的使用场景。

自动安装文件

版本更新完成时打开新版本 apk 文件实现自动安装的功能,应该是最常见的使用场景,也是每个应用必备功能之一。常见操作为,通知栏显示下载新版本完毕,用户点击或者监听下载过程自动打开新版本 apk 文件。适配 Android 7.0 版本之前,我们代码可能是这样:

File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");
Intent installIntent = new Intent(Intent.ACTION_VIEW);
   installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   installIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-
   archive");
startActivity(installIntent);

现在为了适配 7.0 及以上版本的系统,必须使用 Content URI 代替 File URI。

在 res/xml 目录下新建一个 file_provider_paths.xml 文件(文件名自由定义),并添加子目录路径信息:




然后在 Manifest 文件中注册 FileProvider 对象,并链接上面的 path 路径文件:



修改 java 代码,根据 File 对象生成 Content URI 对象,并授权访问:

File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");
Uri apkUri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID+".myprovider", apkFile);
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(installIntent);

如此这般,便完成了应用中调用系统功能打开 apk 文件的 7.0 适配工作。

调用系统拍照

调用系统拍照功能时也需要传递一个 Uri 对象,用于保存图片至指定目录,这里也需要适配 7.0 版本。其他步骤不再赘述,核心 java 代码如下(路径不同,注意添加 res/xml 中的 path 文件子目录):

String filePath = Environment.getExternalStorageDirectory() + "/images/"+System.currentTimeMillis()+".jpg";
File outputFile = new File(filePath);
if(!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdir();
}
Uri contentUri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".myprovider", outputFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
startActivityForResult(intent, REQUEST_TAKE_PICTURE);

调用系统裁剪

调用系统裁剪的过程中涉及到两个 Uri 对象:inputUri 和 outputUri,较为复杂一些。通常,调用系统裁剪的来源为调用系统拍照或选择系统相册。前者返回的是一个 File URI 对象,后者返回的是一个 Content URI 对象。作为裁剪源,我们要做的就是对其做进一步处理。但是不能像上面那样使用 getUriForFile() 方法,这个并不难理解,因为如果是选择系统相册所得的图片,本身也不一定属于我们自己的应用。正确处理方式是这样:

private Uri getImageContentUri(String path){
   Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
     newString[]{MediaStore.Images.Media._ID},
     MediaStore.Images.Media.DATA + "=? ",
     newString[]{path}, null);
     if(cursor != null && cursor.moveToFirst()) {
        intid = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));
        Uri baseUri = Uri.parse("content://media/external/images/media");
        return Uri.withAppendedPath(baseUri, ""+id);
     }  else {
        ContentValues contentValues = new ContentValues(1);
        contentValues.put(MediaStore.Images.Media.DATA, path);
        return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
        contentValues);
     }
}

拿到正确的 Content URI 后,作为 inputUri,传递给 Intent 对象:

Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(inputUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile));
startActivityForResult(intent, REQUEST_PICK);

注意:这里的 outputUri 并没有改变,仍然使用的是 Uri.fromFile() 方法获取的 File URI 类型!这是很奇怪的一点,但是不得不这么做。事实上,使用这种方式调用系统裁剪功能本身就是有问题的!常见问题如:在部分机型上,调用系统裁剪并返回前一个页面时,在 onActivityResult() 方法中得到的 resultCode 值不等于 RESULT_OK。Crop Intent 在官方文档中本来就无迹可寻,本身就是一种不推荐的用法!取而代之的是,我们可以使用 GitHub 上的一些开源库实现应用内的图片裁剪功能,比如 uCropcropper 等。

你可能感兴趣的:(android,bug)