Android网络编程(五)OkHttp2.x用法全解析

相关文章
Android网络编程系列

前言

讲完了Volley,我们接下来看看目前比较火的网络框架OkHttp, 它处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP,此外OkHttp还处理了代理服务器问题和SSL握手失败问题。

查看更多

分享到 评论

Android网络编程(四)从源码解析Volley

相关文章
Android网络编程系列

1.Volley结构图

从上图可以看到Volley分为三个线程,分别是主线程、缓存调度线程、和网络调度线程,首先请求会加入缓存队列,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程;如果在缓存中没有找到结果,则将这条请求加入到网络队列中,然后发送HTTP请求,解析响应并写入缓存,并回调给主线程。

2.从RequestQueue入手

我们都知道使用Volley之前首先要创建RequestQueue:

RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());

这也是volley运作的入口,看看newRequestQueue:

 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (HttpStack)null);
    }

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        return newRequestQueue(context, stack, -1);
    }

连续调用了两个重载函数,最终调用的是:

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), "volley");
        String userAgent = "volley/0";

        try {
            String network = context.getPackageName();
            PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
            userAgent = network + "/" + queue.versionCode;
        } catch (NameNotFoundException var7) {
            ;
        }

        if(stack == null) {
            if(VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
        RequestQueue queue1;
        if(maxDiskCacheBytes <= -1) {
            queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
        } else {
            queue1 = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network1);
        }

        queue1.start();
        return queue1;
    }

可以看到如果android版本大于等于2.3则调用基于HttpURLConnection的HurlStack,否则就调用基于HttpClient的HttpClientStack。并创建了RequestQueue,调用了start()方法:

public void start() {
      this.stop();
      this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
      this.mCacheDispatcher.start();

      for(int i = 0; i < this.mDispatchers.length; ++i) {
          NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
          this.mDispatchers[i] = networkDispatcher;
          networkDispatcher.start();
      }

  }

CacheDispatcher是缓存调度线程,并调用了start()方法,在循环中调用了NetworkDispatcher的start()方法,NetworkDispatcher是网络调度线程,默认情况下mDispatchers.length为4,默认开启了4个网络调度线程,也就是说有5个线程在后台运行并等待请求的到来。接下来我们创建各种的Request,并调用RequestQueue的add()方法:

public <T> Request<T> add(Request<T> request) {
       request.setRequestQueue(this);
       Set var2 = this.mCurrentRequests;
       synchronized(this.mCurrentRequests) {
           this.mCurrentRequests.add(request);
       }

       request.setSequence(this.getSequenceNumber());
       request.addMarker("add-to-queue");
       //如果不能缓存,则将请求添加到网络请求队列中
       if(!request.shouldCache()) {
           this.mNetworkQueue.add(request);
           return request;
       } else {
           Map var8 = this.mWaitingRequests;
           synchronized(this.mWaitingRequests) {
               String cacheKey = request.getCacheKey();
       
      //之前是否有执行相同的请求且还没有返回结果的,如果有的话将此请求加入mWaitingRequests队列,不再重复请求
               if(this.mWaitingRequests.containsKey(cacheKey)) {
                   Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
                   if(stagedRequests == null) {
                       stagedRequests = new LinkedList();
                   }

                   ((Queue)stagedRequests).add(request);
                   this.mWaitingRequests.put(cacheKey, stagedRequests);
                   if(VolleyLog.DEBUG) {
                       VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
                   }
               } else {
  //没有的话就将请求加入缓存队列mCacheQueue,同时加入mWaitingRequests中用来做下次同样请求来时的重复判断依据
                   this.mWaitingRequests.put(cacheKey, (Object)null);
                   this.mCacheQueue.add(request);
               }

               return request;
           }
       }
   }

通过判断request.shouldCache(),来判断是否可以缓存,默认是可以缓存的,如果不能缓存,则将请求添加到网络请求队列中,如果能缓存就判断之前是否有执行相同的请求且还没有返回结果的,如果有的话将此请求加入mWaitingRequests队列,不再重复请求;没有的话就将请求加入缓存队列mCacheQueue,同时加入mWaitingRequests中用来做下次同样请求来时的重复判断依据。
从上面可以看出RequestQueue的add()方法并没有做什么请求网络或者对缓存进行操作。当将请求添加到网络请求队列或者缓存队列时,这时在后台的网络调度线程和缓存调度线程轮询各自的请求队列发现有请求任务则开始执行,我们先看看缓存调度线程。

