精华内容
下载资源
问答
  • 2016-03-21 13:56:07

    当我们的某个数据库或者数组列表等发生添加,删除等改变时,我们希望能在代码中立马知道,那么这时候可以使用内容观察者。

    先在想观察的方法里注册观察者

    public void add(String packageName) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("packagename", packageName);
        db.insert("info", null, values);
        db.close();
        //自定义注册一个内容观察者,uri的字符串随便写什么,但是要有content://头。
        context.getContentResolver().notifyChange(Uri.parse("content://com.example.wanghao.didisafe/applock.db"),null);
    }
    
    public void delete(String packageName) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        db.delete("info", "packagename = ?", new String[]{packageName});
        db.close();
        //自定义注册一个内容观察者
        context.getContentResolver().notifyChange(Uri.parse("content://com.example.wanghao.didisafe/applock.db"), null);
    }
    然后在正在运行的代码中拿到这个观察者的内容解析者
    //在初始化代码里注册一个内容解析者
    Uri uri = Uri.parse("content://com.example.wanghao.didisafe/applock.db");//Uri是一样的	
    observer = new MyObserver(new Handler());
    getContentResolver().registerContentObserver(uri, true, observer);
    在MyObserver里处理变化的数据
    private class MyObserver extends ContentObserver{
    
       public MyObserver(Handler handler) {
          super(handler);
       }
    
       @Override
       public void onChange(boolean selfChange) {
          super.onChange(selfChange);
          Log.i(TAG,"啊啊啊啊,我发现了数据库的内容变化了。");
          protectedPacknames = dao.findAll();
       }
    在destroy里反注册
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        getContentResolver().unregisterContentObserver(observer);
        observer = null;
    }

    更多相关内容
  • 自定义观察者模式

    2015-07-11 18:41:37
    观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,这个例子自定义了一个观察者模式
  • Android中内容观察者的使用---- ContentObserver详解

    千次下载 热门讨论 2011-12-08 18:57:49
    Android中内容观察者的使用---- ContentObserver详解
  • 前言: 原作者看到可以联系删除 触发器的大致步骤: 1, 定义基类(BseModel)方便每个Model继承, 2, 重写Model.php的boot()方法...1, 新建观察者文件,一般在APP\Observers 2, 注册观察者, 一般可以选择已经存在的Serv

    前言:

    原作者看到可以联系删除

    触发器的大致步骤:


    1, 定义基类(BseModel)方便每个Model继承,

    2, 重写Model.php的boot()方法,实现自定义beforeUpdate(等)&afterUpdate(等)

    3, 继承基类(BseModel)的模型新增beforeUpdate(等)&afterUpdate(等), 实现触发器

    观察者的大致步骤:

    1, 新建观察者文件,一般在APP\Observers

    2, 注册观察者, 一般可以选择已经存在的ServiceProvider(一般在App\Providers下面)里面或者新建一个都可以, 修改boot()方法

    3, 根据laravel提供的的10中方法, 更新观察者文件对应方法里面的代码

    可能存在的坑:

    1, 不管是自定义触发器还是观察者, 都只支持ORM Query, 类似 User::find(1)->update() 或者 User::where('id',$id)->first()->update

    User::find(1)->update();##可以监听
    User::where('id',$id)->first()->update();##可以监听
    User::where('id',$id)->update();##不可以监听

    2, 观察者的命名空间需要注意, 特别是如果有继承关系的, 想下面这样的情况,如果是前一个A在操作,不能观察到后一个A, 虽然是继承关系

    class App\Model\API\A extends App\Model\Common\A{}

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    先说自定义触发器:

    转自:https://my.oschina.net/slothcode/blog/874278

    该博客的原文地址已无法打开,不再附上

    Laravel框架本身没有自带beforeSave、afterSave的方法事件,但是可以自行加上这些,类似Yii框架,本身就自带这些方法,这类方法就像一个事件、触发器,可以在模型save之前,做一些属性值改变或者更多逻辑补充。

    列举一下可以增加哪些before/after事件方法

    before/afterCreate() 
    before/afterSave()
    before/afterUpdate()
    before/afterDelete()
    before/afterValidate()

    如何给Model增加这些方法呢?

    首先要给所有model定义一个基类,然后所有model都继承这个基类,

    <?php 
    namespace libs\Eloquent;
    abstract class Model extends \Illuminate\Database\Eloquent\Model {
    
    }

    定义基类之后,我们来看看 \Illuminate\Database\Eloquent\Model,既然要添加对模型的操作,还是得深入了解一下。找到boot方法,上面有注释

    /**
    	 * The "booting" method of the model.("引导"模型的方法)
    	 *
    	 * @return void
    	 */
    	protected static function boot()
    	{
    		$class = get_called_class();
    
    		static::$mutatorCache[$class] = array();
    
    		// Here we will extract all of the mutated attributes so that we can quickly
    		// spin through them after we export models to their array form, which we
    		// need to be fast. This will let us always know the attributes mutate.
              // 在这里我们将提取的所有变异属性,这样我们可以很快旋转通过他们出口模型后数组形式,我们需要快。这将让我们永远知道变异的属性。
    		foreach (get_class_methods($class) as $method)
    		{
    			if (preg_match('/^get(.+)Attribute$/', $method, $matches))
    			{
    				if (static::$snakeAttributes) $matches[1] = snake_case($matches[1]);
    
    				static::$mutatorCache[$class][] = lcfirst($matches[1]);
    			}
    		}
    
    		static::bootTraits();
    	}

    那么可以从这里入手,在自己定义的基类上重写一下这个方法

    /**
         * The "booting" method of the model.("引导"模型方法)
         * 覆盖之前/之后附加方法挂钩到模型事件。
         * @see \Illuminate\Database\Eloquent\Model::boot()
         * @return void
         */
        public static function boot() {
            parent::boot();
            $myself   = get_called_class();
            $hooks    = array('before' => 'ing', 'after' => 'ed');
            $radicals = array('sav', 'validat', 'creat', 'updat', 'delet');
            foreach ($radicals as $rad) {
                foreach ($hooks as $hook => $event) {
                    $method = $hook.ucfirst($rad).'e';
                    if (method_exists($myself, $method)) {
                        $eventMethod = $rad.$event;
                        self::$eventMethod(function($model) use ($method){
                            return $model->$method($model);
                        });
                    }
                }
            }
        }

    接着就是其他所有的model层,举个栗子

    <?php namespace ifish\Model\Ad;
    
    use libs\Eloquent\Model;
    class Ad extends Model
    {
    
        protected $table = 'ad_ad';
    
        protected $guarded = ['id'];
    
        public $timestamps = false;
    
        public function beforeCreate() {
            $this->channel = 'android';
            //或者不返回,因为只有一个布尔假将停止操作
            return true;
        }
    }

    只要调用create方法就会触发对应事件,例如

    Ad::create($data);
    //调用save必须在model内增加beforeSave或者afterSave
    Ad::save($data);

    之前在公司项目中,大量使用mysql触发器,遇到过的应该懂的,反正就一句话,不好维护,出问题了,不好定位,之后只能改造一下,把触发器都抽离,能用before/after就用,不用可以用观察者,反正怎么灵活怎么来。

    再说观察者:

    转自: 

    1,https://learnku.com/articles/18511

    2,https://learnku.com/articles/6657/model-events-and-observer-in-laravel

     

    根据文档的观察者,我们平时的使用方式是先创建一个 App\Observers 文件夹,然后创建想要操作的模型对应的 observer,比如说创建一个 UserObserver。

    <?php
    
    namespace App\Observers;
    
    use App\User;
    
    class UserObserver
    {
    }


    获得这个类之后,我们需要到 AppServiceProvider 的 boot 方法当中进行注册,也可以是其他的 ServiceProvider,不固定。

    public function boot()
    {
        User::observe(UserObserver::class);
    }


    别忘了引入 model,做完这些我们就可以各种操作了。
    laravel 已经为我们预先定义了 10 种方法:
    creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored。
    这些方法分别是进行时与完成之后。比如我们删除用户的时候,可以用 deleting 方法删除跟这个用户有关联的其他数据。

    public function deleting(User $user)
        {
           Thread::where('user_id', $user->id);
        }


    希望弄的更清楚的,可以参考 Laravel 中的模型事件与 Observer

    最近跟着 TDD 构建 Laravel 论坛笔记教程做下去,发现原来还能直接在模型当中直接定义 boot 方法,进行同样的操作。

    public static function boot()
    {
         static::deleting(function ($model) {
                $model->threads->delete();
            });
    }


    我预先定义了关联关系,所以取到相关的 Thread 能直接删除。
    insert 方法不会被监听到,以及批量删除时也同样不会被监听到,必须一条一条创建或者一条一条删除才行。(源码没咋研究,也不是很清楚,只是踩到过这坑)
    就比如上面的代码,这样子批量删除 thread 是不会被监听到的,你可以这样写

    $model->threads->each->delete();
    题外话:
    使用 laravel 快一年了,跟着教程学了差不多三分之一,真的学到了许多以前完全不知道的使用方式。因为有 laracasts 的账户,所以先过一遍视频,然后对着翻译教程再来一遍,收获真的巨大,顺便还能帮助译者改正一些小错误,也有不小的成就感。(小声:推荐小白学习,真的不要再说什么去阅读源码了,效果真的不大,过几天基本全忘关了。多写多用才能记住,才能更好的掌握编程这门技能,也是最快的学习方式)。

    ————————————————
    原文作者:tiroGuang
    转自链接:https://learnku.com/articles/18511
    版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。

    展开全文
  • 观察者模式(结合C#,Unity)

    千次阅读 2022-01-24 23:24:46
    文章目录前言概念简介观察者模式?发布-订阅模式?观察者(发布-订阅)模式应用不用设计模式实现用接口实现观察者模式代码结构介绍实现发布-订阅模式用事件实现改进接口法改进方式事件中心 前言 概念简介 先来看一段...

    前言

    概念简介

    先来看一段比较正式的介绍:
    观察者模式是软件开发中一种十分常见的设计模式,又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种。它定义了一种一对多的依赖关系,让多个观察者对象(Observer)同时监听某一个主题对象(Subject)。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

    以运动会的跑步比赛为例,假设场上有这几个对象:裁判,运动员,观众。那怎么才能知道比赛开始呢?这时候运动员和观众就会作为观察者,“关注”裁判(此时裁判就是主题对象),当裁判的发令枪响起时(主题对象状态发生改变),标志着比赛开始。然后运动员和观众收到“比赛开始”的通知后,各自做出他们的响应(观察者状态更新):跑步运动员向终点奔去,观众开始注视场上的赛况。

    所以整合一下,观察者模式要包括这些组成部分:
    1)一个主题对象,这个名词看起来比较抽象,我们干脆叫它 “被观察者”
    2)多个 关注/订阅 被观察者的 观察者
    3)被观察者的状态发生改变时,观察者会收到通知,然后观察者会做出他们各自的响应(或者说改变他们自己的状态)

    观察者模式?发布-订阅模式?

    关于“观察者模式和发布-订阅模式算不算两个独立的设计模式”这一讨论也是争议不断。
    之前提过观察者模式的别称是“发布-订阅模式”,但是有些地方会说这两种模式是不同的两个模式。
    从两者的实现结构来看,确实会有些不同。我这里用两张图来进行比较:
    在这里插入图片描述

    在这里插入图片描述

    可以明显地看到,发布-订阅模式在原来的观察者和被观察者之间加了一个调度中心
    那么消息发送者(Publisher)就不会将消息直接发送给订阅者(Subscriber),这意味着发布者和订阅者不知道彼此的存在。他们之间的通信全交给了作为第三方的调度中心。

    同样举个生活中的例子:一个 CSDN 博主被好几个粉丝关注,这些粉丝充当了“订阅者”的角色,他们“关注”(订阅)了博主。每当博主(消息发送者)发了一条新的博客,这条博客是发到了 CSDN 平台(作为调度中心)上,那么 CSDN 平台会将“博主发了一条新博客”这个消息通知给关注博主的粉丝们,然后这些粉丝就会做出他们各自的响应(比如浏览博文,点赞之类的)。
    有了调度中心后,博主只要安心地专注于发博客这件事情身上,他不用管谁是他的粉丝,因为“把更新消息发给粉丝”这件事是由 CSDN 平台这个调度中心来执行的,无需博主亲自通知;粉丝关注博主也是借由 CSDN 平台来记录的。
    总结来说,此时 CSDN 平台知道一个博主的粉丝具体是谁,然后当博主在 CSDN 平台上发博客时,CSDN 平台就通知该博主的所有粉丝。

    用程序的话语来解释:

    订阅者把自己想订阅的事件注册到调度中心,在发布者发布该事件到调度中心后,由调度中心统一调度订阅者用于响应事件的处理代码(订阅者收到事件触发消息后所要做的事)。

    那么对于发布-订阅模式:
    1)一共有3个组成部分:发布者,订阅者,调度中心
    2)发布者和订阅者完全不存在耦合
    对于观察者模式:
    1)一共有2个组成部分:被观察者,观察者
    2)被观察者和观察者是直接关联的,但它们是松耦合。这个是指被观察者状态变化时会自动触发观察者状态的变化,只是被观察者需要知道谁是观察者。

    但是发布-订阅模式弱化了对象之间的关联也会存在一些缺点,过度使用可能会使代码不好理解(这个后面会根据实际例子进行说明)

    组成结构上来看,它们确实会有不同。
    实现目的来看,它们是相同的,都是一个对象的状态发生改变时会通知那些与此对象关联的其他对象。
    我的个人理解是,发布-订阅模式是观察者模式的变种,也可以说是观察者模式的优化升级。我们也许不必把太纠结于“它们是不是同一种设计模式”,而是要充分学习它们的思想,在合适的时候运用到实际的开发中,为我们带来便利。不过理解它们在组成结构上的区别还是有必要的,万一面试会考呢?

    观察者(发布-订阅)模式应用

    试想一个常见的游戏场景:玩家 Gameover(死亡)
    玩家死亡时,会伴随着其他的一些事情发生,比如敌人停止移动,界面出现游戏失败 UI …
    这里我们先来考虑玩家死亡时敌人停止移动怎么实现
    注:这里就不展示敌人停止移动具体的代码实现了,我们用一个输出语句来表示;然后为了快速表示玩家死亡,我直接按下键盘的J键来触发

    不用设计模式实现

    那么借助 Unity 引擎和 C# 语言,我们用一种简单的实现方式:
    首先简单搭下游戏场景
    在这里插入图片描述
    球表示玩家,方块表示敌人。
    敌人脚本:

    public class Enemy : MonoBehaviour
    {
        public void StopMove()
        {
            print($"{gameObject.name}停止移动");
        }
    }
    

    敌人停止移动时把信息输出在控制台上。

    玩家脚本:

    public class Player : MonoBehaviour
    {
        public Enemy[] enemies;
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.J))
            {
                PlayerDead();
            }
        }
        private void PlayerDead()
        {
            NotifyEnemy();
        }
        private void NotifyEnemy()
        {
            for (int i = 0; i < enemies.Length; i++)
            {
                enemies[i].StopMove();
            }
        }
    }
    

    让玩家持有敌人的引用,然后玩家死亡时去调用敌人的 StopMove 方法。
    然后我们要在 Unity 编辑器里通过拖拽的方式把敌人游戏物体赋给 Player 的 enemies 数组
    在这里插入图片描述
    那么当我们运行游戏,按下 J 键时就会看到控制台输出了我们想要的结果:
    在这里插入图片描述
    到这里我们的需求就实现完了,是不是很简单呀?不用什么观察者模式都能实现。
    那么现在我给项目增加需求(你是故意找茬是不是?)(其实需求变化在软件开发中是很常见的事啦,习惯就好)
    玩家死亡后,不仅要让敌人停止移动,还要显示游戏失败的UI
    为了表示方便,我还是用输出语句来模拟
    UI 脚本:

    public class GameoverUI : MonoBehaviour
    {
        public void ShowGameOver()
        {
            print("Game over");
        }
    }
    

    修改玩家脚本:

    我们同样在编辑器中通过拖拽的方式为新增的公有变量 gameoverUI 赋值,并且还要修改 PlayerDead 方法。
    可以看到玩家脚本需要持有与它相关联的其他所有脚本对象,进而去调用这些脚本拥有的方法。
    也就是一个类要去调用另一个类的方法,一种最简单的方式就是去引用另一个类的对象。Player 脚本拥有了 Enemy 和 GameoverUI 类的成员,在通过面板拖拽实例化后,便能调用 Enemy 类和 GameoverUI 类的方法。
    那么设想如果玩家死亡会触发一系列对象状态的改变,远不止我们前面设置的2个需求,我们就不得不在玩家类中添加其他脚本的对象引用,这么做伴随着几个缺点

    1. 玩家类和其他与玩家死亡所关联的类会紧紧地耦合在一起,比如说 Enemy 类原先的 StopMove 方法换了个名字,那么我们不得不回到 Player 类中进行对应的修改。当一个类发生修改会影响到另一个类时,会对项目的维护和更新增添许多麻烦。
    2. 如果增加一个玩家死亡触发的事情,比如玩家死亡后播放一段音效,那么我们还要回到 Player 类对 PlayerDead 方法进行修改。
    3. 玩家类持有其他相关类的引用,这些引用变量要实例化后才能使用,否则会报 NullReferenceException。前面的例子中我是声明 public 成员变量,然后在编辑器面板中通过拖拽的方式进行赋值实例化。假如玩家类有很多其他类的引用,那我们还要在面板中一个个地拖拽。随着项目量的增大,有时候大量的拖拽赋值反而会很麻烦,也会显得很乱。当然有的童鞋可能会想把其他相关类的引用声明成 private,然后通过 GameObject.Find(“xxx”) ,GameObject.FindWithTag(“xxx”) 等方法来找到对应的游戏物体,接着通过 GetComponent 方法去找到脚本的组件将对象实例化,来替代面板上的拖拽。虽然也可以实现目的,但是依然比较麻烦,找游戏物体的过程中也会损耗性能,并且仍然存在前两点提的缺点:耦合性比较强

    现在我们用观察者(发布-订阅)模式对代码进行优化。

    用接口实现

    观察者模式代码结构介绍

    先看一种比较标准的观察者模式结构,这里用一种不大标准的 UML 图简要的表示下(用种简要的图来表示观察者模式中的各组成部分之间的联系):
    在这里插入图片描述
    Observer抽象观察者,提供收到被观察者状态变化的通知时触发的方法,我们先不管这个“抽象”的意思,先来看 Subject。

    Subject抽象主题对象(被观察者),持有抽象观察者的列表,因为一个被观察者可以有很多个观察者,但是观察者的类可能是不同的,为每种观察者定义一个列表显然是麻烦的,那我们要定义一个什么样的列表来容纳各个种类的观察者呢?这时候就要用上“基类”的思想,可以让所有的观察者继承自同一个父类,最后列表里装的是这个父类就行了,而这个父类其实并不需要是具体存在的某一个观察者,我们只需把它定义成抽象的,然后在运行期间让这个抽象的父类去指代某一个具体的观察者(有点像多态,也是面向对象设计原则中 “里氏替换原则” 的应用)。这样我们写代码时只用处理抽象基类,而这个抽象基类具体指代的是哪一个具体的子类,是程序运行时会根据实际情况转化的。这种思想可以用两种方式来实现:抽象类和接口。我推荐用接口来实现,原因如下:

    1. 像一些语言如 C# 和 Java ,只允许单继承,如果我们用抽象类来表示的话,会占用掉唯一的继承位,比如 Unity 挂在物体上的游戏脚本要继承自 MonoBehaviour,这种情况下我们只能用接口,因为一个类可以实现多个接口。很多编程语言都有类似“接口”的相关语法,因此用接口实现观察者模式是比较通用的思想,基本不受各语言语法差异的影响。
    2. 这些观察者的共同点只是收到主题对象状态的通知后要触发某些事情,假如我们用一个 Update 方法来表示触发时执行的方法(观察者状态的更新),那既然每个观察者只要实现各自的 Update 方法就行了,其实我们不妨用“实现接口”来替代“继承父类”。只实现抽象的方法更符合接口的定义。

    因此可以定义一个接口作为抽象观察者,让各个观察者去实现这个接口,那么 Subject 的列表里装的就是抽象的接口,在运行期间去访问观察者列表是实际上访问的也就是具体的那些观察者。

    被观察者也可以有一个统一的接口,提供添加观察者,移除观察者,以及通知观察者的方法。每个具体的被观察者可以实现这个接口。

    ConcreteObserver:实现了 Observer 接口的具体观察者。
    ConcreteSubject:实现了 Subject 接口的具体被观察者。

    以上是从代码层面介绍观察者模式各组成部分与各部分之间的联系。这样被观察者只负责在自身状态发生改变或做出某种行为时向自身的订阅清单(也就是存储观察者的列表)发出“通知”(Notify)观察者只负责向目标“订阅”它的变化(通过 Subject 的 Attach),以及定义自身在收到目标“通知”后所需要做出的具体行为 (Observer 的 Update)。至于被观察者怎样准确地通知到每一个观察者,这件事交给被观察者的抽象观察者列表去处理,在运行期间再转化为具体的观察者对象,而不是 “被观察者先持有所有观察者的对象,再直接调用这些对象的行为(方法)”。

    那么用代码实现观察者模式的实现思路就是:
    1)定义抽象观察者的接口,定义自身在收到通知后触发的方法,然后用具体观察者去实现接口。
    2)定义抽象被观察者的接口,定义添加观察者,移除观察者,通知观察者的方法,然后用具体被观察者去实现接口。
    因为 C# 的接口不能定义字段,所以我们不能在抽象被观察者中定义一个列表。在实际的使用过程中,我倾向于把定义观察者列表这一操作放到具体被观察者中去实现。

    3)接下来就是让观察者和被观察者关联到一起。虽然被观察者仍然持有了观察者列表,但是这个列表里装的东西是抽象的接口,我们不必直接持有每一个观察者对象的引用,像之前写的 Player 脚本那样:
    在这里插入图片描述

    而是存储统一的类似所有观察者基类的抽象观察者,所以我们能用抽象观察者去概括具体观察者,能用一个统一的列表去涵盖所有的观察者

    public class Player : ISubject{
    	private List<IObeserver> observers=new List<IObeserver>();
    	...
    }
    

    然后每个观察者在自己的类中把自己添加到被观察者的观察者列表中(相当于订阅了被观察者),当被观察者发起通知时,会去遍历持有的观察者列表,调用每个抽象观察者的 “Update” 方法,那么调用抽象层实际上也就会调用具体观察者重写的抽象接口中的方法。
    在被观察者的眼中,它所交互的都是抽象的观察者,因此不管观察者的代码怎么发生变动,在被观察者的眼中始终是一模一样的抽象观察者,只是实际运行时抽象才指代具体,这样对被观察者的代码本身没有任何影响。所以说观察者模式是低耦合的。

    因此从代码层面理解观察者模式,就是在原先的结构上加了“抽象”层。

    为什么被观察者的观察者列表要是抽象的?为什么抽象能帮助代码解耦?如果看到这里你能够在心中回答这两个问题,相信你有能力手写观察者模式的代码了。如果还是不太清楚也没关系,毕竟概念可能会有一点“抽象”,那么我们直接通过实战来学习!

    下面用具体代码对之前玩家死亡的案例进行改进,来帮助大家加深对上述概念的理解。(这里只会给出部分代码,因为我把重点放在更实用的发布-订阅模式上)
    如果用严格意义上的观察者模式,作为观察者的敌人需要把自己添加给被观察者的列表,但是添加的方法是定义在抽象被观察者接口中的。
    在这里插入图片描述

    因此具体的被观察者,也就是玩家,持有“添加观察者”这个实例方法,如果要调用一个类的实例方法,就必须先实例化这个类,这就导致我们要在具体观察者的类中持有对玩家的引用,提高了观察者与被观察者之间的耦合性,就像下面这张图这样:
    在这里插入图片描述
    当然,稍微变通一下是可以解决。比如将玩家类加上单例模式,或者在被观察者接口中删去添加观察者和移除观察者的方法,然后把玩家类中的观察者列表改为 static,这样我们可以直接在具体观察者类中获取玩家类中静态的列表,调用列表本身的添加方法:
    在这里插入图片描述
    在这里插入图片描述
    但是使用静态会让一个类的所有实例共享这个数据,有时候可能并不适用。把所有被观察者设为单例也并不是个好的选择。

    那之前说了,观察者模式的升级版——发布-订阅模式添加了一个调度中心,能够使观察者和被观察者完全解耦,这在实际开发中是很实用的。因此接下来我会着重于用接口来实现发布-订阅模式。

    实现发布-订阅模式

    我们用一个 GameManager 作为调度中心,相当于一个管理者来管理所有的观察者,并且对外提供添加、移除观察者和通知的方法。那么原先的被观察者发布通知,直接调用的是 GameManager 的通知方法,观察者把自己添加到观察者列表,调用的是 GameManager 的添加方法。观察者与被观察者之间不建立任何联系,全靠第三方的调度中心通信,这样可实现跨模块的交互。
    观察者接口:

    public interface IObserver 
    {
        public void ResponseToNotify();
    }
    

    这里因为有了 GameManger,我们就无需写个多余的被观察者接口。而且像 GameManager 这种作为管理者的脚本,整个游戏期间只需有唯一的对象,因此建议利用单例模式把管理器脚本设为单例,一旦将 GameManager 实例化后,之后使用的都是这个唯一存在的 GameManager【本篇博客不会详细介绍单例模式的相关知识点,但会演示如何使用,并且使用的也是简单的单例模式版本。想要了解更多关于单例模式的可以看这篇文章 Unity 单例基类(结合单例模式)。】

    GameManager脚本(无需继承 MonoBehaviour,我们不必把此脚本挂到任何游戏物体上):

    public class GameManager 
    {
        //单例模式应用
        private static GameManager instance;
        public static GameManager Instance
        {
            get
            {
                if (instance == null)
                    instance = new GameManager();
                return instance;
            }
        }
        private List<IObserver> observers = new List<IObserver>();
        //添加观察者
        public void AddObserver(IObserver observer)
        {
            observers.Add(observer);
        }
        //移除观察者
        public void RemoveObserver(IObserver observer)
        {
            observers.Remove(observer);
        }
        //发送通知给观察者
        public void Notify()
        {
            for (int i = 0; i < observers.Count; i++)
            {
                observers[i]?.ResponseToNotify();
            }
        }
    }
    

    玩家脚本:

    public class NewPlayer : MonoBehaviour
    {
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.J))
            {
                GameManager.Instance.Notify(); //触发玩家死亡通知
            }
        }
    }
    

    敌人脚本:

    public class NewEnemy :MonoBehaviour, IObserver
    {
        private void Start()
        {
            GameManager.Instance.AddObserver(this);
        }
        private void OnDestroy()
        {
            GameManager.Instance.RemoveObserver(this);
        }
        public void ResponseToNotify()
        {
            print($"{gameObject.name}停止移动");
        }
    }
    

    游戏结束 UI 脚本:

    public class NewGameOverUI : MonoBehaviour,IObserver
    {
        public void ResponseToNotify()
        {
            print("游戏结束");
        }
        void Start()
        {
            GameManager.Instance.AddObserver(this);
        }
        private void OnDestroy()
        {
            GameManager.Instance.RemoveObserver(this);
        }
    }
    

    那么以上就是用接口实现发布-订阅模式的代码。
    可以看到,当被观察者 Player 发布死亡通知时,GameManager 会去遍历自身的抽象观察者列表,在它的眼中,无论是敌人还是 UI,全都当作抽象观察者来处理。因此在不修改接口的前提下,观察者与被观察者的代码变动互不影响。被观察者只管将消息发布到 GameManager,然后通知观察者的事全让 GameManager 来做。观察者的其他代码不管怎么改,在 GameManager 眼中始终是抽象的观察者,而且与被观察者也没有任何联系。
    需要注意的是
    将观察者添加到观察者列表后,必须在合适的时候把观察者从观察者列表中移除掉!!!
    举个例子,如果在当前游戏场景把敌人添加到列表中,然后转入下一个没有敌人的游戏场景。因为 GameManager 相当于全局的对象,此时前一个场景的敌人仍然存在于观察者列表里,我们知道发布通知时会通知观察者列表里的所有对象,可是此时敌人在场景中已经不存在了呀,这时可能就会发生诡异的事情了。
    一般来说推荐的组合是:
    1)在 Awake/Start 方法中把观察者添加到列表中,在 OnDestroy 方法中把观察者从列表中移除。
    2)在 OnEnable 方法中把观察者添加到列表中,在 OnDisable 方法中把观察者从列表中移除。

    用事件实现

    现在大家回想一下观察者(发布-订阅)模式的实现目的,是不是和 C# 事件的概念差不多啊?
    C# 事件的概念大致是:

    一个类或者对象中的事件发生后会通知订阅了这个事件的其他类、其他对象。别的类、对象在接收到这个通知之后就会纷纷作出他们各自的响应

    完美契合观察者模式。👍

    那刚刚用接口实现的方式一定能用事件替代。可我为什么要先介绍接口的实现方式呢?因为抛掉语言包袱,接口的实现还是更加通用一点。不过,C# 把 观察者模式的思想嵌入到了事件这一语法当中,如果你是 C# 的开发者,直接使用 C# 提供的事件,多是一件美事啊~

    玩家死亡本身可以当作玩家的一个事件,死亡事件触发后,与玩家死亡关联的敌人和 UI 会收到通知,随后触发各自的一些事情。那么现在我用事件来实现玩家死亡的需求。
    有了事件之后,我们不必把观察者的整个类存在列表里了。因为事件是基于委托的,相当于委托的包装器,而委托绑定的是与之相匹配的方法,所以我们不必定义一个观察者列表,只需定义一个事件,就能涵盖所有与之相匹配的方法。现在我们具体关注的只是事件发生后观察者的某一个会随之触发的方法
    为了解耦,我们还是把事件定义在一个 EventManager 当中,作用和 GameManager 是一样的。这个事件可以匹配无参无返回值的方法。
    EventManager 脚本:

    public  class EventManager
    {
        private event UnityAction OnPlayerDead;
        private static EventManager instance;
        public static EventManager Instance
        {
            get
            {
                if (instance == null)
                    instance = new EventManager();
                return instance;
            }
        }
        public void AddListener(UnityAction action)
        {
            OnPlayerDead += action;
        }
        public void RemoveListener(UnityAction action)
        {
            OnPlayerDead -= action;
        }
        public void TriggerEvent()
        {
            OnPlayerDead?.Invoke();
        }
    }
    

    玩家脚本:

    public class Player : MonoBehaviour
    {
    
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.J))
            {
                EventManager.Instance.TriggerEvent();
            }
        }
       
    }
    

    敌人脚本:

    public class Enemy : MonoBehaviour
    {
        private void Start()
        {
            EventManager.Instance.AddListener(StopMove);
        }
        private void OnDestroy()
        {
            EventManager.Instance.RemoveListener(StopMove);
        }
        public void StopMove()
        {
            print($"{gameObject.name}停止移动");
        }
    }
    

    游戏结束 UI 脚本:

    public class GameoverUI : MonoBehaviour
    {
        private void Start()
        {
            EventManager.Instance.AddListener(ShowGameOver);
        }
        private void OnDestroy()
        {
            EventManager.Instance.RemoveListener(ShowGameOver);
        }
        public void ShowGameOver()
        {
            print("Game over");
        }
    }
    

    运行结果:
    在这里插入图片描述

    改进

    前面我们分别用接口和事件的方式,利用发布-订阅模式的思想,将最初的代码结构优化了许多,降低了对象间的耦合,此时观察者和被观察者之间的通信全由调度中心来转接。但是我们优化后的代码仍然存在一定的问题。

    不管是接口还是事件的实现方式,在消息通知发布时触发的方法全是无参无返回值的,如果我的触发方法带有参数要怎么办?而且游戏中不只有玩家死亡这个情况符合观察者模式,比如游戏胜利,触碰到某个机关,敌人死亡…都可以当作游戏中比较重要的事件。只要是事件,那么就能符合观察者模式的思想。对于接口实现方式,我要对观察者列表进行升级,使之能够涵盖各个事件中涉及的观察者;对于事件实现方法,我要将各种类型的事件存储起来。

    接口法改进方式

    首先解决带有参数的通知问题,我们沿用 C# 事件中的 EventArgs 类(要先 using System 引入 EventArgs 所在的命名空间),它相当于一个参数基类,然后实际传参时可以自定义这个基类的子类,包装我们想要传入的参数类型。
    不过 EventArgs 是 C# 为我们提供好的一个类,如果是用其他语言实现怎么办?我们可以模拟 EventArgs ,自定义一个参数基类,然后再定义这个基类的各个子类用于包装参数。

    然后我们对观察者接口进行改进,接口方法的参数设为 EventArgs,如果到时候触发的方法是无参的,我们传入 null 或者 EventArgs.Empty 就行

    public interface IObserver 
    {
        public void ResponseToNotify(EventArgs e);
    }
    

    借助“事件”的思想,把原来的观察者列表升级为观察者字典,key 是事件名,value是抽象观察者列表:

    public class GameManager 
    {
        //单例模式应用
        private static GameManager instance;
        public static GameManager Instance
        {
            get
            {
                if (instance == null)
                    instance = new GameManager();
                return instance;
            }
        }
        private Dictionary<string, List<IObserver>> observerDic = new Dictionary<string, List<IObserver>>();
        //添加观察者
        public void AddObserver(string eventName, IObserver observer)
        {
            if (observerDic.ContainsKey(eventName))
                observerDic[eventName].Add(observer);
            else
                observerDic.Add(eventName, new List<IObserver> { observer });
            
        }
        //移除观察者
        public void RemoveObserver(string eventName, IObserver observer)
        {
            if (observerDic.ContainsKey(eventName))
                observerDic[eventName].Remove(observer);
        }
        //发送通知给观察者
        public void Notify(string eventName, EventArgs e)
        {
            if (observerDic.ContainsKey(eventName))
            {
                for(int i = 0; i < observerDic[eventName].Count; i++)
                {
                    observerDic[eventName][i]?.ResponseToNotify(e);
                }
            }
        }
    }
    

    其实为了代码复用,可以把单例模式的写法封装成一个单例模块,然后让管理器脚本继承单例模块。这个操作会在稍后介绍事件中心时演示。

    此外,我们可以把各个事件的名字设成常量放在一个全局的静态类,方便后面调用:

    public static class EventName 
    {
        public const string PlayerDead = "PlayerDead";
        public const string Test = "Test";
    }
    

    这时候我们测试传参的情况,假设玩家死亡时会把玩家名字传给游戏失败 UI 来显示
    先自定义一个用于包装参数的类,继承自 EventArgs:

    public class PlayerDeadEventArgs : EventArgs {
        public string playerName;
    }
    

    玩家脚本:

    public class NewPlayer : MonoBehaviour
    {
         void Update()
        {
            if (Input.GetKeyDown(KeyCode.J))
            {
                GameManager.Instance.Notify(EventName.PlayerDead,
                    new PlayerDeadEventArgs {playerName=gameObject.name }); //触发玩家死亡通知
            }
        }
    }
    

    Notify 方法的第二个参数便是我们自定义的传参类,我们把玩家游戏物体的名字封装进我们自定义的这个传参类。如果不需要传参,此处可以传个 null 或者 EventArgs.Empty,如:

    GameManager.Instance.Notify("xxx",EventArgs.Empty);
    

    EventArgs.Empty 出自于 EventArgs 类,是已经定义好的了,相当于表示空的事件参数。既然系统为我们提供好了,那我们就可以直接拿来用了。
    在这里插入图片描述

    游戏失败 UI 脚本:

    public class NewGameOverUI : MonoBehaviour,IObserver
    {
       
        void Start()
        {
            GameManager.Instance.AddObserver(EventName.PlayerDead, this);
        }
        private void OnDestroy()
        {
            GameManager.Instance.RemoveObserver(EventName.PlayerDead, this);
        }
    
        public void ResponseToNotify(EventArgs e)
        {
            var data = e as PlayerDeadEventArgs;
            if (data != null)
            {
                print($"游戏结束,{data.playerName}阵亡");
            }
            
        }
    }
    

    收到自定义的参数类后,我们可以用 as 进行类型转换,如果不为空,说明我们收到了参数类,然后就可以取出封装在参数类中的数据。

    敌人脚本:

    public class NewEnemy :MonoBehaviour, IObserver
    {
        private void Start()
        {
            GameManager.Instance.AddObserver(EventName.PlayerDead, this);
        }
        private void OnDestroy()
        {
            GameManager.Instance.RemoveObserver(EventName.PlayerDead, this);
        }
        public void ResponseToNotify(EventArgs e)
        {
            print($"{gameObject.name}停止移动");
        }
    }
    

    敌人脚本在事件触发时无需对传的参数进行处理。
    运行结果:
    在这里插入图片描述

    事件管理中心

    既然是 C#,我们直接用事件会更加方便。为了改进之前的不足,我们可以创建一个字典来存储所有事件。然后其他的思路与接口改进版类似。这里我单独为事件管理中心写了篇文章。
    这里是链接:Unity 事件管理中心


    小瑕疵:
    虽然发布-订阅模式能够极大地降低耦合,但是有个缺点就是我们看代码可能很难看出观察者和被观察者的关系,因为被中间的一个调度中心给隔开了,有时候可能不方便 debug。


    委托与事件系列:
    C#委托(结合 Unity)
    C#事件(结合 Unity)
    观察者模式(结合C# Unity)
    Unity 事件管理中心
    事件番外篇:UnityEvent

    展开全文
  • 有匹配模式么O(∩_∩)O~,要让内容观察者知道我们的数据发生变化了,要调用notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer)这个方法,第一个参数就是我们匹配的uri,第二个参数是要提醒哪个内容...

    最不知疲倦的事情就是我们最喜欢的事情

    内容提供者ContentProvider,UriMatcher

    说到,内容提供者,我们立马想到的应该是数据库,让我们先去建立数据库吧

    > public class MySqliteOpenHelper extends SQLiteOpenHelper {
        public MySqliteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
    
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        }
     }

    继承SQLiteOpenHelper要我们实现这三个方法,先看下构造函数,第一个参数是上下文,第二个参数是数据库名,第三个参数是..我也没用过,一般填null,第四个参数是数据库的版本号,版本号是什么意思呢,是这样的,当我们new MySqliteOpenHelper对象的时候,如果同一数据库传入版本号1即会执行onCreat方法,你再继续接着new还传版本号1的话,就不执行onCreat了,因为版本1的数据库已经决定好了,但是同一数据库如果你传2的话,这是表明数据库升级了,这时候执行onUpgrade这个方法.好了,我们这里不需要对数据库进行升级,也只创建一个数据库,所以我们把这个类改造一下

    > public MySqliteOpenHelper(Context context) {
            super(context, "db1", null, 1);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE myTableName(_id integer primary key autoincrement, name TEXT)");
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        }

    在onCreat中我们创建了一个表,名字叫做,没错,就叫做我的表名myTableName,它有两个字段_id主键,这android系统的数据库也喜欢把这个当主键,我们跟随潮流,还有一个字段就是name了,好了SqliteOpenHelper搞好了,我们开始我们的内容提供者吧

    > public class MyContentProvider extends ContentProvider{
        @Override
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            return null;
        }
    
        @Nullable
        @Override
        public String getType(Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            return null;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            return 0;
        }
    

    ContentProvider是一个抽象类,继承它要我们实现上面那些方法.看到onCreat方法,我们当然看它了,点进去看看

    > *
         * @return true if the provider was successfully loaded, false otherwise
         */
        public abstract boolean onCreate();

    前面还有一大堆介绍的,有点长.意思就是说如果我们的SQLiteOpenHelper准备好了后就返回true否则返回false,所以我们这样改

    > private MySqliteOpenHelper mMySqliteOpenHelper;
    @Override
        public boolean onCreate() {
    
            mMySqliteOpenHelper = new MySqliteOpenHelper(getContext());
    
            return mMySqliteOpenHelper != null;
        }
    

    接下来我们做什么呢,等等,内容提供者是四大组件之一啊,所以我们应该去清单文件中配置下它啊

    > <provider
                android:name=".db.MyContentProvider"
                android:authorities="com.it.contentprovidernote.myauthorities"/>

    看到这个authorities没有,我们俗称主机名,怎么翻译来的我也不知道….如果不配置它是会提示有错误!当然配置成什么样根据自己决定.配置好主机名就轮到我们的UriMatcher登场了

    > public static final String MYAUTHORITIES = "com.it.contentprovidernote.myauthorities";
        public static final String MYTABLENAME = "myTableName";
        public static final int MYTABLENAME_CODE = 1;
    
    
        private static UriMatcher mUriMatcher ;
        static {
            mUriMatcher  = new UriMatcher(UriMatcher.NO_MATCH);
            mUriMatcher.addURI(MYAUTHORITIES,MYTABLENAME,MYTABLENAME_CODE);
        }

    Uri匹配器UriMatcher 看不懂是干嘛的是吧,干说不好说 ,等下在query方法中有注释结合代码一看就明白了,看下匹配器的这个方法addURI(String authority, String path, int code),第一个参数即我们在清单文件配置的主机名,第二个是自己定义的一个子路径,第三个是匹配绑定值,下面我们会根据这个值找到我们的uri.注意这个值必须是个正数,不然会抛一个异常,它的源码有提示,而且一个uri只能绑定一个值,千万不要让它绑定多个值,那就没用了.好了,这些都弄好了可以开始我们的增删改查了,那个getType就算了吧,具体我也没用过.

    
    @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    
            Cursor cursor = null;
            switch (mUriMatcher.match(uri)){  //这就是匹配器的作用所在了,通过匹配得到的绑定值,一个内容提供者可以操作多种自定义功能
                case MYTABLENAME_CODE:
                    SQLiteDatabase db = mMySqliteOpenHelper.getReadableDatabase();
                    cursor = db.query(MYTABLENAME, projection, selection, selectionArgs, null, null, sortOrder);
                    //这里我们不能把db关了,如果关了的话,返回的cursor就没了,由此可以得出在内容提供者里是不用我们关db的,它自己应该处理了
                default:
                    break;
            }
    
            return cursor;
        }
    
        @Nullable
        @Override
        public String getType(Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
    
            long insert = -1;
            switch (mUriMatcher.match(uri)){
                case MYTABLENAME_CODE:
                    SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
                    insert =  db.insert(MYTABLENAME, null, values);   //看源码插入失败返回-1,即如果插入失败不提醒内容观察者数据改变了
                    if (insert != -1) getContext().getContentResolver().notifyChange(uri,null);
                default:
                    break;
            }
            uri.withAppendedPath(uri,String.valueOf(insert));
            return uri;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
    
            int delete = 0;
            switch (mUriMatcher.match(uri)){
                case MYTABLENAME_CODE:
                    Log.e("MyContentProvider", "进来了");
                    SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
                    delete =  db.delete(MYTABLENAME,selection,selectionArgs);
                    if (delete != 0) getContext().getContentResolver().notifyChange(uri,null);
                default:
                    break;
            }
            return delete;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    
            int update = 0;
            switch (mUriMatcher.match(uri)){
                case MYTABLENAME_CODE:
                    SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
                    update = db.update(MYTABLENAME,values,selection,selectionArgs);
                    if(update != 0) getContext().getContentResolver().notifyChange(uri,null);
                default:
                    break;
            }
            return update;
        }

    看到代码,这下应该明白了我们匹配器UriMatcher的作用了吧,让我们的类变的这么牛,这是什么设计模式来着…..有匹配模式么O(∩_∩)O~,要让内容观察者知道我们的数据发生变化了,要调用notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer)这个方法,第一个参数就是我们匹配的uri,第二个参数是要提醒哪个内容观察者数据发生改变了,填null即是提醒所有注册了这个uri的内容提供者的.所以我们一般都填null.好了,我们内容提供者到这就大功高成了.一般写好内容提供者因为有数据库,基本都是去测试测试的,所有我们也去测试一下吧,在这用下android studio的测试,看图
    这里写图片描述
    android studio帮我们去创建了测试包和类了,不用我们像eclipse那样去清单文件配置之类的了,好了开始写测试方法吧

    > Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
        public void testInsert()
        {
    
            ContentValues values = new ContentValues();
            for (int i = 1; i < 51; i++) {
                values.put("name","走吧走吧去吧去吧      " + i);
                getContext().getContentResolver().insert(url,values);
            }
    
        }
    

    这里内容提供者的uri.parse(string) string都是content://后面再跟我们自己要操作的数据库定义好的uri.如何运行呢,我们把鼠标移动方法上,然后这样点击运行测试方法就行了,对了,运行的时候要开启模拟器,因为测试也是需要安装APK的
    运行测试

    运行结果
    运行成功
    这就是测试成功的样子了,让我们去把数据库导出来,看看到底是不是真的成功了
    数据库

    那测试失败是什么样子呢,我们来看看,我们把uri路径改错来.这样就提示我们错了
    测试错误

    其它功能测试方法

    > 
     public void testDeletee()
        {
            getContext().getContentResolver().delete(url, "_id = ?", new String[]{"1"});
        }
    
        public void testUpdate()
        {
            Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
    
            ContentValues values = new ContentValues();
            values.put("name","999999999999");
            getContext().getContentResolver().update(url, values,"_id = ?", new String[]{"2"});
        }
    
    
        public void testQuery(){
    
            Cursor cursor = getContext().getContentResolver().query(url, new String[]{"_id", "name"}, "_id < ?", new
                    String[]{10 + ""}, null);
            while (cursor.moveToNext()){
    
                String id = cursor.getString(cursor.getColumnIndex("_id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
    
                Log.e("ApplicationTest", "========================="+id+" :    " + name);
            }
    
        }
    

    我都测试过了,都是成功的,说明我们的内容提供者是没有错误的,那我们开始把它用起来吧.我们用一个listview来显示数据,至于怎么加载数据我们用AsyncTask吧,顺便在这里介绍一下它的基本用法,它是一个抽象类,我们继承一下它

    
    >  class MyAsynctask extends AsyncTask<Void, Integer, ArrayList<String>> {
    
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
    
            @Override
            protected ArrayList<String> doInBackground(Void... params) {
                return null;
            }
    
            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
            }
    
            @Override
            protected void onPostExecute(ArrayList<String> strings) {
                super.onPostExecute(strings);
            }
    

    这里可以看出 AsyncTask <\Params, Progress, Result>第一个参数是doInBackground的参数类型,它的参数由执行函数execute提供.它运行在子线程,第二个参数是onProgressUpdate的参数类型,从它的解释我们可以看出,当它运行了publishProgress(Progress… values)即也是这个函数的参数类型,它也会在UI线程调用

    第三个参数是doInBackground的返回类型也是onPostExecute的参数类型,它运行在UI线程,即表示在子线程加载的数据返回到UI线程在onPostExecute处理数据,至于onPreExecute这个方法看名字就知道是执行子线程前的准备工作,在UI线程执行,这里我们不用它,来看看实现吧.

    >  @Override
            protected ArrayList<String> doInBackground(Void... params) {
    
                ArrayList<String> list = new ArrayList<String>();
                Cursor cursor = getContentResolver().query(url, new String[]{"name"}, null, null, null);
                mDialog.setMax(cursor.getCount());
                int i= 0;
                while (cursor.moveToNext()) {
                    SystemClock.sleep(50); //太快了,睡50毫秒
                    publishProgress(++i);
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    list.add(name);
                }
                return list;
            }
    
            @Override
            protected void onProgressUpdate(Integer... values) {
                mDialog.setProgress(values[0]);
            }
    
            @Override
            protected void onPostExecute(ArrayList<String> lsit) {
    
                mListView.setVisibility(View.VISIBLE);
                mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
                mListView.setAdapter(mAdapter);
                mDialog.dismiss();//加载完了就消失
           }
    

    这里我们把加载的进度也实现了,在onCreat中初始化

    > @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mListView = (ListView) findViewById(R.id.listview);
            mListView.setCacheColorHint(0x00000000);
            mListView.setVisibility(View.GONE);
            new MyAsynctask().execute();
    
            mDialog = new ProgressDialog(this);
            mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mDialog.setTitle("加载中");
            mDialog.show();
    

    好了写了这么久,让我们来看看效果吧.
    效果

    好了,最后来加上我们的内容观察者吧.ContentObserver它是一个抽象类,我们需要继承它

    > class MyContentObserver extends ContentObserver {
    
            /**
             * Creates a content observer.
             *
             * @param handler The handler to run {@link #onChange} on, or null if none.
             */
            public MyContentObserver(Handler handler) {
                super(handler);
            }
    
    
            @Override
            public boolean deliverSelfNotifications() {
                return super.deliverSelfNotifications();
            }
    
            @Override
            public void onChange(boolean selfChange) {
    
    
            }
    
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                super.onChange(selfChange, uri);
            }
        }

    看,构造方法是必须实现的,至于后面三个方法是我重写的,第一个方法deliverSelfNotifications它默认是返回false的看它的解释是说如果它自己改变要通知别的观察者的话,返回true,我没用过///还有看下面两个双炮胎onChange,其实下面那个多一个参数即那个两个参数的内部调用的也是一个参数的,我们这里不判断哪个uri变化了,所以为了简单点,我们只要重写那个一个参数的onChange就好了,至于那个参数selfchange顾名思义就是判断自己改变了没有,跟讲的第一个方法那样,我没用过,不理它…望知道的可以推荐个好的博客看看

    > Toast.makeText(getApplicationContext(), "数据库改变了,我收到通知了,接下来怎么办你看着办吧", Toast.LENGTH_SHORT)
                        .show();
    

    然后在onCreat中注册它,registerContentObserver中第一个第三个参数都好理解,第二个参数看解释说如果为false的话你注册的uri对应的数据改变了,会提醒你,这个uri的祖先数据改变了也会提醒,额,好像我们这个就是祖先,应该是后面跟个/string就叫它的孩子了.如果为true的话不但祖先变了会提醒还有孩子也会提醒你,所以我们一般是填false的

    > mObserver = new MyContentObserver(mHandler);
            getContentResolver().registerContentObserver(url, false, mObserver);
    

    再在onDestory注销它

    
    > @Override
        protected void onDestroy() {
            if (mObserver != null) {
                getContentResolver().unregisterContentObserver(mObserver);
                mObserver = null;
            }
            super.onDestroy();
        }
    

    然后在我们的MyAsnyTask的onPostExecute方法,加载完数据后,我们3秒后去删除_id为2的数据 ,然后看看内容能不能收到数据改变的提醒

    > 
     @Override
            protected void onPostExecute(ArrayList<String> lsit) {
    
                mListView.setVisibility(View.VISIBLE);
                mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
                mListView.setAdapter(mAdapter);
                mDialog.dismiss();
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        getContentResolver().delete(url, "_id = ?", new String[]{"2"});
                    }
                }, 3000);
            }
    

    加了内容观察者后
    xiaoguo

    完整的MainActivity代码

    
    > public class MainActivity extends Activity {
    
        private ListView mListView;
        private ArrayAdapter<String> mAdapter;
        Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
        private MyContentObserver mObserver;
        private Handler mHandler = new Handler();
        private ProgressDialog mDialog;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mListView = (ListView) findViewById(R.id.listview);
            mListView.setCacheColorHint(0x00000000);
            mListView.setVisibility(View.GONE);
            new MyAsynctask().execute();
    
            mDialog = new ProgressDialog(this);
            //默认是圈的,这里设置为水平条
            mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mDialog.setTitle("加载中");
            mDialog.show();
    
    
            mObserver = new MyContentObserver(mHandler);
            getContentResolver().registerContentObserver(url, false, mObserver);
        }
    
        class MyContentObserver extends ContentObserver {
            public MyContentObserver(Handler handler) {
                super(handler);
            }
    
            @Override
            public void onChange(boolean selfChange) {
                Toast.makeText(getApplicationContext(), "数据库改变了,我收到通知了,接下来怎么办你看着办吧", Toast.LENGTH_SHORT)
                        .show();
    
            }
    
        }
    
        class MyAsynctask extends AsyncTask<Void, Integer, ArrayList<String>> {
    
            @Override
            protected ArrayList<String> doInBackground(Void... params) {
    
                ArrayList<String> list = new ArrayList<String>();
                Cursor cursor = getContentResolver().query(url, new String[]{"name"}, null, null, null);
                mDialog.setMax(cursor.getCount());
                int i= 0;
                while (cursor.moveToNext()) {
                    SystemClock.sleep(50);
                    publishProgress(++i);
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    list.add(name);
                }
                return list;
            }
    
            @Override
            protected void onProgressUpdate(Integer... values) {
                mDialog.setProgress(values[0]);
            }
    
            @Override
            protected void onPostExecute(ArrayList<String> lsit) {
    
                mListView.setVisibility(View.VISIBLE);
                mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
                mListView.setAdapter(mAdapter);
                mDialog.dismiss();
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        getContentResolver().delete(url, "_id = ?", new String[]{"2"});
                    }
                }, 3000);
            }
        }
    
        @Override
        protected void onDestroy() {
            if (mObserver != null) {
                getContentResolver().unregisterContentObserver(mObserver);
                mObserver = null;
            }
            super.onDestroy();
        }
    }
    

    R.layout.item_text这个是我抄的android.R.layout.simple_list_item_1因为我不知道怎么改它的原始的字体颜色

    
    > <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:padding="15dp"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"
        android:textColor="#000000"
        android:textSize="25sp"/>
    

    啊,好长.说实话,真有点啰嗦/也许看到这么长,大家都不看了吧

    展开全文
  • C++观察者模式(Observer)

    千次阅读 2022-04-08 11:29:17
    观察者是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象。
  • 自定义滚轮WheelView系列---设计模式之观察者模式 一个稍微复杂的自定义控件其实涉及到很多方面,比如View的事件体系,View的工作原理,还有缓存技术,设计模式等等 自定义滚轮很多方面都是参考了ListView的实现原理...
  • 1、ApplicationContext事件机制是观察者设计模式的具体实现,通过ApplicationEvent和ApplicationListener接口,可以实现ApplicationContext事件处理。 2、如果容器中由一个ApplicationListener Bean,当...
  • 用户自定义类继承Observable,作为被观察者自定义方法执行需要的业务逻辑,执行完成后,通过Observable中的notifyObservers()方法通知观察者 2.用户自定义类实现Observer接口,重写update方法,处理被观察者发生...
  • 【云原生】Nacos中的事件发布与订阅--观察者模式

    千次阅读 多人点赞 2022-06-22 10:20:43
    EventDispatcher在Nacos中是一个事件发布与订阅的,也就是我们经常使用的Java设计模式——观察者模式一般发布与订阅主要有三个角色事件: 表示某些类型的事件动作,例如Nacos中的 本地数据发生变更事件 事件源 : ...
  • 观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,其主要解决一个对象状态改变给其他关联对象通知的问题,保证易用和低耦合。一个典型的应用...
  • Android观察者模式

    千次阅读 2019-01-22 12:25:06
    定义 定义对象间一对多的关系,当一...Android内置了Observer和Observable,来看看观察者和被观察者的定义: 观察者 public interface Observer { void update(Observable var1, Object var2); } 被观察者 pub...
  • Java观察者模式(Observer)

    千次阅读 多人点赞 2019-02-16 23:57:11
      观察者模式java GOF23种设计模式中的一种。在最近介绍的Redis和ActiveMQ中都涉及到了观察者模式,所以我们在本文详细介绍下此模式: 观察者模式   观察者模式又称为发布/订阅(Publish/Subscribe)模式,在对象...
  • 1. 观察者模式观察者模式是一种设计模式,其中一个对象(称为主体)根据对象(观察者)维护一个对象列表,自动通知他们对状态的任何更改。意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,...
  • springboot中应用观察者模式

    千次阅读 2020-08-05 11:18:45
    近几天在学习springboot源码时发现,在springboot项目启动过程中设置了很多监听器,发现监听器的原理和设计模式中的 观察者模式 很相似。故灵感来袭总结一下 springboot中应用观察者模式 观察者模式解决的问题是:...
  • 观察者模式就是定义对象之间的一对多依赖,这样一来,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。 这样的好处就是两个或多个对象之间松耦合,它们依然可以交互,但不太清楚彼此的细节。观察...
  • 006-Android-ContentProvider习题

    千次阅读 2021-04-22 13:16:41
    1. 选择题 ...2、利用内容解析查询短信数据时uri怎么写( A)。 A、Uri uri = Uri.parse(“content://sms”); B、Uri uri = Uri.parse(“content://sms/data”); C、Uri uri = Uri.parse(“content
  • 观察者模式:又叫发布订阅模式,多个观察者可以实时监听一个主题对象,而javascript中最常用的实现方式是事件触发机制。 es6实现: 要知道需要有什么东西,和构造函数是es6中基本的对象结构 class BaseEvent { ...
  • 苦心人天不负卧薪尝胆三千越甲可吞吴,有志天不负釜底抽薪百二秦川终属楚。
  • 通过开源框架,来理解下拉刷新和加载更多的解决方案。 通过RecyclerView的Adapter来理解观察者模式。
  • 设计模式 | 观察者模式及典型应用

    千次阅读 2018-10-24 01:05:45
    JDK 提供的观察者接口中的观察者模式 Guava EventBus 中的观察者模式 JDK 委托事件模型DEM中的观察者模式 Spring ApplicationContext 事件机制中的观察者模式 观察者模式 观察者模式是设计模式中的 “超级模式”...
  • 观察者模式

    千次阅读 2015-12-26 19:58:51
    概述 有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。问题的提出在...
  • Java的23种设计模式---(21)观察者模式

    千次阅读 2019-03-14 19:25:19
    观察者模式(Observer) 场景: 如同微信上的公众号,如果你关注或订阅了“CSDN”的公众号,但这个公众号发布推文的时候,会将这个推文发送给所有订阅该公众号的微信账号。 该场景可以使用观察者模式来处理。...
  • Springboot使用@EnableAsync,@EventListener自定义异步,同步监听器(观察者模式) 1. Springboot 启动中开启异步 加个@EnableAsync就行了 @SpringBootApplication @Controller @EnableAsync public class ...
  • Java监听器模式和观察者模式区别

    千次阅读 2020-09-24 22:00:00
    二、观察者模式 Java中内置了观察者模式的,应用开发者可以直接使用,java.util.Observable和java.util.Observer就可以让你简单的使用观察者模式了,不过可惜的是,从JDK9开始,他俩就被废弃了,那么你使用时就...
  • 观察者模式(浅谈监听器工作原理)

    千次阅读 2018-05-18 09:48:42
    从某种角度来说,我们总是处于两种生活状态:观察者与被观察者。当处于观察者状态时,被观察的对象会向我们发出某种信息,使我们产生某种心理活动或行为状态的改变。当我们处于被观察者状态时,我们的行为活动又可以...
  • 使用观察者模式定义事件、监听、发布,同时还需要完成一个广播器的功能,接收到事件推送时进行分析处理符合监听事件接受者感兴趣的事件,也就是使用 isAssignableFrom 进行判断。 isAssignableFrom 和 ...
  • Java 实现观察者(Observer)模式

    千次阅读 2014-10-10 19:11:15
    * 观察目标 继承自 java.util.Observable * @author stone * */ public class UpdateObservable extends Observable { private int data; public UpdateObservable(Observer observer)
  • 我们知道Android有四大组件,ContentProvider是其中之一,顾名思义:内容提供。什么是内容提供呢?一个抽象,可以暴露应用的数据给其他应用。应用里的数据通常说的是数据库,事实上普通的文件,甚至是内存中的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,857
精华内容 15,542
关键字:

自定义内容观察者继承的类的