前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(七十九)资源与权限校验

Android开发笔记(七十九)资源与权限校验

作者头像
aqi00
发布2019-01-18 13:01:05
5760
发布2019-01-18 13:01:05
举报
文章被收录于专栏:老欧说安卓

硬件资源

因为移动设备的硬件配置各不相同,为了防止使用了不存在的设备资源,所以要对设备的硬件情况进行检查。一般情况下,前置摄像头、部分传感器在低端手机上是没有的,像SD卡也可能因为用户没插卡使得找不到SD卡资源。下面是校验这些硬件设备的说明:

SD卡

Android4.0之后增加了多存储卡的支持,故一般手机有内置存储卡和外置存储卡(即SD卡),其中外置存储卡便是可选的。获取各个存储卡的磁盘路径,可通过系统服务STORAGE_SERVICE构造StorageManager对象,再使用反射机制调用getVolumePaths内部方法获得。磁盘路径符合Environment.getExternalStorageDirectory().getPath()的,就是默认的内置存储卡,否则就是外置存储卡。具体的示例代码如下:

代码语言:javascript
复制
	public static String[] getVolumePaths(Context ctx) {
		String[] paths = null;
		StorageManager storMgr = (StorageManager) ctx.getSystemService(Activity.STORAGE_SERVICE);
		Method method = null;
		try {
			method = storMgr.getClass().getMethod("getVolumePaths");
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			return paths;
		}
		try {
			paths = (String[]) method.invoke(storMgr);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return paths;
	}

前置摄像头

后置摄像头对手机来说是标配,但前置摄像头就有部分低端机不支持。摄像头的详细介绍参见《Android开发笔记(五十六)摄像头拍照》。 检查前置摄像头是否存在,可通过获取摄像头个数来判断,个数多于一个就表示有前置摄像头。示例代码如下:

代码语言:javascript
复制
	private void checkCamera() {
		int cameraCount = Camera.getNumberOfCameras();
		mDesc = String.format("%s\n\n摄像头个数=%d", mDesc, cameraCount);
		for (int i=0; i<cameraCount; i++) {
			Camera camera = Camera.open(i);
			Parameters params = camera.getParameters();
			List<Size> sizes = params.getSupportedPreviewSizes();
			mDesc = String.format("%s\n%s摄像头支持的分辨率有%d种", 
					mDesc, (i==0)?"前置":"后置", sizes.size());
			for (int j=0; j<sizes.size(); j++) {
				Size size = sizes.get(j);
				mDesc = String.format("%s\n分辨率%d为:宽%d*高%d", mDesc, j+1, size.width, size.height);
			}
			camera.release();
		}
		tv_check_hardware.setText(mDesc);
	}

传感器

Android的传感器种类繁多,可是大多数手机都只支持少数几种,所以使用传感功能前要先校验当前设备是否存在对应的传感器。传感器的详细介绍参见《Android开发笔记(五十九)巧用传感器》。 获取当前支持的传感器列表的示例代码如下:

代码语言:javascript
复制
	private String[] mSensorType = {
			"加速度", "磁场", "方向", "陀螺仪", "光线", 
			"压力", "温度", "距离", "重力", "线性加速度", 
			"旋转矢量", "湿度", "环境温度", "无标定磁场", "无标定旋转矢量", 
			"未校准陀螺仪", "特殊动作", "步行检测", "计步器", "地磁旋转矢量"};
	private void checkSensor() {
		SensorManager sensroMgr = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		List<Sensor> sensorList = sensroMgr.getSensorList(Sensor.TYPE_ALL);
		mDesc = String.format("%s\n\n传感器个数=%d", mDesc, sensorList.size());
		for (int i=0; i<sensorList.size(); i++) {
			Sensor sensor = sensorList.get(i);
			mDesc = String.format("%s\n传感器%d的类型=%s,名称=%s", 
					mDesc, i+1, mSensorType[sensor.getType()-1], sensor.getName());
		}
		tv_check_hardware.setText(mDesc);
	}

存储资源

由于移动设备上资源有限,因此常常需要判断当前的剩余资源是否足够。比如说,发现剩余内存较低,则app不再进行大量消耗内存的操作,避免设备死机;又比如发现剩余磁盘空间不足,则app不再存储个头较大的图片或视频,避免设备爆盘;再比如发现当前应用的流量消耗较大,则app自动减少联网操作,避免被用户拉入黑名单。

剩余内存

获取设备的剩余内存大小,以及内存总量,可通过系统服务ACTIVITY_SERVICE构造ActivityManager对象,从中获得每个进程的内存使用情况。实现的示例代码如下:

代码语言:javascript
复制
	//获取设备的内存总大小,单位KB
	public static long getMemoryTotalSize() {
		long totalSize;
		// /proc/meminfo读出的内核信息进行解释
		String path = "/proc/meminfo";
		String content = null;
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(path), 8);
			String line;
			if ((line = br.readLine()) != null) {
				content = line;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		int begin = content.indexOf(':');
		int end = content.indexOf('k');
		// 截取字符串信息
		content = content.substring(begin + 1, end).trim();
		totalSize = Integer.parseInt(content);
		return totalSize;
	}

	//获取当前的内存剩余大小,单位KB
	public static long getMemoryLeftSize(Context ctx) {
		ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
		ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
		am.getMemoryInfo(mi);
		return mi.availMem / 1024;
	}

	//获取本app使用的内存大小,单位KB
	public static int getAppUserdMemory(Context ctx) {
		int userdMemory = 0;
		String packageName = ctx.getPackageName();
		ActivityManager actMgr = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
		// 获得系统里正在运行的所有进程
		List<RunningAppProcessInfo> runningList = actMgr.getRunningAppProcesses();
		for (RunningAppProcessInfo runningAppProcessInfo : runningList) {
			// 进程ID号
			int pid = runningAppProcessInfo.pid;
			// 用户ID
			int uid = runningAppProcessInfo.uid;
			// 进程名
			String processName = runningAppProcessInfo.processName;
			if (processName.equals(packageName) != true) {
				continue;
			}
			// 占用的内存
			int[] pids = new int[] { pid };
			MemoryInfo[] infoList = actMgr.getProcessMemoryInfo(pids);
			if (infoList.length > 0) {
				MemoryInfo info = infoList[0];
				userdMemory = info.dalvikPrivateDirty;
			}
		}
		return userdMemory;
	}
}