3.CacheDispatcher缓存调度线程

CacheDispatcher的run()方法:

public void run() {
    if(DEBUG) {
        VolleyLog.v("start new dispatcher", new Object[0]);
    }
    //线程优先级设置为最高级别
    Process.setThreadPriority(10);
    this.mCache.initialize();

    while(true) {
        while(true) {
            while(true) {
                while(true) {
                    try {
                    //获取缓存队列中的一个请求
                        final Request e = (Request)this.mCacheQueue.take();
                        e.addMarker("cache-queue-take");
                        //如果请求取消了则将请求停止掉
                        if(e.isCanceled()) {
                            e.finish("cache-discard-canceled");
                        } else {
                        //查看是否有缓存的响应
                            Entry entry = this.mCache.get(e.getCacheKey());
                            //如果缓存响应为空,则将请求加入网络请求队列
                            if(entry == null) {
                                e.addMarker("cache-miss");
                                this.mNetworkQueue.put(e);
                            //判断缓存响应是否过期    
                            } else if(!entry.isExpired()) {
                                e.addMarker("cache-hit");
                                //对数据进行解析并回调给主线程
                                Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
                                e.addMarker("cache-hit-parsed");
                                if(!entry.refreshNeeded()) {
                                    this.mDelivery.postResponse(e, response);
                                } else {
                                    e.addMarker("cache-hit-refresh-needed");
                                    e.setCacheEntry(entry);
                                    response.intermediate = true;
                                    this.mDelivery.postResponse(e, response, new Runnable() {
                                        public void run() {
                                            try {
                                                CacheDispatcher.this.mNetworkQueue.put(e);
                                            } catch (InterruptedException var2) {
                                                ;
                                            }

                                        }
                                    });
                                }
                            } else {
                                e.addMarker("cache-hit-expired");
                                e.setCacheEntry(entry);
                                this.mNetworkQueue.put(e);
                            }
                        }
                    } catch (InterruptedException var4) {
                        if(this.mQuit) {
                            return;
                        }
                    }
                }
            }
        }
    }
}

static {
    DEBUG = VolleyLog.DEBUG;
}

看到四个while循环有些晕吧,让我们挑重点的说,首先从缓存队列取出请求,判断是否请求是否被取消了,如果没有则判断该请求是否有缓存的响应,如果有并且没有过期则对缓存响应进行解析并回调给主线程。接下来看看网络调度线程。

4.NetworkDispatcher网络调度线程

NetworkDispatcher的run()方法:

public void run() {
       Process.setThreadPriority(10);

       while(true) {
           long startTimeMs;
           Request request;
           while(true) {
               startTimeMs = SystemClock.elapsedRealtime();

               try {
               //从队列中取出请求
                   request = (Request)this.mQueue.take();
                   break;
               } catch (InterruptedException var6) {
                   if(this.mQuit) {
                       return;
                   }
               }
           }

           try {
               request.addMarker("network-queue-take");
               if(request.isCanceled()) {
                   request.finish("network-discard-cancelled");
               } else {
                   this.addTrafficStatsTag(request);
                   //请求网络
                   NetworkResponse e = this.mNetwork.performRequest(request);
                   request.addMarker("network-http-complete");
                   if(e.notModified && request.hasHadResponseDelivered()) {
                       request.finish("not-modified");
                   } else {
                       Response volleyError1 = request.parseNetworkResponse(e);
                       request.addMarker("network-parse-complete");
                       if(request.shouldCache() && volleyError1.cacheEntry != null) {                         
                           //将响应结果存入缓存
                           this.mCache.put(request.getCacheKey(), volleyError1.cacheEntry);
                           request.addMarker("network-cache-written");
                       }

                       request.markDelivered();
                       this.mDelivery.postResponse(request, volleyError1);
                   }
               }
           } catch (VolleyError var7) {
               var7.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
               this.parseAndDeliverNetworkError(request, var7);
           } catch (Exception var8) {
               VolleyLog.e(var8, "Unhandled exception %s", new Object[]{var8.toString()});
               VolleyError volleyError = new VolleyError(var8);
               volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
               this.mDelivery.postError(request, volleyError);
           }
       }
   }

