博客主页

代理模式(Proxy Pattern)也称为委托模式,是结构型设计模式。

定义:为其他对象提供一种代理以控制对这个对象的访问。

使用场景:当无法或者不想直接访问某个对象或者访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

UML类图

定义一个抽象主题类

public abstract class Subject {
    // 一个普通的业务方法
    protected abstract void visit();
}

实现抽象主题的真实主题类

public class RealSubject extends Subject {
    @Override
    protected void visit() {
        // 具体逻辑
        System.out.println("Real Subject");
    }
}

代理类

public class ProxySubject extends Subject {

    // 持有真实主题的引用
    private RealSubject mSubject;
    public ProxySubject( RealSubject subject) {
        mSubject = subject;
    }
    @Override
    protected void visit() {
        // 通过真实主题的引用对象调用真实主题中的逻辑方法
        mSubject.visit();
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        // 构造一个真实的对象
        RealSubject real = new RealSubject();

        // 通过真实的对象构造一个代理对象
        ProxySubject proxy = new ProxySubject(real);

        // 调用代理的相关方法
        proxy.visit();
    }
}

Subject:抽象主题类
该类的主要职责是声明真实主题与代理的共同接口方法,该类既可以是一个抽象类也可以是一个接口。

RealSubject:真实主题类
该类也称为被委托类或被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,而客户类则通过代理类间接地调用真实主题中定义的方法。

ProxySubject:代理类
该类也称为委托类或代理类,该类持有一个真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行,以此起到代理的作用。

Client:客户端
使用代理类的类型

代理模式分类

代理模式可以分为两大部分:一是静态代理,二是动态代理。

静态代理:在代码运行前代理类的class编译文件已经存在
动态代理:通过反射机制动态地生成代理者的对象,也就是说在code阶段压根就不需要知道代理谁,代理谁将会在执行阶段决定。jdk提供了一个便捷的动态代理接口InvocationHandler,实现该接口需要重写invoke

public class DynamicProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

通过invoke方法来调用具体的被代理方法,也就是真实的方法。

// 动态代理类
public class DynamicProxy implements InvocationHandler {
    private Object realObj;
    public DynamicProxy(Object obj) {
        realObj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用被代理类对象的方法
        return method.invoke(realObj, args);
    }
}

声明一个Object的引用,该引用将指向被代理类,调用被代理类的具体方法则在invoke方法中执行。

// 修改后的客户端,注意:Subject必须由抽象类修改为接口
public class Client {
    public static void main(String[] args) {
        // 构造一个真实的对象
        Subject real = new RealSubject();

        // 构造一个动态代理
        DynamicProxy handler = new DynamicProxy(real);

        // 动态构造一个代理
        Subject proxy = (Subject) Proxy.newProxyInstance(
                real.getClass().getClassLoader(),
                new Class[]{Subject.class},
                handler
        );

        proxy.visit();
    }
}

代理模式实战

下面以不同版本的 API 发送通知为例说明代理模式的应用。

public class MainActivity extends AppCompatActivity {

    private static final String CHANNEL_ID = "111111";
    private NotificationManagerCompat mNotifyManager;
    private NotificationCompat.Builder mNotifyBuilder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initNotify();

        findViewById(R.id.send_notify).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 显示通知
                mNotifyManager.notify(0, mNotifyBuilder.build());
            }
        });
    }

    private void initNotify() {
        mNotifyManager = NotificationManagerCompat.from(this);

        Intent intent = new Intent(this, NotifyActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        // 默认情况下,通知的文字内容会被截断以放在一行
        mNotifyBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
                /*小图标*/.setSmallIcon(R.mipmap.ic_launcher)
                /*标题*/.setContentText("标题")
                /*正文文本*/.setContentText("正文文本")
                /*通知优先级*/.setPriority(NotificationCompat.PRIORITY_DEFAULT)
                /*设置通知的点按操作*/.setContentIntent(pendingIntent)
                /*用户点按通知后自动移除通知*/.setAutoCancel(true);

        RemoteViews normalNotifyView = new RemoteViews(getPackageName(), R.layout.layout_remote_notify_normal);

        // 为内容区域创建自定义布局
        mNotifyBuilder.setStyle(new NotificationCompat.DecoratedCustomViewStyle());
        mNotifyBuilder.setCustomContentView(normalNotifyView); // 设置收起后通知的布局

        createNotificationChannel();
    }

    // 创建渠道并设置重要性
    private void createNotificationChannel() {
        // 要能够在 Android 8.0 及更高版本上提供通知
        // 首先必须向 createNotificationChannel() 传递 NotificationChannel 的实例
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            if (mNotifyManager == null) return;
            mNotifyManager.deleteNotificationChannel(CHANNEL_ID);

            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "ChannelName", importance);
            channel.setDescription("ChannelDescription");
            mNotifyManager.createNotificationChannel(channel);
        }
    }
}

以上代码显示通知没有问题,但是Android的不同版本的API提供的方法、样式有不同,比较碎片化。

Notification可以大致分为4类:

  1. 正常视图:状态栏高度为64dp的长条状通知视图
  2. API16中引入的以 Style 方式展示的 BigPictureStyle、BigTextStyle、InboxStyle、MessagingStyle(API24)、MediaStyle(API21)
  3. API16中引入的可以将通知试图显示为256dp高度大视图bigContentView
  4. L中加入的headsUpContentView

接下来为不同的Notification样式定义一个类,先定义一个抽象类表示通知:

