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

硬件资源

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


SD卡

Android4.0之后增加了多存储卡的支持,故一般手机有内置存储卡和外置存储卡(即SD卡),其中外置存储卡便是可选的。获取各个存储卡的磁盘路径,可通过系统服务STORAGE_SERVICE构造StorageManager对象,再使用反射机制调用getVolumePaths内部方法获得。磁盘路径符合Environment.getExternalStorageDirectory().getPath()的,就是默认的内置存储卡,否则就是外置存储卡。具体的示例代码如下:
	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开发笔记(五十六)摄像头拍照》。
检查前置摄像头是否存在,可通过获取摄像头个数来判断,个数多于一个就表示有前置摄像头。示例代码如下:
	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开发笔记(五十九)巧用传感器》。
获取当前支持的传感器列表的示例代码如下:
	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对象,从中获得每个进程的内存使用情况。实现的示例代码如下:
	//获取设备的内存总大小,单位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卡)两种,内部存储的磁盘路径由下面代码获得:
String path = Environment.getDataDirectory().getPath();

外部存储的默认磁盘路径由下面代码获得:
String path = Environment.getExternalStorageDirectory().getPath();


获取磁盘剩余空间,以及总空间,实现的示例代码如下:
	//获取指定路径的总空间,单位字节
	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权限,可通过下面代码来检查:
	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开发笔记(四十六)手机相关事件》。
检查定位权限的示例代码如下:
			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开发笔记(五十六)摄像头拍照》。
检查拍照权限的示例代码如下:
			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开发笔记(五十七)录像录音与播放》。
检查录音权限的示例代码如下:
			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开发笔记的完整目录

你可能感兴趣的:(android,Permission,权限,剩余存储,硬件检查)