网络调度线程也是从队列中取出请求并且判断是否被取消了,如果没取消就去请求网络得到响应并回调给主线程。请求网络时调用this.mNetwork.performRequest(request),这个mNetwork是一个接口,实现它的类是BasicNetwork,我们来看看BasicNetwork的performRequest()方法:

  public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();

        while(true) {
            HttpResponse httpResponse = null;
            Object responseContents = null;
            Map responseHeaders = Collections.emptyMap();

            try {
                HashMap e = new HashMap();
                this.addCacheHeaders(e, request.getCacheEntry());
                httpResponse = this.mHttpStack.performRequest(request, e);
                StatusLine statusCode1 = httpResponse.getStatusLine();
                int networkResponse1 = statusCode1.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                if(networkResponse1 == 304) {
                    Entry requestLifetime2 = request.getCacheEntry();
                    if(requestLifetime2 == null) {
                        return new NetworkResponse(304, (byte[])null, responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                    }

                    requestLifetime2.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(304, requestLifetime2.data, requestLifetime2.responseHeaders, true, SystemClock.elapsedRealtime() - requestStart);
                }


...省略

从上面可以看到在12行调用的是HttpStack的performRequest()方法请求网络,接下来根据不同的响应状态码来返回不同的NetworkResponse。另外HttpStack也是一个接口,实现它的两个类我们在前面已经提到了就是HurlStack和HttpClientStack。让我们再回到NetworkDispatcher,请求网络后,会将响应结果存在缓存中,如果响应结果成功则调用this.mDelivery.postResponse(request, volleyError1)来回调给主线程。来看看Delivery的postResponse()方法:

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
       request.markDelivered();
       request.addMarker("post-response");
       this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
   }

来看看ResponseDeliveryRunnable里面做了什么:

private class ResponseDeliveryRunnable implements Runnable {
       private final Request mRequest;
       private final Response mResponse;
       private final Runnable mRunnable;

       public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
           this.mRequest = request;
           this.mResponse = response;
           this.mRunnable = runnable;
       }

       public void run() {
           if(this.mRequest.isCanceled()) {
               this.mRequest.finish("canceled-at-delivery");
           } else {
               if(this.mResponse.isSuccess()) {
                   this.mRequest.deliverResponse(this.mResponse.result);
               } else {
                   this.mRequest.deliverError(this.mResponse.error);
               }

               if(this.mResponse.intermediate) {
                   this.mRequest.addMarker("intermediate-response");
               } else {
                   this.mRequest.finish("done");
               }

               if(this.mRunnable != null) {
                   this.mRunnable.run();
               }

           }
       }
   }

第17行调用了this.mRequest.deliverResponse(this.mResponse.result),这个就是实现Request抽象类必须要实现的方法,我们来看看StringRequest的源码:

public class StringRequest extends Request<String> {
    private final Listener<String> mListener;

    public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
    }

    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(0, url, listener, errorListener);
    }

    protected void deliverResponse(String response) {
        this.mListener.onResponse(response);
    }

 ...省略
}

在deliverResponse方法中调用了this.mListener.onResponse(response),最终将response回调给了Response.Listener的onResponse()方法。我们用StringRequest请求网络的写法是这样的:

RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
        StringRequest mStringRequest = new StringRequest(Request.Method.GET, "http://www.baidu.com",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.i("wangshu", response);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("wangshu", error.getMessage(), error);
            }
        });
        //将请求添加在请求队列中
        mQueue.add(mStringRequest);

看到第5行整个Volley的大致流程都通了吧,好了关于Volley的源码就讲到这里。

分享到 评论

Android View体系(九)自定义View

前言