剩余磁盘空间

磁盘分内部存储和外部存储(即SD卡)两种,内部存储的磁盘路径由下面代码获得:

代码语言:javascript
复制
String path = Environment.getDataDirectory().getPath();

外部存储的默认磁盘路径由下面代码获得:

代码语言:javascript
复制
String path = Environment.getExternalStorageDirectory().getPath();

获取磁盘剩余空间,以及总空间,实现的示例代码如下:

代码语言:javascript
复制
	//获取指定路径的总空间,单位字节
	public static long getStorageTotalSize(String path) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
			return getStorageTotalSizeNew(path)/1024;
		} else {
			return getStorageTotalSizeOld(path)/1024;
		}
	}
	
	@SuppressWarnings("deprecation")
	private static long getStorageTotalSizeOld(String path) {
		File sdcardDir = new File(path);
		StatFs sf = new StatFs(sdcardDir.getPath());
		long blockSize = sf.getBlockSize();
		long blockCount = sf.getBlockCount();
		return blockSize*blockCount;
	}

	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
	private static long getStorageTotalSizeNew(String path) {
		File sdcardDir = new File(path);
		StatFs sf = new StatFs(sdcardDir.getPath());
		long blockSize = sf.getBlockSizeLong();
		long blockCount = sf.getBlockCountLong();
		return blockSize*blockCount;
	}

	//获取指定路径的剩余空间,单位字节
	public static long getStorageLeftSize(String path) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
			return getStorageLeftSizeNew(path)/1024;
		} else {
			return getStorageLeftSizeOld(path)/1024;
		}
	}
	
	@SuppressWarnings("deprecation")
	private static long getStorageLeftSizeOld(String path) {
		File sdcardDir = new File(path);
		StatFs sf = new StatFs(sdcardDir.getPath());
		long blockSize = sf.getBlockSize();
		long blockCount = sf.getAvailableBlocks();
		return blockSize*blockCount;
	}

	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
	private static long getStorageLeftSizeNew(String path) {
		File sdcardDir = new File(path);
		StatFs sf = new StatFs(sdcardDir.getPath());
		long blockSize = sf.getBlockSizeLong();
		long blockCount = sf.getAvailableBlocksLong();
		return blockSize*blockCount;
	}

剩余流量

Android的流量数据保存在系统文件中,每次开机都会清零,所以查看系统文件得到的已使用流量,其实只是本次开机后的流量数据。系统级别的流量文件路径是/proc/net/dev,应用级别的流量文件路径是/proc/uid_stat/uid/tcp_rcv(注意中间的“uid”要替换为数字的应用id)。 不想解析文件的话,也可以使用Android的工具类TrafficStats来读取流量,该工具的常用方法如下: getTotalRxBytes : 获取接收流量的总字节数。 getTotalTxBytes : 获取发送流量的总字节数。 getMobileRxBytes : 获取移动接收流量的总字节数。(包括2G/3G/4G流量,不包括wifi流量) getMobileTxBytes : 获取移动发送流量的总字节数。 getUidRxBytes : 获取本进程接收流量的总字节数。本进程的应用ID可调用Process.myUid()获得。 getUidTxBytes : 获取本进程发送流量的总字节数。

权限校验

获取权限列表

查看app申请了哪些permission权限,可通过下面代码来检查:

