深入Android应用开发-透彻理解Permission

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不是一成不变的,它也有有效期限。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值