学习了以上的文章后,接下来我们来讲讲自定义View,自定义View一直被认为是高手掌握的技能,因为情况太多,想实现的效果又变化多端,但它也要遵循一定的规则,我们要讲的就是这个规则,至于那些变化多端的酷炫的效果就由各位来慢慢发挥了。但是需要注意的是凡事都要有个度,自定义View毕竟不是规范的控件,如果不设计好不考虑性能反而会适得其反,另外适配起来可能也会产生问题,笔者的建议是如果能用系统控件的还是尽量用系统控件。

查看更多

分享到 评论

Android网络编程(三)Volley用法全解析

100505176ee6982846_副本.jpg
相关文章
Android网络编程系列

前言

Volley想必很多人都用过,为了建立网络编程的知识体系,Volley是必须要讲的知识点,所以我这里有必要再次介绍一下Volley的使用。

查看更多

分享到 评论

Android网络编程(二)HttpClient与HttpURLConnection

java-httpurlconnection-java-http-request_副本.jpg
相关文章
Android网络编程系列

前言

上一篇我们了解了HTTP协议原理,这一篇我们来讲讲Apache的HttpClient和Java的HttpURLConnection,这两种都是我们平常请求网络会用到的。无论我们是自己封装的网络请求类还是第三方的网络请求框架都离不开这两个类库。

查看更多

分享到 评论

Android网络编程(一)HTTP协议原理

http_副本.jpg
相关文章
Android网络编程系列

前言

这篇文章是这个系列的开篇,作为移动开发者,开发的应用不免会对网络进行访问,虽然现在已经有很多的开源库帮助我们可以轻而易举的访问网络,但是我们仍要去了解网络访问的原理,这也是一个优秀开发人员所必备的知识点。这篇文章我们就先来了解一下HTTP协议原理。

查看更多

分享到 评论

Android View体系(八)从源码解析View的layout和draw流程

前言

上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了。

查看更多

分享到 评论

Android View体系(七)从源码解析View的measure流程

前言

在上一篇我们了解了Activity的构成后,开始了解一下View的工作流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw则用来绘制View。这一讲我们来看看measure流程,measure流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量还要遍历去调用子元素的measure()方法。

查看更多

分享到 评论

设计模式(六)代理模式

相关文章
设计模式系列

1.代理模式简介

代理模式介绍

代理模式也叫委托模式,是结构型设计模式的一种。在现实生活中我们用到类似代理模式的场景有很多,比如代购、代理上网、打官司等。

定义

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

代理模式结构图

  • Subject:抽象主题类,声明真实主题与代理的共同接口方法。
  • RealSubject:真实主题类,定义了代理所表示的真实对象,客户端通过代理类间接的调用真实主题类的方法。
  • ProxySubject:代理类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
  • Client:客户端类。

2.代理模式的简单实现

假设我要买一个BV的包(博主我很喜欢一直买不起),国内的太贵了,我找了个代购来帮我海外购买,这样能省好多钱,哈哈。

抽象主题类(Subject)

抽象主题类具有真实主题类和代理的共同接口方法,我想要代购,那共同的方法就是购买:

public interface IShop {
    //购买
    void buy();
}

真实主题类(RealSubject)

这个购买者LiuWangShu也就是我,实现了IShop接口提供的 buy()方法:

public class LiuWangShu implements IShop {
    @Override
    public void buy() {
        System.out.println("购买");
    }
}

代理类(ProxySubject)

我找的代理类同样也要实现IShop接口,并且要持有被代理者,在buy()方法中调用了被代理者的buy()方法:

public class Purchasing implements IShop {
    private IShop mShop;
    public Purchasing(IShop shop){
        mShop=shop;
    }

    @Override
    public void buy() {
        mShop.buy();
    }
}

客户端类(Client)

public class Client {
    public static void main(String[] args){
        //创建LiuWangShu
        IShop liuwangshu=new LiuWangShu();
        //创建代购者并将LiuWangShu作为构造函数传
        IShop purchasing=new Purchasing(liuwangshu);
        purchasing.buy();
    }
}

看完客户端类的代码,其实也是很好理解,就是代理类包含了真实主题类(被代理者),最终调用的都是真实主题类(被代理者)实现的方法,在上面的例子就是LiuWangShu类的buy()方法,所以运行的结果就是“购买”。