public abstract class Notify {
    protected Context context;
    protected static final String CHANNEL_ID = "111111";
    protected NotificationManagerCompat mNotifyManager;
    protected NotificationCompat.Builder mNotifyBuilder;

    public Notify(Context context) {
        this.context = context;
        mNotifyManager = NotificationManagerCompat.from(context);

        Intent intent = new Intent(context, NotifyActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        // 默认情况下,通知的文字内容会被截断以放在一行
        mNotifyBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
                /*小图标*/.setSmallIcon(R.mipmap.ic_launcher)
                /*标题*/.setContentText("标题")
                /*正文文本*/.setContentText("正文文本")
                /*通知优先级*/.setPriority(NotificationCompat.PRIORITY_DEFAULT)
                /*设置通知的点按操作*/.setContentIntent(pendingIntent)
                /*用户点按通知后自动移除通知*/.setAutoCancel(true);

        createNotificationChannel();
    }

    // 创建渠道并设置重要性
    private void createNotificationChannel() {
        // 要能够在 Android 8.0 及更高版本上提供通知
        // 首先必须向 createNotificationChannel() 传递 NotificationChannel 的实例
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            if (mNotifyManager == null) return;
            mNotifyManager.deleteNotificationChannel(CHANNEL_ID);

            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "ChannelName", importance);
            channel.setDescription("ChannelDescription");
            mNotifyManager.createNotificationChannel(channel);
        }
    }

    /**
     * 发送通知
     */
    public abstract void sendNotify();

    /**
     * 取消通知
     */
    public abstract void cancelNotify();

}

Notify类首先声明了NotificationManagerCompat和NotificationCompat.Builder两个成员变量,用来处理与通知相关的逻辑,这两个变量子类需要使用,在构造初始化并为builder设置一些初始化参数。两个抽象方法sendNotify和cancelNotify在子类实现。

public class NotifyNormal extends Notify {
    public NotifyNormal(Context context) {
        super(context);
    }

    @Override
    public void sendNotify() {

        RemoteViews normalNotifyView = new RemoteViews(context.getPackageName(), R.layout.layout_remote_notify_normal);

        // 为内容区域创建自定义布局
        mNotifyBuilder.setStyle(new NotificationCompat.DecoratedCustomViewStyle());
        mNotifyBuilder.setCustomContentView(normalNotifyView); // 设置收起后通知的布局

        mNotifyManager.notify(0, mNotifyBuilder.build());
    }

    @Override
    public void cancelNotify() {
        mNotifyManager.cancel(0);
    }
}

NotifyNormal类用于发送一个自定义视图的普通Notification。

public class NotifyBig extends Notify {
    public NotifyBig(Context context) {
        super(context);
    }

    @Override
    public void sendNotify() {

        RemoteViews normalNotifyView = new RemoteViews(context.getPackageName(), R.layout.layout_remote_notify_normal);
        RemoteViews bigNotifyView = new RemoteViews(context.getPackageName(), R.layout.layout_remote_notify_big);

        // 为内容区域创建自定义布局
        mNotifyBuilder.setStyle(new NotificationCompat.DecoratedCustomViewStyle());
        mNotifyBuilder.setCustomContentView(normalNotifyView); // 设置收起后通知的布局
        mNotifyBuilder.setCustomBigContentView(bigNotifyView); // 设置展开后通知的布局

        mNotifyManager.notify(0, mNotifyBuilder.build());
    }

    @Override
    public void cancelNotify() {
        mNotifyManager.cancel(0);
    }
}

NotifyBig类仅增加一个bigContentView,为了兼容,不要删除contentView。

public class NotifyHeadsUp extends Notify {
    public NotifyHeadsUp(Context context) {
        super(context);
    }

    @Override
    public void sendNotify() {

        RemoteViews normalNotifyView = new RemoteViews(context.getPackageName(), R.layout.layout_remote_notify_normal);
        RemoteViews bigNotifyView = new RemoteViews(context.getPackageName(), R.layout.layout_remote_notify_big);

        // 为内容区域创建自定义布局
        mNotifyBuilder.setStyle(new NotificationCompat.DecoratedCustomViewStyle());
        mNotifyBuilder.setCustomContentView(normalNotifyView); // 设置收起后通知的布局
        mNotifyBuilder.setCustomBigContentView(bigNotifyView); // 设置展开后通知的布局
        mNotifyBuilder.setCustomHeadsUpContentView(normalNotifyView);

        mNotifyManager.notify(0, mNotifyBuilder.build());
    }

    @Override
    public void cancelNotify() {
        mNotifyManager.cancel(0);
    }
}

NotifyHeadsUp类增加headsUpContentView。

public class NotifyProxy extends Notify {

    private Notify notify;

    public NotifyProxy(Context context) {
        super(context);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            notify = new NotifyHeadsUp(context);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            notify = new NotifyBig(context);
        } else {
            notify = new NotifyNormal(context);
        }
    }

    @Override
    public void sendNotify() {
        notify.sendNotify();
    }

    @Override
    public void cancelNotify() {
        notify.cancelNotify();
    }
}

NotifyProxy类中定义一个Notify类型的成员变量,在构造方法中根据不同SDK版本实例化不同的Notify子类。

思考:NotifyNormal、NotifyBig、NotifyHeadsUp这三个类中重写了很多重复的代码,能不能简化呢?
肯定可以的,可以使用结构型模式中的一种设计模式装饰模式,就可以简化这里的代码。

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。