
暂无个人介绍
传说Android 7.0的市场占有率终于1%了,Android 6.0基本上人人有了。这个时候,我不得不把软件开发的SDK目标版本提升到了23。这是就要考虑实现权限的动态分配了。基于我项目用使用了RxJava,所以我就选择了RxPermissions框架来实现权限的动态分配。 一、了解权限 权限分两种:普通权限、危险权限。普通权限在AndroidManifest.xml声明就可以了,危险权限在AndroidManifest.xml声明之后,还需要在软件运行的时候,动态的获取。目前危险权限一共有26个,其他的都是普通权限。 Dangerous Permissions 共有26个: group:android.permission-group.CAMERA permission:android.permission.CAMERA group:android.permission-group.SENSORS permission:android.permission.BODY_SENSORS group:android.permission-group.MICROPHONE permission:android.permission.RECORD_AUDIO group:android.permission-group.CALENDAR permission:android.permission.READ_CALENDAR permission:android.permission.WRITE_CALENDAR group:android.permission-group.LOCATION permission:android.permission.ACCESS_FINE_LOCATION permission:android.permission.ACCESS_COARSE_LOCATION group:android.permission-group.STORAGE permission:android.permission.READ_EXTERNAL_STORAGE permission:android.permission.WRITE_EXTERNAL_STORAGE group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS permission:android.permission.READ_CONTACTS group:android.permission-group.SMS permission:android.permission.READ_SMS permission:android.permission.RECEIVE_WAP_PUSH permission:android.permission.RECEIVE_MMS permission:android.permission.RECEIVE_SMS permission:android.permission.SEND_SMS permission:android.permission.READ_CELL_BROADCASTS group:android.permission-group.PHONE permission:android.permission.READ_CALL_LOG permission:android.permission.READ_PHONE_STATE permission:android.permission.CALL_PHONE permission:android.permission.WRITE_CALL_LOG permission:android.permission.USE_SIP permission:android.permission.PROCESS_OUTGOING_CALLS permission:com.android.voicemail.permission.ADD_VOICEMAIL android.permission.ANSWER_PHONE_CALLS 一共九大分组,每个分组只要申请其中一个权限,其他权限都可以获得。最后一个是Android 8.0新添加的权限,是限制接听来电的权限。如果想查看其他权限可以查看访问权限API。 二、RxPermissions 在GitHub上的地址是:https://github.com/tbruyelle/RxPermissions。 现在主要有两个分支,如下图: 2.x分支是基于RxJava2的源码。master是基于Rxjava的源码。不管是RxJava、RxJava2对RxPermissions的使用没有影响,只是导入的依赖不一样。 RxJava、RxJava2导入的依赖分别是: compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' RxPermissions的使用 1、minSdkVersion 必须大于等于11. 2、在build.gradle中配置。 如果没有的话,添加这个。 android { repositories { jcenter() // If not already there } } 再添加依赖: 基于RxJava dependencies { compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' } 基于RxJava2 dependencies { compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' } 3、初始化 RxPermissions rxPermissions = RxPermissions.getInstance(this); 4、使用方法 申请一个权限的方法: // 假如申请调用系统拍照 rxPermissions.request(Manifest.permission.CAMERA) .subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Boolean aBoolean) { // aBoolean:true开启成功 false开启失败 } }); 申请多个权限的方法: rxPermissions.request(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION) .subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Boolean aBoolean) { // aBoolean:true开启成功 false开启失败 // 只要有一个权限申请失败,就返回false } }); 申请权限并查看申请结果的详细信息: rxPermissions.requestEach(permissions) .subscribe(new Subscriber<Permission>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Permission permission) { if (permission.granted) { Log.e("申请成功:",permission.name); } else if (permission.shouldShowRequestPermissionRationale) { Log.e("申请取消:",permission.name); } else { Log.e("申请失败:",permission.name); } } }); 这个方法,是实现返回每个权限申请的结果。返回的是Permission 类。包含权限申请结果,包含:permission.granted表示申请成功;permission.shouldShowRequestPermissionRationale表示申请取消;如果这两个都不是,表示申请被拒绝,只能在设置界面手动开启。跳转至系统设置界面的方法: /** * 获取应用详情页面intent */ private void getAppDetailSettingIntent() { Intent localIntent = new Intent(); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 9) { localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); localIntent.setData(Uri.fromParts("package", getPackageName(), null)); } else { localIntent.setAction(Intent.ACTION_VIEW); localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); localIntent.putExtra("com.android.settings.ApplicationPkgName", getPackageName()); } startActivity(localIntent); mContext.overridePendingTransitionEnter(); } github还提到了ensure、ensureEach 方法。也分别对应了返回的简单结果和详细结果。这两个方法是实现view绑定权限请求的框架RxBinding时使用的,RxBinding是要结合RxPermission使用的。RxBinding的GitHub地址 权限的请求方法,最好写在onCreate()方法里,这样在启动界面的时候,会先检测权限信息。
从合理利用内存的角度出发,在开发的过程中,用不到的file要进行回收。在需要使用系统应用时,数据的传输需要用Uri,本篇博客就是对根据Uri删除文件的知识总结。 Uri的两种形式: 1. 以“content://”开头的 2. 以“file://”开头的 在Android 7.0中,应用间的数据交互,必须以content://开头。 以“content://”开头的 context.getContentResolver().delete(uri, null, null); 以“file://”开头的 File file = new File(FileUtils.getRealFilePath(context,uri)); if (file.exists()&& file.isFile()){ file.delete(); } 先把uri转换成path后,创建文件。判断是否存在,是不是文件而不是文件夹,最后调用delete()删除。有人说,这种删除方法,会把文件内容删掉,留一个空文件,我测试的时候还没有碰到。 uri转换成path的方法,借鉴其他人的文章,现在找不到文章了。 /** * Try to return the absolute file path from the given Uri * * @param context * @param uri * @return the file path or null */ public static String getRealFilePath(final Context context, final Uri uri ) { if ( null == uri ) return null; final String scheme = uri.getScheme(); String data = null; if ( scheme == null ){ data = uri.getPath(); else if ( ContentResolver.SCHEME_FILE.equals( scheme ) ) { data = uri.getPath(); } else if ( ContentResolver.SCHEME_CONTENT.equals( scheme ) ) { Cursor cursor = context.getContentResolver().query( uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null ); if ( null != cursor ) { if ( cursor.moveToFirst() ) { int index = cursor.getColumnIndex( MediaStore.Images.ImageColumns.DATA ); if ( index > -1 ) { data = cursor.getString( index ); } } cursor.close(); } } return data; } 综合成一个方法: public void deleteUri(Context context, Uri uri) { if (uri.toString().startsWith("content://")) { // content://开头的Uri context.getContentResolver().delete(uri, null, null); } else { File file = new File(FileUtils.getRealFilePath(context,uri)); if (file.exists()&& file.isFile()){ file.delete(); } } }
在工作中有一个需要启动第三方应用的功能,需求是:判断是否安装该应用,如果安装就启动,没有的安装,则启动默认浏览器,访问一个地址来下载。 1.判断是否已安装了该应用 private boolean isInstall(Context context, String packageName ) { final PackageManager packageManager = context.getPackageManager(); // 获取所有已安装程序的包信息 List<PackageInfo> pinfo = packageManager.getInstalledPackages(0); for ( int i = 0; i < pinfo.size(); i++ ) { if(pinfo.get(i).packageName.equalsIgnoreCase(packageName)) return true; } return false; } 包名就是app目录下的build.gradle文件中的applicationId字段的值,如下图所示: 2.启动第三方应用或下载 if (isInstall(MainActivity.this,"应用包名")){ // 如果安装了该应用,启动应用 Intent LaunchIntent =getPackageManager().getLaunchIntentForPackage("应用包名"); startActivity(LaunchIntent); }else { // 如果没有安装,去默认浏览器下载 Intent intent = new Intent(); intent.setAction("android.intent.action.VIEW"); intent.setData(Uri.parse("apk下载的地址")); intent.setClassName("com.android.browser","com.android.browser.BrowserActivity"); startActivity(intent); } 如果已经安装了该应用,直接用应用包名启动应用;如果没有安装的话,使用隐式意图android.intent.action.VIEW;设置intent的data为apk下载地址的uri地址;设置启动的组件名称,默认的浏览器这个值是固定的。 了解Intent
在做Android图片上传功能的时候,获取图片的途径一般都有两种:拍照、从相册选择。 一、拍照 调用相机拍照有两种方法: 直接返回图片。 在调用相机的时候,传入uri,拍照后通过该uri来获取图片。 1.直接返回图片 private int TAKE_SMALL_PHOTO_REQUEST=0;//全局变量 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, TAKE_PHOTO_REQUEST); 就是通过Intent发出隐式意图,制定action为MediaStore.ACTION_IMAGE_CAPTURE,来调用系统的相机。并返回相机拍的图片。在onActivityResult方法里接收。 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_CANCELED) { Toast.makeText(context, "拍照取消!", Toast.LENGTH_LONG).show(); return; } if (resultCode == RESULT_OK) { switch (requestCode) { case TAKE_PHOTO_REQUEST: // 拍照返回结果 Bitmap photo = data.getParcelableExtra("data"); // 按需求处理photo break; } } } 返回的图片以bitmap的格式存放在data的key值是“date”中。取出后可进行相应的操作,比如显示、保存、上传。但是,要注意的是这是返回的bitmap是被系统用默认压缩方式压缩过的图片。那么要想获取原图或用自己的压缩方式处理怎么办呢?就要用到方法二了。 2.在调用相机的时候,传入uri,拍照后通过该uri来获取图片 //全局变量 private int TAKE_BIG_PHOTO_REQUEST=1; private Uri imageUri; imageUri = createImageUri(context);//创建存储图片的uri,该方法见下边的讲解 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_BIG_PHOTO_REQUEST); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_CANCELED) { deleteUri(imageUri, null, null);// 拍照取消,删除不用的文件 Toast.makeText(context, "拍照取消!", Toast.LENGTH_LONG).show(); return; } if (resultCode == RESULT_OK) { switch (requestCode) { case TAKE_BIG_PHOTO_REQUEST: // 直接使用之前新建的图片uri,来操作图片 // 按需求处理photo,比如显示 iv.setImageURI(imageUri); break; } } } 这种方法是向Intent中添加一条图片的uri数据,这时拍照完成后,系统会将图片存在这个uri中,在onActivityResult中,就可以直接使用这个uri操作图片了。隐式意图“MediaStore.ACTION_IMAGE_CAPTURE”和关键字“MediaStore.EXTRA_OUTPUT”也可以分别用“android.media.action.IMAGE_CAPTURE”和“output”代替。都是一个意思,只不过是不同的表示方法。 删除文件方法deleteUri(imageUri, null, null),参考我的一篇博客Android 根据Uri删除文件里的两种删除方法,文章的最后有一个综合方法,不同的方法为了兼容下边创建存储图片、获取uri的两种不同的方法。 创建存储图片、获取uri 1. 方法一 private Uri createImageUri(Context context){ Uri uri = null; if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ String name = String.valueOf(System.currentTimeMillis()); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.Media.TITLE, name); contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, name + ".jpeg"); contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); } return uri; } 创建文件和获取uri同时进行。 首先将图片的文件信息保存在ContentValues 中,在通过ContentResolver类的insert方法来创建图片文件,并获取uri。该uri是以“content://”开头的,因此,可以用在Android 7.0以上版本。 优点:简单。 缺点:只能进行外部存储,存放图片的默认文件夹Pictures。原因是: insert方法创建时只有两种存储方式: 1.EXTERNAL_CONTENT_URI,就是现在用的。 2.INTERNAL_CONTENT_URI,指向的是内部存储的根目录,而我们是访问不了的,会报错。 不熟悉外部存储和内部存储的小伙伴可以参考我的一篇博客Android内部存储与外部存储解析 希望对你有所帮助。 从原因的分析看,这种方法只能存在手机有外部存储的时候可以用,不过现在手机基本上都实现了SD内置本身就可以外部存储,不用担心这个。但是如果开发需求需要,存放在指定的文件夹怎么办,那看方法二了。 2.方法二 private Uri createImageUri(Context context,String myPath) { //创建文件 String state = Environment.getExternalStorageState(); File rootDir = state.equals(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory() : context.getCacheDir(); File folderDir = new File(rootDir.getAbsolutePath() + myPath);//myPath是图片存放的自定义的路径 if (!folderDir.exists() && folderDir.mkdirs()) { } String fileName = System.currentTimeMillis() + ".JPEG"; File tmpFile = new File(folderDir, fileName); // 调用parUri方法转成uri return parUri(tmpFile); } /** * 生成uri * @param cameraFile * @return */ private Uri parUri(File cameraFile) { Uri imageUri; String authority = context.getPackageName() + ".provider"; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { //通过FileProvider创建一个content类型的Uri imageUri = FileProvider.getUriForFile(this, authority, cameraFile); } else { imageUri = Uri.fromFile(cameraFile); } return imageUri; } 这种方法是我要实现一个图片选择器的时候,看到LuckSiege开源框架里的一个方法,也引发了我对FileProvider类的学习,之后对FileProvider进行总结。 创建文件和获取uri分两步: 1. rootDir是外部存储的根目录,第二个参数可以自定义路径,比如“/photoTest/”;当不能进行外部存储的时候,调用getCacheDir()放在应用的cache目录下。 2. 转成Uri,Uri.fromFile(cameraFile)方法,传化成Uri以file://开头,这时如果想调用系统剪裁或与其他应用进行通信,在Android 7.0以上就会报错闪退。就要在SDK版本大于24时,用FileProvider.getUriForFile(this, authority, cameraFile)方法,转化成Uri以content://开头,来适用Android7.0以上。如上边的方法parUri(File cameraFile)。FileProvider使用时需要配置一些东西,网上有很多讲解,文章末尾有一个包含有配置demo可以下载。 这样就完美又灵活的实现了创建图片文件。 二、从相册获取 调用系统相册 Intent intent = new Intent(); intent.setAction(Intent.ACTION_PICK); intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, TAKE_ALBUM_REQUEST); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); MLog.e("resultCode" + resultCode); if (resultCode == RESULT_CANCELED) { ToastUtil.showMessage(mContext, "取消设置图片!"); } if (resultCode == RESULT_OK) { switch (requestCode) { case TAKE_ALBUM_REQUEST: Uri uri = data.getData();// 获取选择图片的uri // 对uri进行处理 break; } } 调用系统图片很简单,不过一般系统图片会非常大,直接显示的时候,会报OOM。需要先进行压缩或剪裁的处理。在压缩或剪裁之前注意创建一个新的文件,处理后保存在新的文件里,否则就会处理后的图就会覆盖原图片,并且有些手机覆盖原图的时候会报错。 通过拍照、相册获取图片demo
问题:在实现ViewPager+Fragment+侧滑栏的界面时,华为搭载Android5.0以上操作系统的手机出现底部虚拟导航栏挡住布局。如下图所示: 问题解决后: 尝试 在实现这个功能的时候,我发现底部虚拟导航栏遮盖布局不同的情况对应不同的解决方法。当没有侧滑功能的时候,主要有一下两种: 1. OnCreate()方法中不能出现下边的代码: getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 就是设置导航栏半透明,这会使布局向上向下扩展至整个屏幕,导航栏则覆盖在布局上边,就会导致导航栏挡住布局。有的说法是换成设置状态栏半透明,如下边的代码: getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 这种做法其实是不好的,属于伤敌一千自伤八百。因为,我们设置这个属性一般是为了实现沉浸式状态栏的,去掉了第一种代码,就不能实现了。比如说我使用了SystemBarTint第三方框架来实现沉浸式状态栏。这时就需要用到方法2了。 2. 在布局的根布局中添加android:fitsSystemWindows=”true” 比如: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="@dimen/theme_divide_height" android:background="#3D81D6"/> </LinearLayout> 我们看一下,Android官方API对这个属性的解释: Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity. May be a boolean value, such as "true" or "false". 翻译: 布尔内部属性,基于系统窗口(如状态栏)来调整视图布局。如果为true,则调整此视图的填充,以便为系统窗口留出空间。只有在非嵌入activity中此视图才会生效。 这个方法就使系统窗口可以自动调整,可以实现需求。但是如果界面中有侧滑菜单的,并且实现了顶部导航栏透明,和底部导航栏颜色填充的话,就需要下边的方法了。 有效方法 在 style.xml 文件中的项目的主题样式中添加 <item name="android:windowDrawsSystemBarBackgrounds">false</item> 我们看一下,Android官方API对这个属性的解释: Flag indicating whether this Window is responsible for drawing the background for the system bars. If true and the window is not floating, the system bars are drawn with a transparent background and the corresponding areas in this window are filled with the colors specified in statusBarColor and navigationBarColor. Corresponds to FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS. May be a boolean value, such as "true" or "false". 翻译: 标志是指示此窗口是否负责绘制系统栏的背景。如果真正的窗口不浮,系统栏被画在这个窗口透明背景和相应领域内statusbarcolor和navigationbarcolor指定的颜色。对应于flag_draws_system_bar_backgrounds。 可以看出该属性是负责绘制系统栏的背景的,如果真正的窗口被遮盖了,设置true,则会绘制系统栏的背景,使真正的窗口上移,不被遮挡住。 如果你的项目兼容的最低版本小于21的话 ,会红线提示错误,虽然可以运行但是代码无效。解决方法是:在提示错误的代码上Alt+Enter,会提示: 选择第一个,就会自动生成适配Android 21的values文件夹:values-v21,里边有包含该属性的styles.xml文件。之前添加的报错的属性就可以删掉了。当然,你也可以自己新建文件夹,自己实现。如下图: 如果不知道项目的主题样式在哪儿,可以用下边的查找方式: 打开资源配置文件AndroidManifest.xml,跟进属性 Android:theme=”@style/AppTheme”中的style: tips:android:windowDrawsSystemBarBackgrounds在Android官方API文档版本21以上的可以查到,下边附一个我使用的文档的连接: 最新版Android官方API文档 好了,到此就结束了。希望能帮到大家。