3.动态代理的简单实现

从编码的角度来说,代理模式分为静态代理和动态代理,上面的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件,而动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段不需要知道代理谁,代理谁我们将会在代码运行时决定。Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法。下面我们在上面静态代理的例子上做修改:

创建动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicPurchasing implements InvocationHandler{
    private Object obj;
    public DynamicPurchasing(Object obj){
        this.obj=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=method.invoke(obj, args);
        return result;
    }
}

在动态代理类中我们声明一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke()方法中执行。接下来我们修改客户端类代码:

import java.lang.reflect.Proxy;
public class Client {
    public static void main(String[] args){
        //创建LiuWangShu
        IShop liuwangshu=new LiuWangShu();
        //创建动态代理
        DynamicPurchasing  mDynamicPurchasing=new DynamicPurchasing(liuwangshu);
        //创建LiuWangShu的ClassLoader
        ClassLoader loader=liuwangshu.getClass().getClassLoader();
        //动态创建代理类
        IShop purchasing= (IShop) Proxy.newProxyInstance(loader,new Class[]{IShop.class},mDynamicPurchasing);
        purchasing.buy();
    }
}

4.代理模式的应用

代理模式类型

代理模式的类型主要有以下几点:

  1. 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。
  2. 虚拟代理:使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建。
  3. 安全代理:用来控制真实对象访问时的权限。
  4. 智能指引:当调用真实的对象时,代理处理另外一些事,比如计算真实对象的引用计数,当该对象没有引用时,可以自动释放它;或者访问一个实际对象时,检查是否已经能够锁定它,以确保其他对象不能改变它。

代理模式使用场景

无法或者不想直接访问某个对象时可以通过一个代理对象来间接的访问。

分享到 评论

Android View体系(六)从源码解析Activity的构成

前言

本来这篇是要讲View的工作流程的,View的工作流程主要指的measure、layout、draw这三大流程,在讲到这三大流程之前我们有必要要先了解下Activity的构成,所以就有了这篇文章。

查看更多

分享到 评论

Android View体系(五)从源码解析View的事件分发机制

前言

三年前写过事件分发机制的文章但是写的不是很好,所以重新再写一篇,关于事件分发机制的文章已经有很多,但是希望我这篇是最简洁、最易懂的一篇。

查看更多

分享到 评论

Swift快速入门(五)集合

相关文章
Swift快速入门系列

前言

Swift提供了数组和字典两种集合类型来存储数据,Swift的数组用来存储顺序相同类型相同的类型,字典则采用kay-value的形式存储数据。

查看更多

分享到 评论

Android View体系(四)从源码解析Scroller

前言

Android View体系(二)实现View滑动的六种方法这篇文章中我们讲到了用Scroller来实现View的滑动,所以这篇文章我们就不介绍Scroller是如何使用的了,本篇就从源码来分析下Scroller为何能够实现View的滑动。

查看更多

分享到 评论

设计模式(五)观察者模式

相关文章
设计模式系列

1.观察者模式模式简介

定义

观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

观察者模式结构图

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

2.观察者模式简单实现

观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。好了我们来看看用代码如何实现:

抽象观察者(Observer)

里面定义了一个更新的方法:

public interface Observer {
    public void update(String message);
}

具体观察者(ConcrereObserver)

微信用户是观察者,里面实现了更新的方法:

public class WeixinUser implements Observer {
    // 微信用户名
    private String name;
    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }

   
}

抽象被观察者(Subject)

抽象主题,提供了attach、detach、notify三个方法:

