我们知道,Android系统的各个模块提供了很强大的功能(比方电话,电源和设置等),通过使用这些功能。应用程序能够表现的更强大。更灵活。只是,使用这些功能并非无条件的。而是须要拥有一些权限。接下来,我们就開始解说还有一个很重要的知识点——应用程序权限声明。当中主要包含应用程序的权限声明,自己定义应用程序的訪问权限和SDK版本号限定。
1.<uses-permission>——应用程序的权限申请
权限 | 描写叙述 |
android.permission.ACCESS_NETWORK_STATE | 同意应用程序訪问网络状态 |
android.permission.ACCESS_WIFI_STATE | 同意应用程序訪问WI-FI状态信息 |
com.android.voicemail.permission.ADD_VOICEMALL | 同意应用程序往系统中加入一封语音邮件 |
android.permission.BATTERY_STATS | 同意应用程序更新手机电池统计信息 |
android.permission.BIND_APPWIDGET | 同意应用程序通知AppWidget服务哪个应用程序能够訪问AppWidget的数据 Launcher是使用此权限的一个实例 |
android.permission.BLUETOOTH | 同意应用程序连接一个已经配对的蓝牙设备 |
android.permission.BLUETOOTH_ADMIN | 同意应用程序主动发现和配对蓝牙设备 |
android.permission.BROADCAST_PACKAGE_REMOVED | 同意医用程序发送应用程序包已经卸载的通知 |
android.permission.BROADCAST_SMS | 同意应用程序广播短信回执通知 |
android.permission.BROADCAST_STICKY | 同意应用程序广播Sticky Intent。 有些广播的数据在其广播完毕后被放在系统中,这样应用程序能够高速訪问它们的数据,而无需等到下一个广播到来 |
android.permission.CALL_PHONE | 同意应用程序初始化一次电话呼叫 |
android.permission.CAMERA | 请求訪问摄像设备 |
android.permission.CHANGE_CONFIGURATION | 同意应用程序改动当前的配置,比方语言种类,屏幕方向等。比方,我们的设置模块就使用了这个权限 |
android.permission.CHANGE_NEWWORK_STATE | 同意应用程序改变连接状态 |
android.permission.CHANGE_WIFI_STATE | 同意应用程序改变WI-FI连接状态 |
android.permission.DEVICE_POWER | 同意应用程序訪问底层设备电源管理 |
android.permission.EXPAND_STATUS_BAR | 同意应用程序展开或者收起状态栏 |
android.permission.INSTALL_LOCATION_PROVIDER | 同意应用程序安装一个数据提供者到本地管理器中 |
android.permission.INSTALL_PACKAGES | 同意应用程序安装还有一个应用程序 |
android.permission.INTERNET | 同意应用程序打开网络。 假设希望开发一个和网络相关的应用程序,那么首先应该考虑是否须要这个权限 |
android.permission.KILL_BACKGROUND_PROCESSES | 同意应用程序调用killBackgroundProcesses()方法 |
android.permission.MODIFY_PHONE_STATE | 同意改动电话状态,但不包含拨打电话 |
android.permission.MOUNT_FORMAT_FILESYSTEMS | 同意应用程序格式化可移除的外部存储设备 |
android.permission.MOUNT_UNMOUNT_FILESYSTEMS | 同意应用程序挂载或者卸载外部存储设备 |
android.permission.NFC | 同意应用程序运行NFC的输入输出操作 |
android.permission.READ_CALENDAR | 同意应用程序读取日历的数据 |
android.permission.READ_CONTACTS | 同意应用城西读取联系人的数据 |
android.permission.READ_PHONE_STATE | 同意应用程序訪问电话状态 |
android.permission.READ_SMS | 同意应用程序訪问短信信息 |
android.permission.RECEIVE_BOOT_COMPLETED | 同意应用程序在系统完毕以后接受到android.intent.action.BOOT_COMPLETED广播 |
android.permission.RECEIVE_MMS | 同意应用程序监控MMS |
android.permission.RECEIVE_SMS | 同意应用程序监控SMS |
android.permission.RECEIVE_WAP_PUSH | 同意应用程序监控WAP的推送信息 |
android.permission.SEND_SMS | 同意应用程序主动发送短息 |
android.permission.SET_TIME | 同意应用程序设置系统时间 |
android.permission.SET_TIME_ZONE | 同意应用程序设置系统时区 |
android.permission.SET_WALLPAPER | 同意应用程序设置桌面壁纸 |
android.permission.STATUS_BAR | 同意应用程序操作(打开,关闭,禁用)状态栏和它的图标 |
android.permission.VIBRATE | 同意应用程序同意应用程序訪问振动设备 |
android.permission.WAKE_LOCK | 同意应用程序使用电源管理器的屏幕锁功能 |
android.permission.WRITE_CALENDAR | 同意用户写入日历数据。假设我们仅仅申请了这个权限。那么我们对日历的数据仅仅有写权限没有读权限 |
android.permission.WRITE_CONTACTS | 同意用户写入联系人数据,假设我们仅仅申请了这个权限,那么我们对联系人的数据仅仅有写入权限,没有读权限 |
android.permission.WRITE_EXTERNAL_STORAGE | 同意应用程序把数据写入外部存储设备 |
android.permission.WRITE_SETTINGS | 同意应用程序读写系统设置 |
android.permission.WRITE_SMS | 同意应用程序写短信 |
应用程序在不同的场景下可能须要上表所看到的的某些权限,比方当我们须要使用SD卡时,则须要申请SD卡相关权限。
以下我们举例来解释这个问题。
在这个实例中。我们将改造HelloWorld应用程序,并在sdcard的根文件夹下加入一个名为“abc.txt”的文本文件。因为须要訪问外部存储设器。因此须要申请android.permission.WRITE_EXTERNAL_STORAGE权限,否则代码将会失败。详细过程例如以下所看到的。
①须要在HelloWorld应用程序的AndroidManifest.xml文件里加入对应的权限,例如以下列代码所看到的:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
②要在原来的代码中加入一些创建文件的代码,例如以下所看到的:
public class MainActivity extends FragmentActivity {
private static final String SDCARD= Environment.getExternalStorageDirectory()+File.separator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState==null){
getSupportFragmentManager().beginTransaction().add(android.R.id.content,new FileFragment()).commit();
}
}
public static class FileFragment extends Fragment {
public FileFragment(){}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.file_fragment,container,false);
Button mybut= (Button) view.findViewById(R.id.mybut);
mybut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File sdcardFile=new File(SDCARD+"abc.txt");
try {
sdcardFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
});
return view;
}
}
}
③启动程序,这时在sdcard所链接的文件夹下。我们会发现已经建立了abc.txt文件,例如以下图所看到的。
最后我们来做一个实验。将AndroidManifest.xml文件里的<uses-permission>节点删除,在同样的文件夹下并没有发现所创建的文件,这样操作之后,我们就能在日志里面发现一些异常信息。例如以下图所看到的。
从日志中我们能够发现程序抛出了java.io.IOException异常,而且提示“Permission denied”。而它发生的地方,正好是创建文件的地方。
因此,我们能够得出一个结论,在试图读写外部存储设备的时候。必须先要申请android.permission.WRITE_EXTERNAL_STORAGE这个权限,否则程序抛出警告性异常,相关操作也就无法进行。
Android提供了丰富的软硬件功能模块,它能让应用程序变得强大。开发过程也更便捷,但在使用前,必需要为应用程序申请必要的权限。
这点很重要,否则应用程序就会出现莫名其妙的错误。
正如前面的实例中看到的结果。假设没有对应的权限,就无法创建文件,而程序并没有显示一个异常的提示。这时我们就可能要花费大量的时间去找问题的根源。
因此,本人建议大家开发之前细致分析需求,分析应用为什么功能,而这些动能是否须要权限才干够訪问。
2.<permission>节点——自己定义应用程序的訪问权限
前面我们学习了怎样使用权限。事实上,应用程序除了能够使用权限之外,还能够定义自己的权限,用来限制对本应用程序或其它应用程序的特殊组件或功能訪问。
在这里,我们来学习<permission>节点的作用——怎样声明自己的权限。
手动向AndroidManifest.xml文件里加入一个<permission>节点。它仅仅能包括在<manifest>节点下,其语法例如以下所看到的:
<permission android:description="string resource"
android:icon="drawable resource"
android:logo="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal"|"dangerous"|"signature"|"signatureOrSystem"]/>
以上代码中,须要说明的有下面3个属性。
①android:name:声明权限的名称。这个名称必须是唯一的,因此,应该使用Java风格的命名。比方com.test.permission.TEST。
②android:permissionGroup:声明权限从属于哪一个权限组。这个权限组能够是Android预编译的,也能够是自己定义的。下表列除了Android系统预编译的系统权限组。
权限组名称 | 描写叙述 |
android.permission-group.ACCOUNTS | 用于直接訪问由帐号管理器管理的帐号 |
android.permission-group.COST_MONEY | 用于使用户不须要直接參与就可花钱的权限 |
android.permission-group.DEVELOPMENT_TOOLS | 与开发特征相关的权限群 |
android.permission-group.HARDWARE_CONTROLS | 用于提供直接訪问设备硬件的权限 |
android.permission-group.LOCATION | 用于同意用户訪问用户当前位置的权限 |
android.permission-group.MESSAGES | 用于同意应用程序以用户的名义发送信息或者拦截用户收到的信息的权限 |
android.permission-group.NETWORK | 适用于提供网络服务的訪问权限 |
android.permission-group.PERSONAL_INFO | 适用于提供訪问到用户私人数据的权限,如联系人,日历事件和电子邮件信息 |
android.permission-group.PHONE_CALLS | 适用于关联訪问和改动电话状态的权限。比方拦截去电,读取和改动电话的状态 |
android.permission-group.STORAGE | 与SD卡訪问有关的权限组 |
android.permission-group.SYSTEM_TOOLS | 与系统API有关的权限组 |
③android:protectionLevel:描写叙述了隐含在权限中的潜在风险,该属性的值能够是下表中的一个字符串。
值 | 意义 |
normal | 默认值。
低风险的权限,它能够使请求的应用程序訪问孤立的应用程序级的功能,给其它应用程序,系统或者用户带来最小的风险。系统在安装时,会自己主动授予这样的类型的权限给请求的应用程序,无须用户明白声明。 |
dangerous | 高风险权限,它将事请求的应用程序能訪问用户的私有数据或者控制那些会对用户产生负面影响的设备。因为这样的权限存在潜在的风险,系统可能不会自己主动被赋予请求的应用程序。
比如,不论什么一个由应用程序请求的危急权限可能会显示给用户,而且在处理之前被要求确认。比如。下面权限就属于此类权限。 <permission android:name="android.permission.RECEIVE_SMS" |
signature | 签名级别。系统仅仅在请求的应用程序用相同的签名作为声明权限时才授予该权限。假设认证匹配。则系统不通知用户或者无须用户明白批准就能够自己主动授权。 |
signatureOrSystem | 签名或者系统级别。系统只将它授给Android系统镜像文件(*.img文件)中的应用程序,或者是和系统镜像中的那些用相同认证签名的应用程序。
普通情况下,尽量避免使用该选项,这是因为signature保护层级应满足大多数需求和project,而无论应用程序被确切地安装在何处。signatureOrSystem权限适用于特定的特殊环境,在这种环境里,多个厂商已经将应用程序构建到系统镜像中,而且须要明白共享特定特征。 |
在<permission>节点中。除了上面介绍的3个属性外。还有其它一些属性仅仅是为了便于阅读而存在,这里我们不在具体介绍。
在Android系统提供的应用程序中,有一些定义了自己的权限,比方Launcher。
以下的代码片段用Launcher 的AndroidManifest.xml片段来说明怎样手动声明自己的权限:
<permission
android:name="com.android.launcher.permission.INSTALL_SHORT-CUT"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_install_shortcut"
android:description="@string/perdesc_install_shortcut"/>
由于声明权限这一功能使用频率比較低。因此读者在开发应用程序的时候须要思考是否有必要声明自己的权限。
3.<uses-sdk>节点——SDK版本号限定
大家都知道,软件对于平台版本号是的一定要求的。假设平台版本号可以达到软件执行的要求,那就能保证软件的稳定性。
比方,大家都知道NFC功能是不能在Android 1.5中执行的。假设正在开发带类似功能的应用程序,那就必须对所在平台有所要求。而<uses-sdk>节点正是用来满足这样的需求的。
<uses-sdk>节点使用一个整型值来表达应用程序与一个或多个Android平台版本号的兼容性。值得注意的是,这些整型值代表的是API级别。应用程序给定的API级别将和一个给定Android系统的API级别做比較。当然。对于不同的Android设备,这可能会有所不同。须要说明的是,这个节点用于指定API级别,而不是用于指定SDK的版本号号或Android平台的。
使用此节点的代码例如以下所看到的:
<uses-sdk android:minSdkVersion="integer"
android:targetSdkVersion="integer"
android:maxSdkVersion="integer"/>
①android:minSdkVersion:用于指定要执行应用程序所需的最小API级别。假设系统的API级别比该属性指定的值要小,则Android系统会阻止用户安装此应用。在大多数情况下。应指定这个属性。
假设没有指定该属性。那么系统会觉得此属性值为“1”。
②android:targetSdkVersion:用于指定应用程序的目标API级别。
③android:maxSdkVersion:指定最大的API级别。
4.<instrumentation>节点——应用的监控器
<instrumentation>节点用于监控应用程序与系统交互,它会在应用程序组件实例化之前被实例化。这个节点在多数情况下用于单元測试。其语法结构例如以下:
<instrumentation android:functionalTest=["true"|"false"]
android:handleProfiling=["true"|"false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:targetPackage="string">
以下具体说明这些属性:
①android:functionalTest:标识这个Instrumentation类是否作为一个功能測试执行,它的默认值是false。
②android:handleProfiling:标识这个Instrumentation对象是否打开和关闭性能分析,它的默认值是false。
③android:icon:这个属性代表这个Instrumentation类的图标
④android:label:该属性是Instrumentation类的标题。
⑤android:name:该属性是Instrumentation子类的名称,是一个类的Java风格的名称。比如com.example.liyuanjinglyj.myInstrumentation。
⑥android:targetPackage:该属性是须要监控的目标应用程序名称。这个名称来自与目标应用程序AndroidManifest.xml中<manifest>节点的package属性值。
当然在eclipse须要专门创建一个測试项目。并且还要配置这个节点,可是在android studio并不须要这么做,考虑到2015年底谷歌不再支持eclipse。所以仅仅解说android studio单元測试。而解说这个节点是由于到如今eclipse依旧有大量的开发人员使用,所以,解说了节点的具体说明。
以下举例说明怎样进行单元測试:
①创建项目,依旧拿我们一直使用的HelloWorld做实验,你会发现项目文件夹里面有这样一个測试包:
②右键点击这个包。选择NEW-CLASS菜单项。新建MyFirstTest继承自ActivityInstrumentationTestCase2<T>,例如以下图所看到的:
package com.example.liyuanjing.helloworld;
import android.test.ActivityInstrumentationTestCase2;
/**
* Created by liyuanjing on 2015/7/14.
*/
public class MyFirstTest extends ActivityInstrumentationTestCase2<MainActivity>{ public MyFirstTest(){
super(MainActivity.class);
}
}
在这段代码中,ActivityInstrumentationTestCase2<T>中的<T>要换成我们測试的类,由于我们要測试MainActivity所以换成这个。
③我们測试内容为。在编辑框中输入一些字符,当点击button后,文本框等于编辑框输入的字符串,MainActivity代码例如以下:
public class MainActivity extends FragmentActivity {
private static final String SDCARD= Environment.getExternalStorageDirectory()+File.separator;
private FileFragment fileFragment=new FileFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState==null){
getSupportFragmentManager().beginTransaction().add(android.R.id.content,fileFragment).commit();
}
}
public static class FileFragment extends Fragment {
private View mRootView;
public FileFragment(){}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.file_fragment,container,false);
final EditText myEdit=(EditText)view.findViewById(R.id.myEdit);
final TextView content=(TextView)view.findViewById(R.id.content);
Button myBut=(Button)view.findViewById(R.id.myBut);
myBut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
content.setText(myEdit.getText().toString());
}
});
this.mRootView=view;
return view;
}
public View getRootView(){
return this.mRootView;
}
}
public FileFragment getFragment(){
return fileFragment;
}
}
測试类的代码例如以下:
public class MyFirstTest extends ActivityInstrumentationTestCase2<MainActivity>{
private TextView content;
private EditText myEdit;
private Button myBut;
private MainActivity mainActivity;
public MyFirstTest(){
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
this.mainActivity=(MainActivity)getActivity();
this.content=(TextView)this.mainActivity.getFragment().getRootView().findViewById(R.id.content);
this.myEdit=(EditText)this.mainActivity.getFragment().getRootView().findViewById(R.id.myEdit);
this.myBut=(Button)this.mainActivity.getFragment().getRootView().findViewById(R.id.mybut);
}
public void testButtonClick(){
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_H);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_E);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_L);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_L);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_O);
getInstrumentation().waitForIdleSync();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
myBut.performClick();
}
});
getInstrumentation().waitForIdleSync();
SystemClock.sleep(1000);
assertEquals(this.content.getText().toString(),this.myEdit.getText().toString());
}
}
注意:
Ⅰ在MyFirstTest类的构造放中,因为指定了被測试项目的Activity的具体信息,就使得MyFirstTest与Activity关联上。
ⅡsetUp()方法是一个重写的方法,它的作用是在測试前做必要的设置,比方实例化一些对象。打开网络等,它与tearDown()方法成对出现。这里我们没有实现tearDown()方法,当測试完毕以后。框架会自己主动回调基类,也就是tearDown()方法。
最后,执行測试类,例如以下图所看到的:
当系统完毕測试后,就能够在Android studio集成开发环境中得到下图所看到的的结果:
在图中能够看到。编写的Test測试类返回值为OK。这就证明測试达到了预期的目的。