精华内容
参与话题
问答
  • Flutter状态管理Provider详解 flutterproviders 发布于 3月9日 provider 可以进行依赖注入和状态管理,使用widget创建,适用于widget。 它是故意设计成使用widget来进行依赖注入和状态管理的,而不是纯使用...

    https://segmentfault.com/a/1190000021953500

     

     

    Flutter状态管理Provider详解

     flutterproviders

    发布于 3月9日

    provider

    可以进行依赖注入和状态管理,使用widget创建,适用于widget。

    它是故意设计成使用widget来进行依赖注入和状态管理的,而不是纯使用dart类,像是stream这些。因为widget简单且健壮可伸缩。

    使用weidget来进行状态管理可以保证。

    1. 维护性。
    2. 可测试和兼容性。
    3. 健壮性。

    使用

    暴露一个值

    暴露一个对象实例(object instance)

    除了暴露一个值的可访问性,provider还包括这个值的创建,监听,销毁。

    为了暴露一个新创建的对象,可以使用provider的默认构造函数。不要使用.value命名构造函数类创建一个值对象,不然可能会造成其他不期望影响。

    • 可做 create中创建一个对象。
    Provider(
      create:(_)=>new MyModel(),
      child:...
    )
    • 不可做 使用Provider.value命名构造函数创建一个对象。
    ChangeNotifierProvider.value(
      value:new MyModel(),
      child:...
    )

    如果创建了一个对象,它使用了为了可能变更的变量做参数,那么考虑使用ProxyProvider:

    int count;
    
    ProxyProvider0(
      update:L(_,__)=> new MyModel(count),
      child:...
    )

    复用一个已经存在的对象实例

    如果有个对象实例,你想把其暴露在其他地方使用,那么你应该使用Provider的.value命名构造函数。

    不这么做可能会造成在该对象还在使用时被dispose

    • 可做 使用ChangeNotifierProvider.value来提供一个已经存在的ChangeNotifier
    MyChangeNotifier varibale;
    
    ChangeNotifierProvider.value(
      value:variable,
      child:...
    )
    • 不可做 在默认构造函数中重用CahangeNotifier
    MyChangeNotifier variable;
    
    ChangeNotifierProvider(
      create:(_)=>variable,
      child:...
    )

    读取一个值(获取暴露的值)

    获取一个值最简单的方法是使用静态方法Provider.of<T>(BuildContext context)

    这个方法会从当前的context在widget树中向根widget方向查找符合类型T的最近的值。(如果没有找到就throw)。

    除了Provider.of方法我们也可以使用ConsumerSelector两个widget。
    这对于高效的组织代码以及难以获取BuildContext的情况比较有帮助。

    多个Provider的情况(嵌套)/MultiProvider

    当在一个较大的应用中注入较多的数据时,Provider会飞快地嵌套多层。

    Provider<Something>(
      create:(_)=>Something(),
      child: Provider<SomethingElse>(
        create:(_)=>SomethingElse(),
        child:Provider<AnotherThing>(
          create:(_)=>AnotherTing(),
          child:someWidget,
        )
      )
    )

    可以这样写

    MultiProvider(
      Providers:[
        Provider<Somthing>(create:(_)=>Something()),
        Provider<SomthingElse>(create:(_)=>SomethingElse()),
        Provider<AnotherThing>(create:(_)=>AnotherTthing()),
      ],
      child:someWidget
    )

    上面代码的结果是严格的一样的。MultiProvider仅仅是改变了代码的形式。

    ProxyProvider

    从版本3.0.0开始增加了一个新的Provider:ProxyProvider。

    ProxyProvider本身是一个Provider,它把其他多个provider的数据结合成一个新的对象,并且把这个结果发送一个一个Provider。

    被结合的的这些provider中的任何一个数据更新了,这个新的对象都会更新。

    下面这个例子使用了ProxyProvider,他把其他provider中的counter做了个中转。

    Widget build(BuildContext context){
      return MultiProvider(
        providers:[
          ChangeNotifierProvider(create:(_)=>Counter()),
          ProxyProvider<Counter,Translations>(
            create:(_,counter,__)=>Translations(clunter.value),
          ),
          child:Foo()
        ]
      )
    }
    
    class Translations{
      const Translations(this._value);
    
      final init _value;
    
      String get title=>'You clicked: $_value times';
    }

    ProxyProvider有很多种变体,例如:

    • ProxyProvider vs ProxyProvider2 vs ProxyProvider3...

    类名后的数字是指ProxyProvider依赖其他Provider的数量。

    • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,...他们的工作方式是类似的,相对于发送结果给一个Provider,一个ChangeNotifierProxyProvider会发送给一个ChangeNotifierProxyProvider

    问答

    在InitState中获取Provider发生了错误,怎么办?

    这个错误是因为你想监听一个在其生命周期中不会被再次调用的provider。

    这说明你不会再使用其他的生命周期(didChangeDependencies/build),或者你不在乎数据更新。
    不要这么做

    initState(){
      super.initState();
      print(Provider.of<Foo>(context).value);
    }

    你可以这么做

    Value value;
    
    didChangeDependencies(){
      super.didChangeDependencies();
      final value = Provider.of<Foo>(context).value;
      if(value != this.value){
        this.value = value;
        print(value);
      }
    }

    每当值发生了变化,都会被打印。
    也可以这么做

    initState(){
      super.initState();
      print(Provider.of<Foo>(context,listen:false).value);
    }

    这样只会打印value一次,不再更新。

    我使用了ChangeNotifier,当我更新数据时发生了错误,发生了什么?

    这个经常发生在widget树正在构建时,你对ChangeNotifier进行了更改操作。

    一个典型的情景是,发起了一个http请求,然后该future被保存在了notifer中。

    initState(){
      super.initState();
      Provider.of<Foo>(context).fetchSomething();
    }

    这样是禁止的,因为更改必须是立即的。

    这意味着有些widget可能在变动之前build,然而其他的在变动之后build。这可能会造成你的ui发生冲突,所以是禁止的。

    相比,你可以在整个widget树都同步之后(渲染前/选然后?)进行变动。

    • 直接在你的模型之内进行创建:
    class Mymodel width ChangeNotifier{
      MyModel(){
        _fetchSomething();
      }
      
      Future<void> _fetchSomething()async {}
    }

    这个适用于没有额外参数的情况。

    • 一部发生在最后一帧:
    initState(){
      super.initState();
      Future.microtash(()=>{
        Provider.of<Foo>(context).fetchSomething(someValue);
      })
    }

    这个多少是不太理想的,但是允许传入参数进行变更。

    对于复杂的状态我是否必须使用ChangeNotifier?

    不是。

    你可以使用任何对象来呈现状态。例如其他可用的方式是Provider.value()结合一个StatefulWidget使用。

    这里有个计数的例子,使用了这个方法:

    class Example extends StatefulWidget{
      const Example({Key key, this.child}):super(key key);
    
      final Widget child;
    
      @override
      ExampleState createState()=> ExampleState();
    }
    
    class ExampleState extends State<Example>{
      int _count;
    
      void increment(){
        setState((){
          _count++;
        })
      }
    
      @override
      Widget build(BuildContext context){
        return Provider.value(
          value:_count,
          child:Provider.value(
            value:this,
            child:widget.child
          )
        )
      }
    }

    可以如此读取数据:

    return Text(Provider.of<int>(context).toString());

    如此更改数据:

    return FloatingActionButton(
      onPress:Provider.of<Examp0leState>(context).increment,
      child:Icon(Icons.plus_one),
    );

    此外,你也可以创建自己的provider。

    我制作一个自己的Provider吗?

    当然,provider暴露了所有的小的组件,这些制作了一个简陋的provider。

    包括:

    • SingleChildCloneableWidget,可用来创建任何配个MultiProvider工作的widget。
    • InheritedProvider,通用的InheritedWidget,使用Provider.of来获取。
    • DelegateWidget/BuilderDelegate/ValueDelegate帮助处理"MyProvider() 创建一个对象" vs ”Myprovider.value() 随时间更新"的逻辑。

    我的widget build太频繁,怎么办?

    相较于Provider.of,你可以使用Consumer/Selector。

    他们可选的child参数只允许重建widget中非常小的具体部分。

    Foo(
      child:Consumer<A>(
        builder:(_,a,child){
          return Bar(a:a,child:child);
        }
        child: Baz(),
      ),
    )

    这个例子中只有Bar会在A更新时被重建,Foo和Baz非必要下不会更新。

    更深一步,使用selector来忽略widget树中一些没有影响的更新也是可能的。

    Selector<List,int>(
      selector:(_,list)=>list.length,
      builder:(_,length,__){
        return Text('$length');
      }
    );

    这个代码片段中,只有list的length变化时,才会被重构。即使一个item发生了变化也不会更新。

    我能够使用同样的类型获取两个不同的provider吗?

    不能。
    你可以使用多个Provider共享同样的类型,一个widget只能获取到他们中的一个:最近的那个。

    不然,你必须给与不同的provider不同的数据类型。

    相较:

    Provider<String>(
      create:(_)=>'england',
      child:Provider<String>(
        create:(_)=>'London',
        child:...
      )
    )

    推荐:

    Provider<Country>(
      create:(_)=>'england',
      child:Provider<City>(
        create:(_)=>'London',
        child:...
      )
    )

    现有的providers

    provider包提供了一些不同类新的'provider'应对不同类型的对象。
    如下:

    名字 说明
    Provider provider的最基本形式,可以添加和暴露任何形式的值
    ListenableProvider 使用Listenable对象的特殊provider,ListenableProvider会监听对象,并且在监听器在任何时候调用时要求widget重构。
    ChangeNotifierProvider ChangeNotifier规格的ListenableProvider,他会在必要时自动调用ChangeNotifier.dispose.
    ValueListenableProvider 监听一个ValueListenable并且只暴露ValueListenable.value。
    StreamProvider 监听Stream并且暴露最新的emitted的值。
    FutureProvider 添加一个Future,并且在future完成时更新附从。

     

    阅读 1.6k 发布于 3月

    展开全文
  • Flutter Provider使用指南

    千次阅读 2020-06-08 09:13:55
    在大热的跨端框架flutter中,笔者将对社区中使用广泛的provider框架进行介绍。 准备工作 安装与引入 provider pub链接 官方文档宣称(本文基于4.0版本),provider是一个依赖注入和状态管理的混合工具,通过组件来...

    前言

      使用一种语言编写各种应用的时候,横亘在开发者面前的第一个问题就是如何进行状态管理。在前端领域,我们习惯使用框架或者各种辅助库来进行状态管理。例如,开发者经常使用react自带的context,或者mobx/redux等工具来管理组件间状态。在大热的跨端框架flutter中,笔者将对社区中使用广泛的provider框架进行介绍。

    准备工作

    安装与引入

    provider pub链接
    官方文档宣称(本文基于4.0版本),provider是一个依赖注入和状态管理的混合工具,通过组件来构建组件。
    provider有以下三个特点:

    1. 可维护性,provider强制使用单向数据流
    2. 易测性/可组合性,provider可以很方便地模拟或者复写数据
    3. 鲁棒性,provider会在合适的时候更新组件或者模型的状态,降低错误率

    在pubspec.yaml文件中加入如下内容:

    dependencies:
      provider: ^4.0.0
    

    然后执行命令flutter pub get,安装到本地。
    使用时只需在文件头部加上如下内容:

    import 'package:provider/provider.dart';
    

    暴露一个值

    如果我们想让某个变量能够被一个widget及其子widget所引用,我们需要将其暴露出来,典型写法如下:

    Provider(
      create: (_) => new MyModel(),
      child: ...
    )
    

    读取一个值

    如果要使用先前暴露的对象,可以这样操作

    class Home extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        MyModel yourValue = Provider.of<MyModel>(context)
        return ...
      }
    }
    

    暴露和使用多个值(MultiProvider)

    Provider的构造方法可以嵌套使用

    Provider<Something>(
      create: (_) => Something(),
      child: Provider<SomethingElse>(
        create: (_) => SomethingElse(),
        child: Provider<AnotherThing>(
          create: (_) => AnotherThing(),
          child: someWidget,
        ),
      ),
    ),
    

    上述代码看起来过于繁琐,走入了嵌套地狱,好在provider给了更加优雅的实现

    MultiProvider(
      providers: [
        Provider<Something>(create: (_) => Something()),
        Provider<SomethingElse>(create: (_) => SomethingElse()),
        Provider<AnotherThing>(create: (_) => AnotherThing()),
      ],
      child: someWidget,
    )
    

    代理provider(ProxyProvider)

    在3.0版本之后,有一种新的代理provider可供使用,ProxyProvider能够将不同provider中的多个值整合成一个对象,并将其发送给外层provider,当所依赖的多个provider中的任意一个发生变化时,这个新的对象都会更新。下面的例子使用ProxyProvider来构建了一个依赖其他provider提供的计数器的例子

    Widget build(BuildContext context) {
      return MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => Counter()),
          ProxyProvider<Counter, Translations>(
            create: (_, counter, __) => Translations(counter.value),
          ),
        ],
        child: Foo(),
      );
    }
    
    class Translations {
      const Translations(this._value);
    
      final int _value;
    
      String get title => 'You clicked $_value times';
    }
    

    各种provider

    可以通过各种不同的provider来应对具体的需求

    • Provider 最基础的provider,它会获取一个值并将它暴露出来
    • ListenableProvider 用来暴露可监听的对象,该provider将会监听对象的改变以便及时更新组件状态
    • ChangeNotifierProvider ListerableProvider依托于ChangeNotifier的一个实现,它将会在需要的时候自动调用ChangeNotifier.dispose方法
    • ValueListenableProvider 监听一个可被监听的值,并且只暴露ValueListenable.value方法
    • StreamProvider 监听一个流,并且暴露出其最近发送的值
    • FutureProvider 接受一个Future作为参数,在这个Future完成的时候更新依赖

    项目实战

    接下来笔者将以自己项目来举例provider的用法
    首先定义一个基类,完成一些UI更新等通用工作

    import 'package:provider/provider.dart';
    
    class ProfileChangeNotifier extends ChangeNotifier {
      Profile get _profile => Global.profile;
    
      @override
      void notifyListeners() {
        Global.saveProfile(); //保存Profile变更
        super.notifyListeners();
      }
    }
    

    之后定义自己的数据类

    class UserModle extends ProfileChangeNotifier {
      String get user => _profile.user;
      set user(String user) {
        _profile.user = user;
        notifyListeners();
      }
    
      bool get isLogin => _profile.isLogin;
      set isLogin(bool value) {
        _profile.isLogin = value;
        notifyListeners();
      }
    
      String get avatar => _profile.avatar;
      set avatar(String value) {
        _profile.avatar = value;
        notifyListeners();
      }
    

    这里通过setget方法劫持对数据的获取和修改,在有相关改动发生时通知组件树同步状态。
    在主文件中,使用provider

    class MyApp extends StatelessWidget with CommonInterface {
    
      MyApp({Key key, this.info}) : super(key: key);
      final info;
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        UserModle newUserModel = new UserModle();
        return MultiProvider(
          providers: [
            //  用户信息
            ListenableProvider<UserModle>.value(value: newUserModel),
          ],
          child: ListenContainer(),
        );
      }
    }
    

    接下来,在所有的子组件中,如果需要使用用户的名字,只需Provider.of<UserModle>(context).user即可,但是这样的写法看上去不够精简,每次调用时都需要写很长的一段开头Provider.of<xxx>(context).XXX很是繁琐,故而这里我们可以简单封装一个抽象类:

    abstract class CommonInterface {
      String cUser(BuildContext context) {
        return Provider.of<UserModle>(context).user;
      }
    }
    

    在子组件声明时,使用with,来简化代码

    class MyApp extends StatelessWidget with CommonInterface {
      ......
    }
    

    在使用时只需cUser(context)即可。

    class _FriendListState extends State<FriendList> with CommonInterface {
      @override
      Widget build(BuildContext context) {
        return Text(cUser(context));
      }
    }
    

    项目完整代码详见本人仓库

    其他相关细节和常见问题(来自官方文档)

    1. 为什么在initState中获取Provider会报错?
      不要在只会调用一次的组件生命周期中调用Provider,比如如下的使用方法是错误的
    initState() {
      super.initState();
      print(Provider.of<Foo>(context).value);
    }
    

    要解决这个问题,要么使用其他生命周期方法(didChangeDependencies/build)

    didChangeDependencies() {
      super.didChangeDependencies();
      final value = Provider.of<Foo>(context).value;
      if (value != this.value) {
        this.value = value;
        print(value);
      }
    }
    

    或者指明你不在意这个值的更新,比如

    initState() {
      super.initState();
      print(Provider.of<Foo>(context, listen: false).value);
    }
    
    1. 我在使用ChangeNotifier的过程中,如果更新变量的值就会报出异常?
      这个很有可能因为你在改变某个子组件的ChangeNotifier时,整个渲染树还处在创建过程中。
      比较典型的使用场景是notifier中存在http请求
    initState() {
      super.initState();
      Provider.of<Foo>(context).fetchSomething();
    }
    

    这是不允许的,因为组件的更新是即时生效的。
    换句话来说如果某些组件在异步过程之前构建,某些组件在异步过程之后构建,这很有可能触发你应用中的UI表现不一致,这是不允许的。
    为了解决这个问题,需要把你的异步过程放在能够等效的影响组件树的地方

    • 直接在你provider模型的构造函数中进行异步过程
    class MyNotifier with ChangeNotifier {
      MyNotifier() {
        _fetchSomething();
      }
    
      Future<void> _fetchSomething() async {}
    }
    
    • 或者直接添加异步行为
    initState() {
      super.initState();
      Future.microtask(() =>
        Provider.of<Foo>(context).fetchSomething(someValue);
      );
    }
    
    1. 为了同步复杂的状态,我必须使用ChangeNotifier吗?
      并不是,你可以使用一个对象来表示你的状态,例如把Provider.value()StatefulWidget结合起来使用,达到即刷新状态又同步UI的目的.
    class Example extends StatefulWidget {
      const Example({Key key, this.child}) : super(key: key);
    
      final Widget child;
    
      @override
      ExampleState createState() => ExampleState();
    }
    
    class ExampleState extends State<Example> {
      int _count;
    
      void increment() {
        setState(() {
          _count++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Provider.value(
          value: _count,
          child: Provider.value(
            value: this,
            child: widget.child,
          ),
        );
      }
    }
    

    当需要读取状态时:

    return Text(Provider.of<int>(context).toString());
    

    当需要改变状态时:

    return FloatingActionButton(
      onPressed: Provider.of<ExampleState>(context).increment,
      child: Icon(Icons.plus_one),
    );
    
    1. 我可以封装我自己的Provider么?
      可以,provider暴露了许多细节api以便使用者封装自己的provider,它们包括:SingleChildCloneableWidgetInheritedProviderDelegateWidgetBuilderDelegateValueDelegate
    2. 我的组件重建得过于频繁,这是为什么?
      可以使用Provider.of来替代Consumer/Selector.
      可以使用可选的child参数来保证组件树只会重建某个特定的部分
    Foo(
      child: Consumer<A>(
        builder: (_, a, child) {
          return Bar(a: a, child: child);
        },
        child: Baz(),
      ),
    )
    

    在以上例子中,当A改变时,只有Bar会重新渲染,FooBaz并不会进行不必要的重建。
    为了更精细地控制,我们还可以使用Selector来忽略某些不会影响组件数的改变。

    Selector<List, int>(
      selector: (_, list) => list.length,
      builder: (_, length, __) {
        return Text('$length');
      }
    );
    

    在这个例子中,组件只会在list的长度发生改变时才会重新渲染,其内部元素改变时并不会触发重绘。

    1. 我可以使用两个不同的provider来获取同一个类型的值吗?
      不可以,哪怕你给多个provider定义了同一个类型,组件也只能获取距离其最近的一个父组件中的provider的值.

     

    8人点赞

     

    日记本

     



    作者:广兰路地铁
    链接:https://www.jianshu.com/p/31499233c673
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • Android Provider用法

    万次阅读 2018-03-31 17:39:57
    ContentProviderContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。之所以使用ContentProvider,主要有以下几个理由:1,ContentProvider提供了对底层数据存储方式的抽象。...

    ContentProvider

    ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。

    之所以使用ContentProvider,主要有以下几个理由:
    1,ContentProvider提供了对底层数据存储方式的抽象。比如下图中,底层使用了SQLite数据库,在用了ContentProvider封装后,即使你把数据库换成MongoDB,也不会对上层数据使用层代码产生影响。

    2,Android框架中的一些类需要ContentProvider类型数据。如果你想让你的数据可以使用在如SyncAdapter, Loader, CursorAdapter等类上,那么你就需要为你的数据做一层ContentProvider封装。

    3,第三个原因也是最主要的原因,是ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题。

    ContentProvider是对数据层的封装,我们可以通过ContentResolver,来对不同的ContentProvider进行增,删,改,查的操作。

    ContentResolver

    有些人可能会疑惑,为什么我们不直接访问Provider,而是又在上面加了一层ContentResolver来进行对其的操作,这样岂不是更复杂了吗?其实不然,大家要知道一台手机中可不是只有一个Provider内容,它可能安装了很多含有Provider的应用,比如联系人应用,日历应用,字典应用等等。所以Android提供了ContentResolver来统一管理与不同ContentProvider间的操作。

    Context.java的源码中有一段

    /** Return a ContentResolver instance for your application's package. */
     public abstract ContentResolver getContentResolver();
    

    所以我们可以通过在所有继承Context的类中通过调用getContentResolver()来获得ContentResolver

    那ContentResolver是如何来区别不同的ContentProvider的呢?这就涉及到URI(Uniform Resource Identifier)问题,对URI是什么还不明白的童鞋请自行Google。

    ContentProvider中的URI

    ContentProvider中的URI有固定格式,如下图:

    Authority:授权信息,用以区别不同的ContentProvider;
    Path:表名,用以区分ContentProvider中不同的数据表;
    Id:Id号,用以区别表中的不同数据;

    URI组装代码示例:

    public class TestContract {
    
        protected static final String CONTENT_AUTHORITY = "me.pengtao.contentprovidertest";
        protected static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
    
        protected static final String PATH_TEST = "test";
        public static final class TestEntry implements BaseColumns {
    
            public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_TEST).build();
            protected static Uri buildUri(long id) {
                return ContentUris.withAppendedId(CONTENT_URI, id);
            }
    
            protected static final String TABLE_NAME = "test";
    
            public static final String COLUMN_NAME = "name";
        }
    }
    

    从上面代码我们可以看到,我们创建了一个
    content://me.pengtao.contentprovidertest/test的uri,并且开了一个静态方法,用以在有新数据产生时根据id生成新的uri。下面介绍下如何把此uri映射到数据库表中。

    实作

    首先我们创建一个自己的TestProvider继承ContentProvider。默认该Provider需要实现如下六个方法,onCreate(), query(Uri, String[], String, String[], String),insert(Uri, ContentValues), update(Uri, ContentValues, String, String[]), delete(Uri, String, String[]), getType(Uri),方法的具体介绍可以参考
    http://developer.android.com/reference/android/content/ContentProvider.html

    下面我们以实现insert和query方法为例

    private final static int TEST = 100;
    
    static UriMatcher buildUriMatcher() {
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = TestContract.CONTENT_AUTHORITY;
    
        matcher.addURI(authority, TestContract.PATH_TEST, TEST);
    
        return matcher;
    }
    
    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    
        Cursor cursor = null;
        switch ( buildUriMatcher().match(uri)) {
            case TEST:
                cursor = db.query(TestContract.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);
                break;
        }
    
        return cursor;
    }
    
    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        Uri returnUri;
        long _id;
        switch ( buildUriMatcher().match(uri)) {
            case TEST:
                _id = db.insert(TestContract.TestEntry.TABLE_NAME, null, values);
                if ( _id > 0 )
                    returnUri = TestContract.TestEntry.buildUri(_id);
                else
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                break;
            default:
                throw new android.database.SQLException("Unknown uri: " + uri);
        }
        return returnUri;
    }
    

    此例中我们可以看到,我们根据path的不同,来区别对不同的数据库表进行操作,从而完成uri与具体数据库间的映射关系。

    因为ContentProvider作为四大组件之一,所以还需要在AndroidManifest.xml中注册一下。

    <provider    
        android:authorities="me.pengtao.contentprovidertest"  
        android:name=".provider.TestProvider" />
    

    然后你就可以使用getContentResolver()方法来对该ContentProvider进行操作了,ContentResolver对应ContentProvider也有insert,query,delete等方法,详情请参考:
    http://developer.android.com/reference/android/content/ContentResolver.html

    此处因为我们只实现了ContentProvider的query和insert的方法,所以我们可以进行插入和查询处理。如下我们可以在某个Activity中进行如下操作,先插入一个数据peng,然后再从从表中读取第一行数据中的第二个字段的值。

    ContentValues contentValues = new ContentValues();
    contentValues.put(TestContract.TestEntry.COLUMN_NAME, "peng");
    contentValues.put(TestContract.TestEntry._ID, System.currentTimeMillis());
    getContentResolver().insert(TestContract.TestEntry.CONTENT_URI, contentValues);
    
    Cursor cursor = getContentResolver().query(TestContract.TestEntry.CONTENT_URI, null, null, null, null);
    
    try {
        Log.e("ContentProviderTest", "total data number = " + cursor.getCount());
        cursor.moveToFirst();
        Log.e("ContentProviderTest", "total data number = " + cursor.getString(1));
    } finally {
        cursor.close();
    }
    

    数据共享

    以上例子中创建的ContentProvider只能在本应用内访问,那如何让其他应用也可以访问此应用中的数据呢,一种方法是向此应用设置一个android:sharedUserId,然后需要访问此数据的应用也设置同一个sharedUserId,具有同样的sharedUserId的应用间可以共享数据。

    但此种方法不够安全,也无法做到对不同数据进行不同读写权限的管理,下面我们就来详细介绍下ContentProvider中的数据共享规则。

    首先我们先介绍下,共享数据所涉及到的几个重要标签:
    android:exported 设置此provider是否可以被其他应用使用。
    android:readPermission 该provider的读权限的标识
    android:writePermission 该provider的写权限标识
    android:permission provider读写权限标识
    android:grantUriPermissions 临时权限标识,true时,意味着该provider下所有数据均可被临时使用;false时,则反之,但可以通过设置<grant-uri-permission>标签来指定哪些路径可以被临时使用。这么说可能还是不容易理解,我们举个例子,比如你开发了一个邮箱应用,其中含有附件需要第三方应用打开,但第三方应用又没有向你申请该附件的读权限,但如果你设置了此标签,则可以在start第三方应用时,传入FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION来让第三方应用临时具有读写该数据的权限。

    知道了这些标签用法后,让我们改写下AndroidManifest.xml,让ContentProvider可以被其他应用查询。

    声明一个permission

    <permission android:name="me.pengtao.READ" android:protectionLevel="normal"/>
    

    然后改变provider标签为

    <provider
        android:authorities="me.pengtao.contentprovidertest"
        android:name=".provider.TestProvider"
        android:readPermission="me.pengtao.READ"
        android:exported="true">
    </provider>
    

    则在其他应用中可以使用以下权限来对TestProvider进行访问。

    <uses-permission android:name="me.pengtao.READ"/>
    

    有人可能又想问,如果我的provider里面包含了不同的数据表,我希望对不同的数据表有不同的权限操作,要如何做呢?Android为这种场景提供了provider的子标签<path-permission>,path-permission包括了以下几个标签。

    <path-permission android:path="string"
                     android:pathPrefix="string"
                     android:pathPattern="string"
                     android:permission="string"
                     android:readPermission="string"
                     android:writePermission="string" />
    

    可以对不同path设置不同的权限规则,具体如何设定我这里就不做详细介绍了,可以参考
    http://developer.android.com/guide/topics/manifest/path-permission-element.html

    进阶原理介绍

    可以参考:http://gityuan.com/2016/07/30/content-provider/

    展开全文
  • Provider 权限详解

    千次阅读 2018-08-27 16:49:00
    转载请标明出处: ... 前言: 之前有篇博文 《Android基础总结之八:ContentProvider》大概说明provider 的基础...对于AndroidManifest.xml中provider 的解析可以看下博文《android PMS 如何解析 APK》,本文主要...

    转载请标明出处:

    https://blog.csdn.net/shift_wwx/article/details/82108549

     

    前言:

    之前有篇博文 《Android基础总结之八:ContentProvider》大概说明provider 的基础知识。对于AndroidManifest.xml中provider 的解析可以看下博文《android PMS 如何解析 APK》,本文主要对provider 权限进行详细解析。如果provider 会被其他程序使用时,需要将export 属性设为true,还需要进行readPermission 和writePermission 设置。当然,对于SDK 22以后出现的FileProvider 又是另一种情况,本文会结合source code 详细分析Provider 在使用中权限管理。

    本文代码基于版本Android O。

     

    实例:

    之前出现一个exception 的log,本文结合这个实例进行解析。

    --------- beginning of crash
    08-24 18:14:43.989 E/AndroidRuntime( 2366): FATAL EXCEPTION: main
    08-24 18:14:43.989 E/AndroidRuntime( 2366): Process: com.android.messaging, PID: 2366
    08-24 18:14:43.989 E/AndroidRuntime( 2366): java.lang.SecurityException: UID 10098 does not have permission to content://com.android.dialer.files/my_cache/my_cache/%2B12543365555_08-11-18_0442AM.amr [user 0]
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.os.Parcel.readException(Parcel.java:2005)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.os.Parcel.readException(Parcel.java:1951)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:4352)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.Instrumentation.execStartActivity(Instrumentation.java:1613)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.Activity.startActivityForResult(Activity.java:4501)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.Activity.startActivityForResult(Activity.java:4459)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.Activity.startActivity(Activity.java:4820)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.Activity.startActivity(Activity.java:4788)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.messaging.ui.Q.wT(SourceFile:200)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.messaging.ui.conversationlist.ShareIntentActivity.pR(SourceFile:179)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.messaging.ui.conversationlist.o.onClick(SourceFile:98)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:166)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.os.Handler.dispatchMessage(Handler.java:106)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.os.Looper.loop(Looper.java:164)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at android.app.ActivityThread.main(ActivityThread.java:6518)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at java.lang.reflect.Method.invoke(Native Method)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    08-24 18:14:43.989 E/AndroidRuntime( 2366): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
    08-24 18:14:43.989 D/RecurrenceRule(  599): Resolving using anchor 2018-08-24T18:14:43.989+08:00[Asia/Shanghai]

    Log 提示uid 为10098 的进程没有权限使用一个provider。

     

    源码分析:

    debug 函数startActivity(),最终会调用到AMS 中,下面将堆栈打印出来:

    08-24 18:14:43.946 E/ActivityManager(  599): WJ Stack:java.lang.Throwable
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.checkGrantUriPermissionLocked(ActivityManagerService.java:8975)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.checkGrantUriPermissionFromIntentLocked(ActivityManagerService.java:9264)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.grantUriPermissionFromIntentLocked(ActivityManagerService.java:9304)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1203)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:1000)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:577)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityStarter.startActivityLocked(ActivityStarter.java:283)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityStarter.startActivityMayWait(ActivityStarter.java:822)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:4616)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:4603)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:121)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
    08-24 18:14:43.946 E/ActivityManager(  599): 	at android.os.Binder.execTransact(Binder.java:697)

    在AMS 中会调用grantUriPermissionFromIntentLocked():

        void grantUriPermissionFromIntentLocked(int callingUid,
                String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) {
            NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
                    intent, intent != null ? intent.getFlags() : 0, null, targetUserId);
            if (needed == null) {
                return;
            }
    
            grantUriPermissionUncheckedFromIntentLocked(needed, owner);
        }

    这里参数intent、targetPkg、owner、targetUserId 都是ActivityStarter 传进来。

     

    最终函数会调用到checkGrantUriPermissionLocked(),这个是权限处理的核心函数。

    其实,AMS中为应用需要确定Uri 权限,单独提供了一个public 接口函数checkGrantUriPermission(),通过code 会发现该函数最终也是会调用到checkGrantUriPermissionLocked()。

        public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri,
                final int modeFlags, int userId) {
            enforceNotIsolatedCaller("checkGrantUriPermission");
            synchronized(this) {
                return checkGrantUriPermissionLocked(callingUid, targetPkg,
                        new GrantUri(userId, uri, false), modeFlags, -1);
            }
        }

    来看下函数checkGrantUriPermissionLocked():

        int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
                final int modeFlags, int lastTargetUid) {
            // 要求Intent 的flags 设为FLAG_GRANT_READ_URI_PERMISSION 或FLAG_GRANT_WRITE_URI_PERMISSION
            if (!Intent.isAccessUriMode(modeFlags)) {
                return -1;
            }
    
            ...
            ...
    
            // 要求scheme 是content
            if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
                if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                        "Can't grant URI permission for non-content URI: " + grantUri);
                return -1;
            }
    
            // Bail early if system is trying to hand out permissions directly; it
            // must always grant permissions on behalf of someone explicit.
            final int callingAppId = UserHandle.getAppId(callingUid);
            if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
                if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
                    // Exempted authority for cropping user photos in Settings app
                } else {
                    Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
                            + " grant to " + grantUri + "; use startActivityAsCaller() instead");
                    return -1;
                }
            }
    
            ...
            ...
    
            // 确定targetUid 是否有该permission
            if (targetUid >= 0) {
                // First...  does the target actually need this permission?
                if (checkHoldingPermissionsLocked(pm, pi, grantUri, targetUid, modeFlags)) {
                    // No need to grant the target this permission.
                    if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                            "Target " + targetPkg + " already has full permission to " + grantUri);
                    return -1;
                }
            }
    		
            ...
            ...
    		
            /* There is a special cross user grant if:
             * - The target is on another user.
             * - Apps on the current user can access the uri without any uid permissions.
             * In this case, we grant a uri permission, even if the ContentProvider does not normally
             * grant uri permissions.
             */
            boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
                    && checkHoldingPermissionsInternalLocked(pm, pi, grantUri, callingUid,
                    modeFlags, false /*without considering the uid permissions*/);
    
            // Second...  is the provider allowing granting of URI permissions?
            if (!specialCrossUserGrant) {
                if (!pi.grantUriPermissions) {
                    throw new SecurityException("Provider " + pi.packageName
                            + "/" + pi.name
                            + " does not allow granting of Uri permissions (uri "
                            + grantUri + ")");
                }
                if (pi.uriPermissionPatterns != null) {
                    final int N = pi.uriPermissionPatterns.length;
                    boolean allowed = false;
                    for (int i=0; i<N; i++) {
                        if (pi.uriPermissionPatterns[i] != null
                                && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
                            allowed = true;
                            break;
                        }
                    }
                    if (!allowed) {
                        throw new SecurityException("Provider " + pi.packageName
                                + "/" + pi.name
                                + " does not allow granting of permission to path of Uri "
                                + grantUri);
                    }
                }
            }
    
            // 确认callingUid 是否有该permission
            if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
                // 确认是否之前已经存在该grant uri permission
                if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) {
                    if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
                        throw new SecurityException(
                                "UID " + callingUid + " does not have permission to " + grantUri
                                        + "; you could obtain access using ACTION_OPEN_DOCUMENT "
                                        + "or related APIs");
                    } else {
                        throw new SecurityException(
                                "UID " + callingUid + " does not have permission to " + grantUri);
                    }
                }
            }
            return targetUid;
        }

    1. Intent 的flags 要求设为 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION,不然不处理

    2. 要求Uri 的scheme 是content,不然不处理

    3. 如果provider 的grantUriPermissions属性为true,会接着确认grant-uri-permission,详细看 grant-uri-permission 文档

    4. checkHoldingPermissionsLocked() 函数确定Provider的所需基本权限,详细看下面

    5. checkUriPermissionLocked() 是在上面check fail 时,会进一步确认是否之前在provider 中已经grant,详细看下面。

     

    先来看下checkHoldingPermissionsLocked():

        private final boolean checkHoldingPermissionsLocked(
                IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
            if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                    "checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid);
            if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
                if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
                        != PERMISSION_GRANTED) {
                    return false;
                }
            }
            Slog.v(TAG_URI_PERMISSION,
                    "enter checkHoldingPermissionsInternalLocked");
            return checkHoldingPermissionsInternalLocked(pm, pi, grantUri, uid, modeFlags, true);
        }

    对于多用户需要最开始check INTERACT_ACROSS_USERS 权限,接着调用checkHoldingPermissionsInternalLocked():

        private final boolean checkHoldingPermissionsInternalLocked(IPackageManager pm, ProviderInfo pi,
                GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
            if (pi.applicationInfo.uid == uid) {
                return true;
            } else if (!pi.exported) {
                return false;
            }
    
            boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
            boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
    
            try {
                // check if target holds top-level <provider> permissions
                if (!readMet && pi.readPermission != null && considerUidPermissions
                        && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
                    readMet = true;
                }
                if (!writeMet && pi.writePermission != null && considerUidPermissions
                        && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
                    writeMet = true;
                }
    
                // track if unprotected read/write is allowed; any denied
                // <path-permission> below removes this ability
                boolean allowDefaultRead = pi.readPermission == null;
                boolean allowDefaultWrite = pi.writePermission == null;
    
                // check if target holds any <path-permission> that match uri
                final PathPermission[] pps = pi.pathPermissions;
                if (pps != null) {
                    final String path = grantUri.uri.getPath();
                    int i = pps.length;
                    while (i > 0 && (!readMet || !writeMet)) {
                        i--;
                        PathPermission pp = pps[i];
                        if (pp.match(path)) {
                            if (!readMet) {
                                final String pprperm = pp.getReadPermission();
                                if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                                        "Checking read perm for " + pprperm + " for " + pp.getPath()
                                        + ": match=" + pp.match(path)
                                        + " check=" + pm.checkUidPermission(pprperm, uid));
                                if (pprperm != null) {
                                    if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
                                            == PERMISSION_GRANTED) {
                                        readMet = true;
                                    } else {
                                        allowDefaultRead = false;
                                    }
                                }
                            }
                            if (!writeMet) {
                                final String ppwperm = pp.getWritePermission();
                                if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
                                        "Checking write perm " + ppwperm + " for " + pp.getPath()
                                        + ": match=" + pp.match(path)
                                        + " check=" + pm.checkUidPermission(ppwperm, uid));
                                if (ppwperm != null) {
                                    if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
                                            == PERMISSION_GRANTED) {
                                        writeMet = true;
                                    } else {
                                        allowDefaultWrite = false;
                                    }
                                }
                            }
                        }
                    }
                }
    
                // grant unprotected <provider> read/write, if not blocked by
                // <path-permission> above
                if (allowDefaultRead) readMet = true;
                if (allowDefaultWrite) writeMet = true;
    
            } catch (RemoteException e) {
                return false;
            }
    
            return readMet && writeMet;
        }

    1. 应用uid 和provider uid 相同时,check 通过

    2. provider 的exported 属性如果为false,check 直接不通过

    3. 在provider 的exported 属性为true 时,为了保护provider 有时候需要加上read permission 和write permission,如果provider 设定了这两个permission,应用在使用过的时候需要保证有这两个权限,如code 中会通过函数checkUidPermission()。

    当然,如果provider 没有加这两个权限的保护,系统认为read 和write 都是允许的,如code:

    if (allowDefaultRead) readMet = true;
    if (allowDefaultWrite) writeMet = true;

    结论:

    如果希望checkHoldingPermissionsLocked() 通过,必须满足下面其中一点:

    1. 应用的uid 和provider uid 相同

    2. provider 的exported 设为true,而且应用必须同时拥有read permission 和write permission,如果provider 没有加这个保护,默认情况下应用是有这两个权限

    第二种情况比较特殊,如果是FileProvider 那么exported 必须是false,code 如下:(详细看 FileProvider文档

        public void attachInfo(Context context, ProviderInfo info) {                                    
            super.attachInfo(context, info);                                                                                                              
                                                                                                           
            // Sanity check our security                                                                   
            if (info.exported) {                                                                           
                throw new SecurityException("Provider must not be exported");                              
            }                                                                                              
            if (!info.grantUriPermissions) {                                                               
                throw new SecurityException("Provider must grant uri permissions");                        
            }                                                                                              
                                                                                                           
            mStrategy = getPathStrategy(context, info.authority);                                          
        }

     

    上面checkGrantUriPermissionLocked() 函数我们知道最终需要两个条件中一个满足就可以check 通过:

    1. checkHoldingPermissionsLocked() 函数能check pass

    2. checkUriPermissionLocked() 函数能check pass

    上面解析了checkHoldingPermissionsLocked() 的check 过程,详细看该函数后面的结论。对于FileProvider 比较特殊,要求exported 属性必须为false,函数check 肯定是fail,如果该函数check 不过,只需要保证checkUriPermissionLocked() 函数能check pass 就可以,也就是说即使provider 的exported 的属性值为false,也有可能check pass的。下面来详细解析checkUriPermissionLocked() 函数:

        private final boolean checkUriPermissionLocked(GrantUri grantUri, int uid,
                final int modeFlags) {
            final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
            final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
                    : UriPermission.STRENGTH_OWNED;
    
            // Root gets to do everything.
            if (uid == 0) {
                return true;
            }
    
            final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
            if (perms == null) return false;
    
            // First look for exact match
            final UriPermission exactPerm = perms.get(grantUri);
            if (exactPerm != null && exactPerm.getStrength(modeFlags) >= minStrength) {
                return true;
            }
    
            // No exact match, look for prefixes
            final int N = perms.size();
            for (int i = 0; i < N; i++) {
                final UriPermission perm = perms.valueAt(i);
                if (perm.uri.prefix && grantUri.uri.isPathPrefixMatch(perm.uri.uri)
                        && perm.getStrength(modeFlags) >= minStrength) {
                    return true;
                }
            }
    
            return false;
        

    函数主要确认mGrantedUriPermissions 是否有对应uid 额ArrayMap 存在,也就是说之前必须要创建这样的ArrayMap 并且是将其add 到mGrantedUriPermissions 中。

        private final SparseArray<ArrayMap<GrantUri, UriPermission>>
                mGrantedUriPermissions = new SparseArray<ArrayMap<GrantUri, UriPermission>>();

    而这个mGrantedUriPermissions 是在函数findOrCreateUriPermissionLocked() 中创建:

        private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
                String targetPkg, int targetUid, GrantUri grantUri) {
            ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
            if (targetUris == null) {
                targetUris = Maps.newArrayMap();
                mGrantedUriPermissions.put(targetUid, targetUris);
            }
    
            UriPermission perm = targetUris.get(grantUri);
            if (perm == null) {
                perm = new UriPermission(sourcePkg, targetPkg, targetUid, grantUri);
                targetUris.put(grantUri, perm);
            }
    
            return perm;
        }

     

    其实从 FileProvider 文档 中可以知道一般grant uri permission 有两种选择:

    1. 通过接口grantUriPermission() 

    Call the method Context.grantUriPermission(package, Uri, mode_flags) for the content:// Uri, using the desired mode flags. This grants temporary access permission for the content URI to the specified package, according to the value of the the mode_flags parameter, which you can set toFLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION or both. The permission remains in effect until you revoke it by calling revokeUriPermission() or until the device reboots.

     

    2. 通过Intent 配置

    Permissions granted in an Intent remain in effect while the stack of the receiving Activity is active. When the stack finishes, the permissions are automatically removed. Permissions granted to one Activity in a client app are automatically extended to other components of that app.

     

    实例结论:

    实例中出现的exception 问题,应该是Android 自身的问题,在分享到短信的时候,短信自身又启动了一个Activity,这样就导致了checkHoldingPermissionsLocked() 传入的 targetUid 和 callingUid都是短信自身的uid,所以最后导致check 都不能pass。在第一次startActivity的时候会进入checkGrantUriPermissionLocked(),但是碰到条件给过滤掉了,我们需要修改该过滤条件使其满足:

            if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
                if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
                    // Exempted authority for cropping user photos in Settings app
                } else {
                    Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
                            + " grant to " + grantUri + "; use startActivityAsCaller() instead");
                    return -1;
                }
            }

    其实通过code 可以看到Android 源生也针对com.android.settings.files 进行了特殊的过滤。我们只需要参考这个即可。

     

     

    参考文献:

    https://developer.android.com/guide/topics/manifest/provider-element

    https://developer.android.com/guide/topics/manifest/grant-uri-permission-element

    https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles

    展开全文
  • flutter中Provider详解

    万次阅读 2019-09-28 00:45:02
    为什么我们需要状态管理 如果应用足够简单,Flutter 作为一个声明式框架,你或许只需要将 数据 映射成 视图 就可以了。你可能并不需要状态管理,就像下面这样。 image.png ...但是随着功能的增加,你的应用程序将...
  • ContentProvider讲解与实例应用

    万次阅读 2018-03-29 20:25:35
    什么是ContentProvider: 是Android的四大组件之一 主要用于不同的应用程序之间实现数据共享功能 什么是ContentResolver: ... 是数据调用者,ContentProvider将数据发布出来,通过ContentResolver对象结合Uri...
  • Android ContentProvider原理分析

    千次阅读 2018-11-11 15:10:45
    目录 ContentProvider概述 类图 时序图 源码解析 installProvider ContentResolver中的CURD acquireProvider 到AMS获取ContentProvider publishContentProvider removeDyingProvider ...ContentProvider作为An...
  • ContentProvider简介

    千次阅读 2017-10-21 14:39:11
    一 前言  ContentProvider是不同应用程序之间进行数据交换的标准API,ContentProvide以Uri的形式对外提供数据,允许其他应用访问和修改数据;其他应用使用ContentResolve根据Uri进行访问操作指定的数据。...
  • Provider

    2019-05-09 11:37:00
    共享数据类的写法: 示例: class Bloc { final StreamController<String> _streamController = StreamController(); Stream<String> stream; Bloc() { stream = _streamController.stream....
  • 本文已在我的公众号hongyangAndroid原创首发。 转载请标明出处: ... 本文出自张鸿洋的博客 本文已在我的公众号hongyangAndroid原创首发,文章合集。 ...之前项目的新特性适配工作都是同事在做,一直没有怎么太关注...
  • Android N 之前的 Uri 常规Uri有两种: 媒体文件的Uri是content://, 表示这是一个数据库数据。去数据库查询正常返回。 其他的文件Uri是file://, 表示这个是一个文件。这个uri是通过Uri.fromFile(File file)方法...
  • 首先扯点别的:今天不上班,在家里和剑宗喝了点酒,和同学聊了会天,也是挺开心,现在学会习。以前调用系统相机拍照的时候,流程是这样的 private void takePhoto() { Intent takePictureIntent = new Intent...
  • Android系统APP之SettingsProvider

    万次阅读 多人点赞 2017-03-01 14:53:31
    Android系统APP之SettingsProvider 设置共享 系统设置 Android系统APP之SettingsProvider 前言 SettingsProvider概览 主要源码 数据分类 AndroidManifestxml配置 SettingsProvider的启动过程 封装Settings...
  • Java Provider 详细信息

    千次阅读 2014-12-22 15:42:38
    Provider.1=SUN version 1.7------ Alg.Alias.Signature.SHA1/DSA Alg.Alias.Signature.1.2.840.10040.4.3 Alg.Alias.Signature.DSS SecureRandom.SHA1PRNG ImplementedIn KeyStore.JKS Alg.Alias.MessageDige
  • Android FileProvider详细解析和踩坑指南

    万次阅读 多人点赞 2018-12-07 14:29:46
    详细梳理一下android的文件系统,以及做一下FileProvider的解析。
  • 最近在测试FileProvider相关功能的时候,在从自定义相册选择图片通过FileProvider来获取content uri的时候程序突然崩溃了,报出了 Failed to find configured root that contains xxxx 的错误,一开始以为是自己...
  • Flutter Provider状态管理-Consumer

    千次阅读 多人点赞 2019-10-28 15:44:48
    如果对Consumer很了解的同学可以继续学习Flutter Provider状态管理 - Selector 个人觉得Flutter的学习有三个很重要的阶段 widget的学习和使用 数据以及状态的管理 和原生的交互 对于第一点不必多说,大家开始...
  • 众所周知在android7.0,修改了对私有存储的限制,导致在获取资源的时候,不能通过Uri.fromFile来获取uri了我们需要适配7.0+的机型需要这样写:1:代码适配 if (Build.VERSION.SDK_INT &gt; 23) {// ...
  • FileProvider使用

    千次阅读 2017-05-31 14:06:22
    问题3.FileProvider4.事例5.原理文章最后有代码链接1.背景targetSdkVersion:25 模拟器:genymotion api7.02.问题Android7.0开始,应用私有目录被限制访问,官方做了如下限制: 1.私有文件的文件权限不应再由所有...
  • 本篇将带你深入理解 Flutter 中 State 的工作机制,并通过对状态管理框架 Provider 解析加深理解,看完这一篇你将更轻松的理解你的 “State 大后宫” 。 前文: 一、 Dart语言和Flutter基础 二、 快速开发...

空空如也

1 2 3 4 5 ... 20
收藏数 404,124
精华内容 161,649
关键字:

provider