public interface Subject {
    /**
     * 增加订阅者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 删除订阅者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}

具体被观察者(ConcreteSubject)

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:

public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //创建微信用户
        WeixinUser user1=new WeixinUser("杨影枫");
        WeixinUser user2=new WeixinUser("月眉儿");
        WeixinUser user3=new WeixinUser("紫轩");
        //订阅公众号
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公众号更新发出消息给订阅的微信用户
        mSubscriptionSubject.notify("刘望舒的专栏更新了");
    }
}

结果

杨影枫-刘望舒的专栏更新了
月眉儿-刘望舒的专栏更新了
紫轩-刘望舒的专栏更新了

3.使用观察者模式的场景和优缺点

使用场景

  • 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优点

解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。

缺点

在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

4.Android中的观察者模式

android源码中也有很多使用了观察者模式,比如OnClickListener、ContentObserver、android.database.Observable等;还有组件通讯库RxJava、RxAndroid、EventBus;在这里将拿我们最常用的Adapter的notifyDataSetChanged()方法来举例:
当我们用ListView的时候,数据发生变化的时候我们都会调用Adapter的notifyDataSetChanged()方法,这个方法定义在BaseAdaper中,我们来看看BaseAdaper的部分源码:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {

    //数据集观察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public boolean hasStableIds() {
        return false;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    /**
     * 当数据集变化时,通知所有观察者
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

很明显BaseAdapter用的是观察者模式,BaseAdapter是具体被观察者,接下来看看mDataSetObservable.notifyChanged():

public class DataSetObservable extends Observable<DataSetObserver> {
    public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

我们看到了mObservers,这就是观察者的集合,这些观察者是在ListView通过setAdaper()设置Adaper时产生的:

@Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
        ...
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();
            //创建数据观察者
            mDataSetObserver = new AdapterDataSetObserver();
            //注册观察者
            mAdapter.registerDataSetObserver(mDataSetObserver);

            ...
        }

接下来看看观察者AdapterDataSetObserver中处理了什么:

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }
    }

从上面的代码看不出什么,再看看AdapterDataSetObserver的父类AdapterView的AdapterDataSetObserver:

class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局
            requestLayout();
        }

        ...

        public void clearSavedState() {
            mInstanceState = null;
        }
    }

我们看到在onChanged()方法中调用了requestLayout()方法来重新进行布局。好了,看到这里我们都明白了,当ListView的数据发生变化时,我们调用Adapter的notifyDataSetChanged()方法,这个方法又会调用观察者们(AdapterDataSetObserver)的onChanged()方法,onChanged()方法又会调用requestLayout()方法来重新进行布局。

分享到 评论

Android View体系(三)属性动画

前言

上一篇文章讲了View滑动的六种方法,其中一种是使用动画,这篇文章我们来讲一讲动画的其中一种:属性动画。

查看更多

分享到 评论

Android View体系(二)实现View滑动的六种方法

1.View的滑动简介

View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到View的滑动的处理。其实不管是那种滑动的方式基本思想都是类似的:当触摸事件传到View时,系统记下触摸点的坐标,手指移动时系统记下移动后的触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。
实现View滑动有很多种方法,这篇文章主要讲解六种滑动的方法,分别是:layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo与scollBy和Scroller;在下一篇文章我们会详细介绍属性动画。

2.实现View滑动的六种方法

layout()

view进行绘制的时候会调用onLayout()方法来设置显示的位置,因此我们同样也可以通过修改View的left、top、right、bottom这四种属性来控制View的坐标。首先我们要自定义一个View,在onTouchEvent()方法中获取触摸点的坐标:

   public boolean onTouchEvent(MotionEvent event) {
        //获取到手指处的横坐标和纵坐标
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;

...

接下来我们在ACTION_MOVE事件中计算偏移量,再调用layout()方法重新放置这个自定义View的位置就好了:

case MotionEvent.ACTION_MOVE:
    //计算移动的距离
    int offsetX = x - lastX;
    int offsetY = y - lastY;
    //调用layout方法来重新放置它的位置
    layout(getLeft()+offsetX, getTop()+offsetY,
            getRight()+offsetX , getBottom()+offsetY);
    break;

当我们每次移动时都会调用layout()方法来对自己重新布局,从而达到移动View的效果。

自定义View的全部代码(CustomView.java):

package com.example.liuwangshu.moonviewslide;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class CustomView extends View {
    private int lastX;
    private int lastY;

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context) {
        super(context);
    }

    public boolean onTouchEvent(MotionEvent event) {
        //获取到手指处的横坐标和纵坐标
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;

            case MotionEvent.ACTION_MOVE:
                //计算移动的距离
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //调用layout方法来重新放置它的位置
                layout(getLeft()+offsetX, getTop()+offsetY,
                        getRight()+offsetX , getBottom()+offsetY);
                break;
        }

        return true;
    }
}

布局中引用自定义View:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.liuwangshu.moonviewslide.CustomView
        android:id="@+id/customview"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_margin="50dp"
        android:background="@android:color/holo_red_light" />
</LinearLayout>

offsetLeftAndRight()与offsetTopAndBottom()

这两种方法和layout()方法效果方法差不多,使用也差不多,我们将ACTION_MOVE中的代码替换成如下代码:

case MotionEvent.ACTION_MOVE:
    //计算移动的距离
    int offsetX = x - lastX;
    int offsetY = y - lastY;
    //对left和right进行偏移
    offsetLeftAndRight(offsetX);
    //对top和bottom进行偏移
    offsetTopAndBottom(offsetY);
    break;

LayoutParams(改变布局参数)

LayoutParams主要保存了一个View的布局参数,因此我们可以通过LayoutParams来改变View的布局的参数从而达到了改变View的位置的效果。同样的我们将ACTION_MOVE中的代码替换成如下代码:

LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) getLayoutParams();
              layoutParams.leftMargin = getLeft() + offsetX;
              layoutParams.topMargin = getTop() + offsetY;
              setLayoutParams(layoutParams);

因为父控件是LinearLayout,所以我们用了LinearLayout.LayoutParams,如果父控件是RelativeLayout则要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams外,我们还可以用ViewGroup.MarginLayoutParams来实现:

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

动画

可以采用View动画来移动,在res目录新建anim文件夹并创建translate.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="300" android:duration="1000"/>
</set>

在Java代码中引用:

mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

当然使用属性动画移动那就更简单了,我们让CustomView在1000毫秒内沿着X轴像右平移300像素:

ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(1000).start();

scollTo与scollBy

scollTo(x,y)表示移动到一个具体的坐标点,而scollBy(dx,dy)则表示移动的增量为dx、dy。其中scollBy最终也是要调用scollTo的。scollTo、scollBy移动的是View的内容,如果在ViewGroup中使用则是移动他所有的子View。我们将ACTION_MOVE中的代码替换成如下代码:

((View)getParent()).scrollBy(-offsetX,-offsetY);

这里要实现CustomView随着我们手指移动的效果的话,我们就需要将偏移量设置为负值。

Scroller

我们用scollTo/scollBy方法来进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用Scroller来实现有过度效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔完成的。Scroller本身是不能实现View的滑动的,它需要配合View的computeScroll()方法才能弹性滑动的效果。
在这里我们实现CustomView平滑的向右移动。

  • 首先我们要初始化Scroller:
public CustomView(Context context, AttributeSet attrs) {
      super(context, attrs);
      mScroller = new Scroller(context);
  }
  • 接下来重写computeScroll()方法,系统会在绘制View的时候在draw()方法中调用该方法,这个方法中我们调用父类的scrollTo()方法并通过Scroller来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断的进行重绘,重绘就会调用computeScroll()方法,这样我们就通过不断的移动一个小的距离并连贯起来就实现了平滑移动的效果:
@Override
public void computeScroll() {
    super.computeScroll();
    if(mScroller.computeScrollOffset()){
        ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
         //通过不断的重绘不断的调用computeScroll方法
         invalidate();
    }  
}
  • 调用Scroller.startScroll()方法。我们在CustomView中写一个smoothScrollTo()方法,调用Scroller.startScroll()方法,在2000毫秒内沿X轴平移delta像素:
public void smoothScrollTo(int destX,int destY){
      int scrollX=getScrollX();
      int delta=destX-scrollX;
      //1000秒内滑向destX
      mScroller.startScroll(scrollX,0,delta,0,2000);
      invalidate();
  }
  • 最后我们在ViewSlideActivity.java中调用CustomView的smoothScrollTo()方法:
//使用Scroll来进行平滑移动
mCustomView.smoothScrollTo(-400,0);

这里我们是设定CustomView沿着X轴向右平移400像素。

github源码下载

分享到 评论