Android的permission机制想必大家并不陌生。在开始主要内容之前,我们来简单回顾一下吧。
1.Android为每个应用程序分配一个唯一的UID,以确保每个应用程序有独立的空间,使它与其它应用程序分隔开来,避免其他应用程序进行非授权的访问。
2.Android可以为其几大组件定义permission,包括Activity, Service, ContentProvider, BroadCast Intent.
3.在manifest中定义或声明使用permission.
下面给一些例子,便于理解和使用上面说的几点:
1.声明自定义permission
<permission android:name=“com.example.perm.READ_INCOMING_MSG”
android:label=“Read incoming messages from the EX service.”
android:description=“Allows the app to access any messages received by
the EX service. Any app granted this permission will be able to read all
messages processed by the ex1 application.”
android.protectionLevel=“dangerous”
android:permissionGroup=“android.permission-group.PERSONAL_INFO”
/>
2.使用权限
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.testapps.test1”>
...
<uses-permission android:name=“android.permission.INTERNET” />
...
</manifest>
3.为Activity添加permission
<activity android:name=“.Activity1”
android.permission=“com.example.perm.START_ACTIVITY”>
<intent-filter>
...
</intent-filter>
</activity>
添加后,只有声明使用 com.example.perm.START_ACTIVITY的应用才能启动此Activity.
4.为Service添加permission,类似于Activity。
<service android:name=“.MailListenerService”
android:permission=“com.example.perm.BIND_TO_MSG_LISTENER”
android:enabled=“true”
android:exported=“true”
<intent-filter></intent-filter>
</service>
5.为ContentProvider添加permission,这个应用的最为广泛。
a.在provider中添加权限。我们添加了读权限和写权限。
<provider android:name=“com.example.test.app1.MailProvider”
android:authorities=“com.example.test.app1.mailprovider”
android:readPermission=“com.example.perm.DB_READ”
android:writePermission=“com.example.perm.DB_WRITE”>
</provider>
b.有时候我们需要主动grant权限给其他应用。
<provider android:name=“com.example.test.app1.MailProvider”
android:authorities=“com.example.test.app1.mailprovider”
android:readPermission=“com.example.perm.DB_READ”
android:writePermission=“com.example.perm.DB_WRITE”
android:grantUriPermission=“true”>
</provider>
上面我们主动grant了整个provider的权限给其他应用。我们还可以只grant其中的某个子路径给其他应用。
<provider android:name=“com.example.test.app1.MailProvider”
android:authorities=“com.example.test.app1.mailprovider”
android:readPermission=“com.example.perm.DB_READ”
android:writePermission=“com.example.perm.DB_WRITE”>
<grant-uri-permission android:path=“/attachments/”>
</provider>
c.在程序中动态的grant URI权限给Action接收者。
uri = “content://com.example.test.provider1/attachments/42”;
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, “image/gif”);
startActivity(intent);
d.在程序中动态的将权限grant给某一指定应用。
uri = “content://com.example.test.provider1/attachments/42”;
Context.grantUriPermission(“com.example.test.app2”, uri,intent.FLAG_GRANT_READ_URI_PERMISSION);
6.为Broadcast添加permisson。
a.在发送Broadcast时指定permission
Intent bcastIntent = new Intent(MESSAGE_RECEIVED);
context.sendBroadcast(bcastIntent, “com.example.perm.MSG_NOTIFY_RCV”);
b.在注册receiver时指定permission
IntentFilter intentFilter = new IntentFilter(MESSAGE_RECEIVED);
UIMailBroadcastReceiver rcv = new UIMailBroadcastReceiver();
context.registerReceiver(rcv, intentFilter,“com.example.perm.MSG_NOTIFY_SEND”, null);
7.在程序中动态检查是否拥有某一权限(不鼓励使用)
int canProcess = checkCallingOrSelfPermission(“com.example.perm.READ_INCOMING_MSG”);
if (canProcess != PERMISSION_GRANTED) throw new SecurityException();
在我们使用权限的过程中,经常会碰到android:protectionLevel这个域。它称为permission level:
Android permissions fall into four levels:
Normal – These permissions cannot impart real harm to the user (e.g. change the wallpaper) and, while apps need to requestthem, they are automatically granted.
Dangerous – These can impart real harm (e.g. call numbers,open Internet connections, etc) and apps need to request them with user confirmation.
Signature – These are automatically granted to a requesting app. if that app is signed by the same certificate (so, developed by thesame entity) as that which declared/created the permission. This level is designed to allow apps that are part of a suite, or otherwise related, to share data.
Signature/System – Same as Signature, except that the system image gets the permissions automatically as well. This is designed for use by device manufacturers only.
当某一系统的provider权限被声明为signature时,我们第三方的应用程序是无法访问到的,但如果它grant了其中的某些子路径,那么这些子路径就可以访问。
在JellyBean中,我们看到DownloadProvider的ALL_ACCESS_DOWNLOADS权限被声明为signature保护方式,但幸运的是all_download这个子路径是被grant出来的。
所以第三方的应用程序是可以接收share intent来访问它的。那么下面就到了我们要重点讨论的问题:grant URI的有效期限问题。
Grant URI的有效期限
正如上面我们描述的情形,当我们拿到了DownloadProvider给我们的权限时(前提是我们注册了接收ACTION_SEND的activity),在我们的Activity创建时可以接收到
这个URI来进行一系列操作。下面给出一段测试代码:
HelloActivity.java
package com.xm.permission;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class HelloActivity extends Activity {
private Uri mUri;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = getIntent();
if (intent.getAction() == Intent.ACTION_SEND) {
mUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
}
if (mUri == null) {
return;
}
InputStream inputStream;
try {
inputStream = getContentResolver().openInputStream(mUri);
FileOutputStream fileOutputStream = new FileOutputStream(new File("/sdcard/test.bin"));
int length;
byte[] buffer = new byte[1440];
while ((length = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.flush();
fileOutputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xm.permission"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".HelloActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:label="@string/app_name" >
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
运行上面的code,我们可以成功的写一个文件到SDCard上。也许这简单的不能再简单了,但我们的问题来了,看下面:
我们添加了另外一个activity:
package com.xm.permission;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class Hello2 extends Activity {
private Uri mUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = getIntent();
mUri = intent.getData();
if (mUri == null) {
return;
}
InputStream inputStream;
try {
inputStream = getContentResolver().openInputStream(mUri);
FileOutputStream fileOutputStream = new FileOutputStream(new File("/sdcard/test23.bin"));
int length;
byte[] buffer = new byte[1440];
while ((length = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.flush();
fileOutputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
同时在AndroidManifest.xml中添加如下一句:
<activity android:name=".Hello2"></activity>
并在HelloActivity.java中的catch语句后加上如下代码:
Intent intent2 = new Intent(HelloActivity.this, Hello2.class);
intent2.setData(mUri);
startActivity(intent2);
finish();
再次运行,结果会怎么样?
答案是:可能抛出下面的异常:
E/AndroidRuntime(14582): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xm.permission/com.xm.permission.Hello2}:
java.lang.SecurityException: Permission Denial: reading com.android.providers.downloads.DownloadProvider uri content://downloads/all_downloads/1 from pid=14582, uid=10192
requires android.permission.ACCESS_ALL_DOWNLOADS, or grantUriPermission()
明明之前有权限,怎么突然没有了呢?
这是因为我们添加了下面的code:
finish();// HelloActivity.java
当我们调用finish()方法结束HelloActivity后,权限也随之消失。如果我们添加如下code在HelloActivity.java中,就不会抛出异常:
Intent intent2 = new Intent(HelloActivity.this, Hello2.class);
intent2.setData(mUri);
startActivity(intent2);
也就是说,当调用finish()以后,最初负责接收permission的Activity被释放,permission也一并被回收了。
所以我们要尽量在接收URI的activity中处理完所有操作。这里如果我们想存储这个URI到Database以备后用的话,也会出现权限的问题。所以,如果
这个资源需要在三方的程序中访问,最好是将它拷贝过来到SDCard,但要注意会有暴露用户隐私的风险。如果都是小文件,可以考虑拷贝到应用程序目录。
总结:当protectionlevel设置为signature时,动态Grant给第三方应用的permission不是一成不变的,它也有有效期限。