代码语言:javascript
复制
	private String[] mPerArray;
	PackageManager pm = getPackageManager();
	try {
		PackageInfo pack = pm.getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS);
		mPerArray = pack.requestedPermissions;
	} catch (NameNotFoundException e) {
		e.printStackTrace();
	}
	if (mPerArray!=null && mPerArray.length>0) {
		mDesc = String.format("%s\n当前请求的权限个数=%d", mDesc, mPerArray.length);
		for (int i=0; i<mPerArray.length; i++) {
			mDesc = String.format("%s\n权限%d的名称=%s", mDesc, i+1, mPerArray[i]);
		}
	} else {
		mDesc = String.format("%s\n请求权限列表失败", mDesc);
	}
	tv_check_permission.setText(mDesc);

不过即使app申请了必要的权限,运行时仍有可能出错,原因除了缺少对应的硬件之外,还可能是相关功能未开启,甚至可能是安全软件强行屏蔽了部分权限。检查功能的开关状态(例如数据连接、GPS等),具体例子参见《Android开发笔记(五十五)手机设备基本操作》。如果是被安全软件屏蔽权限,则app很可能会扔出运行时异常,此时在代码中加入异常捕获情节,即可即时判断拥有权限与否。 下面是几个常用业务场景的权限检查例子:

检查定位权限

判断是否能够正常定位,除了检查功能开关状态,还要检查是否存在定位提供者。定位功能的详细介绍参见《Android开发笔记(四十六)手机相关事件》。 检查定位权限的示例代码如下:

代码语言:javascript
复制
			if (checkValid("android.permission.ACCESS_FINE_LOCATION") != true) {
				return;
			} else if (checkValid("android.permission.ACCESS_COARSE_LOCATION") != true) {
				return;
			} else {
				if (SwitchUtil.getMobileDataStatus(this) == true
						|| SwitchUtil.getGpsStatus(this) == true) {
					LocationManager locMgr = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
					Criteria criteria = new Criteria();
					String bestProvider = locMgr.getBestProvider(criteria, true);
					if (bestProvider != null) {
						tv_check_permission.setText("正常定位");
					} else {
						Log.d(TAG, "bestProvider is null");
						tv_check_permission.setText("无法获取定位提供者");
					}
				} else {
            		tv_check_permission.setText("无法定位,GPS和数据连接均未开启");
				}
			}

检查拍照权限

判断是否能够正常拍照,如无法拍照则app在执行Camera的open方法时,(即打开摄像头时)会扔出异常“java.lang.RuntimeException: Fail to connect to camera service”。拍照功能的详细介绍参见《Android开发笔记(五十六)摄像头拍照》。 检查拍照权限的示例代码如下:

代码语言:javascript
复制
			if (checkValid("android.permission.CAMERA") != true) {
				return;
			} else {
				try {
					Camera camera = Camera.open();
					camera.release();
					tv_check_permission.setText("正常拍照");
				} catch (Exception e) {
					e.printStackTrace();
					tv_check_permission.setText("拍照失败:" + e.getMessage());
				}
			}

检查录音权限

判断是否能够正常录音,如无法录音则app在执行MediaRecorder的setAudioSource方法时,(即打开麦克风时)会扔出异常“java.lang.RuntimeException: setAudioSource failed.”。录音功能的详细介绍参见《Android开发笔记(五十七)录像录音与播放》。 检查录音权限的示例代码如下:

代码语言:javascript
复制
			if (checkValid("android.permission.RECORD_AUDIO") != true) {
				return;
			} else {
				try {
					MediaRecorder mRecorder = new MediaRecorder();
					mRecorder.setAudioSource(AudioSource.MIC); // 如被关闭录音权限,则setAudioSource就会扔出异常
					mRecorder.setAudioSamplingRate(10); // 设置音频的采样率,单位赫兹(Hz)
					mRecorder.setAudioChannels(2); // 设置音频的声道数。1表示单声道,2表示双声道
					mRecorder.setAudioEncodingBitRate(1000); // 设置音频每秒录制的字节数
					mRecorder.setOutputFormat(OutputFormat.DEFAULT);
					mRecorder.setAudioEncoder(AudioEncoder.AMR_NB);
					mRecorder.setMaxDuration(3000);
					mRecorder.setOutputFile(createRecordDir().getAbsolutePath());
					mRecorder.prepare();
					mRecorder.release();
					tv_check_permission.setText("正常录音");
				} catch (Exception e) {
					e.printStackTrace();
					tv_check_permission.setText("录音失败:" + e.getMessage());
				}
			}

点击下载本文用到的资源与权限校验的工程代码 点此查看Android开发笔记的完整目录

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年03月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 硬件资源
    • SD卡
      • 前置摄像头
        • 传感器
        • 存储资源
          • 剩余内存
            • 剩余磁盘空间
              • 剩余流量
              • 权限校验
                • 获取权限列表
                  • 检查定位权限
                    • 检查拍照权限
                      • 检查录音权限
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档