首页

医疗保健类产品设计、界面设计及交互设计灵感

博博

医疗保健类产品设计、界面设计及交互设计灵感

UI巴巴 2018-08-03 21:40:30

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

今天将从医疗保健类产品开始延展到互动和交互界面。很多医疗产品的界面有可能是一个小屏幕,也有可能是非常大的屏幕。

产品

医疗类的产品我们选择了一些可穿戴设备的概念设计。

医疗保健类产品设计、界面设计及交互设计灵感

Kingyo设计的Sange手表

医疗保健类产品设计、界面设计及交互设计灵感

Crux Product Design 和 Chris Pearce 设计的

医疗保健类产品设计、界面设计及交互设计灵感

Amazfit

医疗保健类产品设计、界面设计及交互设计灵感

Gražina Bočkutė为盲人设计的可穿戴配件

交互

我们与不同设备的交互不断变化,将语音用户界面引入医疗行业将彻底改变人们对护理的看法。精细设计的语音助理能够像人一样,更贴心。

医疗保健类产品设计、界面设计及交互设计灵感

Michal Sambora设计的Alexa助理的界面

医疗保健类产品设计、界面设计及交互设计灵感

Gleb Kuznetsov✈设计的ai智能语音助理

医疗保健类产品设计、界面设计及交互设计灵感

SELECTO设计的语音助理

界面

干净,简洁,充满未来感,避免错误的发生。

医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

Android MVP极限封装(一)

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

MVP架构在Android这一块已经盛行依旧,对于一些学习能力比较强的人来说,已经能够运用自如甚至改造优化了,对于吾等菜鸟,却是如此的陌生,今日这篇博客,算是小弟在学习和应用上的一点总结罢了,如有不足,还请各位大神不吝指教。

MVP架构是什么就不多说了,博主主要很大家分享的是,如何设计MVP架构。

先来分析一下MVP如何使用:M-V-P三层之间,P作为中间层,负责M,V之间的数据交互的中介,将数据从M层获取,处理之后提交到V层,换句话说,V需要持有P的实例,P层需要持有V的实例。原理很简单,使用泛型对数据进行封装处理: 
1.定义一个V层的空接口,主要是方便封装:

/**
 * V层接口
 */ public interface IView { }
            
  • 1
  • 2
  • 3
  • 4
  • 5

2.定义一个P层的接口:

/**
 * 抽象为接口
 * 
 */ public interface IPresenter<V extends IView> { /**
     * 绑定视图
     * 
     * @param view
     */ void attachView(V view); /**
     * 解除绑定(每个V记得使用完之后解绑,主要是用于防止内存泄漏问题)
     */ void dettachView();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.封装P基类:绑定解绑V实例

/**
 * 抽象类 统一管理View层绑定和解除绑定
 *
 * @param <V>
 */ public class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> { private WeakReference<V> weakView; protected M model;

    public V getView() { return proxyView;
    } /**
     * 用于检查View是否为空对象
     *
     * @return */ public boolean isAttachView() { return this.weakView != null && this.weakView.get() != null;
    } @Override public void attachView(V view) { this.weakView = new WeakReference<V>(view);
    } @Override public void dettachView() { if (this.weakView != null) { this.weakView.clear(); this.weakView = null;
        }
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4.M层封装:

/**
 * M层
 */ public interface IModel { } /**
 * 登录model
 * Created by admin on 2018/2/5.
 */ public interface ILoginModel extends IModel { void login();
} /**
 * 登录
 * Created by admin on 2018/2/5.
 */ public class LoginModel implements ILoginModel { @Override public void login() { // TODO: 2018/2/5 发起登录请求  }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

之后,将数据提交到activity或者fragment就行了。 
最基本的铺垫已经做好了,接下来就该封装View了:

/**
 * Created by admin on 2018/2/5.
 */ public abstract class MvpActivity<V extends IView, P extends BasePresenter<V>> extends AppCompatActivity implements IView { private P presenter;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
        ...
        presenter=getPresenter();
        presenter.attachView(this);
    } protected P getPresenter() { return presenter;
    } protected void setPresenter(P presenter) { this.presenter = presenter;
    } protected V getView() { return (V) this;
    }
    ...
    @Override protected void onDestroy() {
        presenter.dettachView();
        ... super.onDestroy();
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

收工,MVP基础框架搭建完成了。没错,就是基础框架,但是能不能用呢,让我们拭目以待吧。 
先来写一个View:

public interface ILoginView extends IView { void onLoginSuccess(); void onFailed();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后是Presneter:

/**
 * Created by admin on 2018/2/5.
 */ public class LoginPresenter extends BasePresenter<ILogin, LoginModel> { public LoginPresenter() {
        model = new LoginModel();
    }

    public void login(){
        model.login(new LoginCallBack() { @Override public void onSuccess() { if(null!=(ILogin)getView()){
                    weakView.onLoginSuccess();
                }
            } @Override public void onFailure() { if(null!=(ILogin)getView()){
                    weakView.onFailure();
                }
            }
        });
    }

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

最后来完成Activity的逻辑:

public class LoginActivity extends MvpActivity<ILoginView, LoginPresenter> implements ILoginView { ...
    @Override public LoginPresenter getPresenter() { return new LoginPresenter();
    } public void login(View view) {
        String name = etUserName.getText().toString();
        String pwd = etUserPwd.getText().toString();
        getPresenter().login(name, pwd);
    }

    @Override public void onLoginSuccess() {

    }

    @Override public void onFailed(){

    ...
}


    




    


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


布局怎么做到不单调而有层次?来看高手的9个技巧

雪涛

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

简单布局怎么做到不单调而有层次?看看设计师 Czékmány Zoltán 的9个技巧。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计  cs界面设计  ipad界面设计  包装设计  图标定制  用户体验 、交互设计、 网站建设 平面设计服务

全面易懂!写给新手的信息架构设计指南

雪涛


如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

很多产品设计师,在画原型或者设计 UI 的时候痴迷于工具的使用,拿到需求文档之后急于动手画图,忽略了信息架构设计对于产品的作用。


信息架构作为一个产品的骨架,是产品非常重要的一部分,它决定了一个产品

的布局和未来的发展方向以及用户对一个产品的最初印象和整体体验。毫不夸张的说,好的产品信息架构是产品成功的一半。

那么到底什么是产品的信息架构呢?该如何设计产品的信息架构?如何评判一个产品信息架构的好坏?我们接着往下看:

一、信息架构的概念

让我们来看一个例子:

一个饭店需要有哪些设施,如果你是饭店的老板如何合理的排布这些设施,可以让客户感觉很舒服的用餐,这个过程就是一个信息架构的过程。他可以让客户对你的饭店产生好感,从而下次用餐的时候还会想到来你这里吃饭。

在排布饭店设施的过程中我们要遵循一些规范,比如用户的习惯或者施工规范等,正是因为需要遵循这些规范,所以我们需要一个信息架构来体现这些。

比较官方的信息架构解释是这样的:信息架构设计是对信息进行结构、组织以及归类的设计,好让使用者与用户容易使用与理解的一项艺术与科学。

简单来说,信息架构设计就是让用户可以 容易地理解你的产品是怎样的。让他们在使用你产品的时候可以更顺利更自然。就像一进入饭店就会有一种感觉,门口是等餐的地方,进去就应该吃饭,如果找洗手间一定不会往门口走,而会往深处走。这就是信息架构的好处:他让用户使用同类产品时更容易上手和理解,让产品更容易被接受。

二、为何需要信息架构设计

那对于线上产品来说为什么需要合理的信息架构呢?大家来看下边3组 app 的 tab栏截图。你能仅仅从 tab栏就看出这款 app 是什么类型的 app,如何使用吗?

很明显的,第一个是一款购物类 app,第二个是一款图片社交类的 app,第三个是微信的 tab,虽然首页名称是微信,但是我相信如果把名称换成「聊天」,你还是能认出这是微信的 tab栏。

从底部标签栏就可以大致看出产品是用来干嘛的,这就是信息架构的作用。一个合理的信息架构可以让产品非常容易被用户理解,可以让用户第一眼对产品有一个简单的认知,指导自己可以用产品做什么事,指导产品提供什么服务。

再看一组反例:

这三组 tab栏就让人很困惑了,看了半天你也许根本不知道这几款 app 是做什么用的,以及如何使用。如果你让用户很困惑,他会分分钟抛弃你的 app。

所以信息架构的核心目标是为用户提供更好的体验,获得更高的留存率。

一款信息架构良好的产品必然遵循以下两个标准:

  • 让用户打开 app 的第一秒就知道这是一款什么 app,怎么用;
  • 用户想要使用某一功能时,能够第一时间找到。

我们通过这两个标准来印证下上边3个正面案例的信息架构:

相信你能很快速的识别出这款软件的用途和用法,这就给提升留存提供了基础。

那么如果信息架构像架构一个饭店一样简单,那么信息架构为何需要设计?

因为你的实际产品功能可能有这么多:

毕竟我们不是支付宝,没办法把功能像豆腐块一样堆叠起来,我们需要一些科学的设计方法。

三、如何设计信息架构

合理的信息架构设计需要考虑5个步骤:

下面我来分步讲解一下。

1. 了解用户,场景,习惯

首先你的产品是给到用户用,你当然要最大限度的了解你的用户,我们先来看下一个概念:「心智模型」。

心智模型是经由经验及学习,脑海中对某些事物发展的过程,所写下的剧本。人类在经历或学习某些事件之后,会对事物的发展及变化,归纳出一些结论,然后像是写剧本一样,把这些经验浓缩成一本一本的剧本,等到重复或类似的事情再度发生,我们便不自觉的应用这些先前写好的剧本,来预测事物的发展变化。心智模型是你对事物运行发展的预测。再说得清楚一点,你「希望」事物将如何发展,并不是心智模型,但你「认为」事物将如何发展,就是你的心智模型了。

假设你从没见过 iPad,而我刚递给你一台并告诉你可以用它来看书。在你打开 iPad 使用它之前,你头脑里会有一个在 iPad 上如何阅读的模型。你会假想书在 iPad 屏幕上是怎样的,你可以做什么事情,比如翻页或使用书签,以及这些事情的大致做法。即使你以前从没有使用过 iPad,你也有一个用 iPad 看书的「心智模型」。你头脑里的心智模型的样式和运作方式取决于很多因素。

用户往往带着以往使用 APP 的一些习惯来使用产品;线下做同一件事的习惯、生活习惯、心智模型等。要考虑哪些是可以创新的,哪些是用户习惯,要在不妨碍用户习惯的情况下作出更能让用户接受的创新。

你要考虑清楚4个问题:

用户通常用你的产品做什么?

用户用你的产品来做什么?用来看新闻还是用来聊天?一定要考虑清楚用户的核心流程。从核心流程中提取信息架构的基础形式。

用户用这类产品最关心什么?

用新闻app 时咨询的真实性实效性,购物类app 精准搜索和售后功能,就是你的用户关注点在哪里,这是一个很好的突破口。

用户有哪些思维定式?

和用户年龄身份相关的属性,产品体验符合相应用户的思维模式,心智模型,用户就会比较容易接受。

用户用什么类似的产品?

类似的产品也会带来一些用户习惯,迎合这些习惯也会让用户快速上手接受产品。

了解了你的用户场景和使用习惯之后你会知道如何做出符合用户心智的,容易被接受的产品,你不需要担心做的产品没有差异性或者没有竞争力,我们可以在核心流程之外做出创新点,让用户觉得你的产品又好用又有些不一样。

2. 了解业务

这里的业务包括与产品接触的内部及外部的人提出的需求,比如公司的运营,市场,销售,BD,公司的外部合作伙伴等。

这些人的需求我们也要收集,比如运营人员想更方便的管理注册用户,销售想更多的添加广告位,市场推广人员要求能统计不同渠道带来产品的下载量,注册数,活跃数,合作伙伴需要进行账号,内容互通等,总之只要与业务有关的人的意见,尽可能的在产品设计前多收集,即使做不了,也告诉他们原因,要不然产品上线后就等着被他们吐槽吧。

3. 调研竞品的信息架构

在做一款 app 时,我们面临了和无数竞品争抢用户的局面,这时候分析竞品就非常必要,我们需要在知己知彼的前提下,做好核心流程功能,再思考如何在差异功能上做好突破。

首先我们需要把竞品功能梳理成思维导图:

其实思维导图就是信息架构比较基础的形式了,但是光有思维导图没用,我们需要对思维导图进行分析。

我以前做过的一款人脉 app 为例,当初对比了领英、赤兔和脉脉,分析了这4款 app 的思维导图后得出的共性和差异点:

共性就是要符合用户使用习惯的地方,如果你调研的3-5个产品都这么做了,很可能这里是产生用户习惯的地方,是我们需要去遵循的,这是获得用户好感度的基础。

分析产品时你一定也会得出一些产品差异的地方,而这些差异就是你的产品竞争点,也是别人用你的 app 不用其他 app 的理由。比如人脉软件都会有社交相关的功能,但是脉脉会比较注重职场招聘、直播等互联网职场人比较关心的点,这样对应的用户群体就比较会吃你这套,会提升用户的粘性。

相信你在梳理了竞品的信息架构,总结了共性和差异点之后对产品的信息架构已经有一个比较清晰的认知了,在做自己产品信息架构的时候也会更胸有成竹。但是最后还有一件事我们可以做,就是对我们的要做的产品功能做卡片分类。

4. 卡片分类

卡片分类法是我们工作中常用到的一种方法,它可以在用户侧再一次印证和检测我们的产品信息架构。

卡片分类法就是让用户对功能卡片进行分类,组织,并给相关功能的集合重新定义名称的一种自下而上的整理方法。

说直白点就是准备一堆卡片,在这些卡片上写上你所需要包含的功能名称,然后给到用户侧,让用户进行分类,让用户进行组织,来了解用户到底觉得这些功能应该怎么合并怎么归类的一种方法。它可以帮助你站在用户角度去了解用户是怎么认定这些功能的,也可以在卡片分类法的过程中更加了解用户是怎么想的。

卡片分类法大概的步骤和注意点是这样的:

卡片分类法最终会产出这样的一个树形图:

5. 产出信息架构

其实到这一步信息架构大概的雏形已经有了,你可以用 axure 或者类似 mindnode 的软件把信息架构梳理出来。

接下来你要对信息架构进行重要性分级,这样在产品开发的前期可以帮助梳理产品研发的优先级,集中精力解决用户的最大痛点。在产出页面时也可以更好的把控页面元素的大小层级,位置关系等。

最后你需要注意层和度的平衡:层一般不超过5层,超过操作困难。度过多会让用户认知成本增加,容易找不到想找的内容。这里的度指的是同一页面展示的信息量。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计  cs界面设计  ipad界面设计  包装设计  图标定制  用户体验 、交互设计、 网站建设 平面设计服务


Retrofit源码分析

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

1、简介

retrofit是一个封装okhttp请求的网络请求库,可以通过Rxjava适配返回信息。

2、原理分析

我们通过Retrofit.Builder建造者模式创建一个Retrofit实例对象

public static final class Builder {
    /**
      *Android线程切换的类 
      */
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder() {
      this(Platform.get());
    }

    Builder(Retrofit retrofit) {
      platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;

      converterFactories.addAll(retrofit.converterFactories);
      // Remove the default BuiltInConverters instance added by build().
      converterFactories.remove(0);

      callAdapterFactories.addAll(retrofit.callAdapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      callAdapterFactories.remove(callAdapterFactories.size() - 1);

      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }

    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      callAdapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

    public List<CallAdapter.Factory> callAdapterFactories() {
      return this.callAdapterFactories;
    }

    public List<Converter.Factory> converterFactories() {
      return this.converterFactories;
    }

    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories =
          new ArrayList<>(1 + this.converterFactories.size());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }
 } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

通过Retrofit.Builder中build方法创建一个Retrofit实例对象,在创建Retrofit时会判断用户创建OkhttpClient对象,没有创建Retrofit会创建一个默认okhttpClient对象,然后设置Platform中的主线程线程池,设置线程池处理器交给主线程Looper对象。然后创建一个Retrofit对象。我们通过Retrofit.create创建一个接口代理类

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在调用Creater方法时,通过代理类创建Service实例对象,当我们通过接口实例对象调用方法时,通过invoke方法时,通过Method创建一个ServiceMethod对象,然后把ServiceMethod存储起来

 public ServiceMethod build() {
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          if (responseType == Response.class || responseType == okhttp3.Response.class) {
            throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
          }
          responseConverter = createResponseConverter();

          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }

          if (httpMethod == null) {
            throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
          }

          if (!hasBody) {
            if (isMultipart) {
              throw methodError(
                  "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
            }
            if (isFormEncoded) {
              throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
            }
          }

          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0; p < parameterCount; p++) {
            Type parameterType = parameterTypes[p];
            if (Utils.hasUnresolvableType(parameterType)) {
              throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                  parameterType);
            }

            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
            if (parameterAnnotations == null) {
              throw parameterError(p, "No Retrofit annotation found.");
            }

            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }

          if (relativeUrl == null && !gotUrl) {
            throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
          }
          if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
            throw methodError("Non-body HTTP method cannot contain @Body.");
          }
          if (isFormEncoded && !gotField) {
            throw methodError("Form-encoded method must contain at least one @Field.");
          }
          if (isMultipart && !gotPart) {
            throw methodError("Multipart method must contain at least one @Part.");
          }

          return new ServiceMethod<>(this);
        }

    private CallAdapter<T, R> createCallAdapter() {
            /**
             *获取方法返回值类型
             */
          Type returnType = method.getGenericReturnType();
          if (Utils.hasUnresolvableType(returnType)) {
            throw methodError(
                "Method return type must not include a type variable or wildcard: %s", returnType);
          }
          if (returnType == void.class) {
            throw methodError("Service methods cannot return void.");
          }
          //获取注解信息
          Annotation[] annotations = method.getAnnotations();
          try {
            //noinspection unchecked
            return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
          } catch (RuntimeException e) { // Wide exception range because factories are user code.
            throw methodError(e, "Unable to create call adapter for %s", returnType);
          }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

在创建ServiceMethod时,获取我们okhttp请求是否有返回值,没有返回值抛出异常,然后获取注解信息,然后获取retrofit中CallAdapter.Factory,然后调用get方法,我们在通过rxjavaFactoryAdapter.create创建的就是实现CallAdapter.Factory对象,然后调用CallAdapter.Factory中respenseType方法,然后通过我们传递converter对数据进行序列化,可以通过gson和fastjson进行实例化对象,然后通过parseMethodAnnomation解析请求类型

 private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          if (this.httpMethod != null) {
            throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                this.httpMethod, httpMethod);
          }
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;

          if (value.isEmpty()) {
            return;
          }

          // Get the relative URL path and existing query string, if present.
          int question = value.indexOf('?');
          if (question != -1 && question < value.length() - 1) {
            // Ensure the query string does not have any named parameters.
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError("URL query string \"%s\" must not have replace block. "
                  + "For dynamic query parameters use @Query.", queryParams);
            }
          }

          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

通过注解类型获取到请求类型时,通过调用相关方法解析获取到请求url,然后通过注解获取方法中是否有注解字段,有注解信息存储到Set集合中。然后创建一个OkhttpCall对象,通过调用serviceMethod.adapt方法做网络请求,serviceMethod.adapt调用是callAdapter中的adapt方法,如果用户没有设置callAdapter模式使用的是ExecutorCallAdapterFactory中的adapt方法

 public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            if (getRawType(returnType) != Call.class) {
                return null;
            } else {
                final Type responseType = Utils.getCallResponseType(returnType);
                return new CallAdapter<Object, Call<?>>() {
                    public Type responseType() {
                        return responseType;
                    }

                    public Call<Object> adapt(Call<Object> call) {
                        return new ExecutorCallAdapterFactory.ExecutorCallbackCall(ExecutorCallAdapterFactory.this.callbackExecutor, call);
                    }
                };
            }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在ExectorCallAdapterFactory中调用组装的Call方法中enqueue方法调用异步网络请求,成功后通过Platform中MainThreadExecutor切换到主线程。在调用callback中的enqueue,onResponse和onFairlure方法时实际是调用到OkhttpCall方法的onResponse方法,在OkHttpCall.enqueue中重新组建OkHttp.Call url和参数信息,然后封装请求,请求成功后通过parseResponse解析返回信息状态,然后把返回信息状态成ResponseBody对象,调用ServiceMethod.toResponse解析,在toResponse中实际是我们设置ConverterFactory对象解析数据,完成后调用callBack中onSuccess方法。

 @Override public void enqueue(final Callback<T> callback) {
        checkNotNull(callback, "callback == null");

        okhttp3.Call call;
        Throwable failure;

        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;

          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              call = rawCall = createRawCall();
            } catch (Throwable t) {
              throwIfFatal(t);
              failure = creationFailure = t;
            }
          }
        }

        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }

        if (canceled) {
          call.cancel();
        }

        call.enqueue(new okhttp3.Callback() {
          @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
              callFailure(e);
              return;
            }

            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }

          @Override public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
        });
      }
蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

如何用最简单的点线面,解决没灵感?

分享达人

点、线、面和构成手法,就像是大厦的基石一样,看起来毫不起眼,但力量却无比强大。

2017【百度Doodle 设计盘点】中秋节

分享达人

是百度专门为重大节日或纪念日设计制作的动态百度Logo

我们特此盘点了2017年的百度Doodle设计

PYTHON爬虫——必应图片关键词爬取

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

图片三个网站的图片搜索结果进行爬取和下载。 
首先通过爬虫过程中遇到的问题,总结如下: 
1、一次页面加载的图片数量各个网站是不定的,每翻一页就会刷新一次,对于数据量大的爬虫几乎都需要用到翻页功能,有如下两种方式: 
1)通过网站上的网址进行刷新,例如必应图片:

url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r
&mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0'
    
  • 1
  • 2

2)通过selenium来实现模拟鼠标操作来进行翻页,这一点会在Google图片爬取的时候进行讲解。 
2、每个网站应用的图片加载技术都不一样,对于静态加载的网站爬取图片非常容易,因为每张图片的url都直接显示在网页源码中,找到每张图片对应的url即可使用urlretrieve()进行下载。然而对于动态加载的网站就比较复杂,需要具体问题具体分析,例如google图片每次就会加载35张图片(只能得到35张图片的url),当滚动一次后网页并不刷新但是会再次加载一批图片,与前面加载完成的都一起显示在网页源码中。对于动态加载的网站我推荐使用selenium库来爬取。

对于爬取图片的流程基本如下(对于可以通过网址实现翻页或者无需翻页的网站): 
1. 找到你需要爬取图片的网站。(以必应为例)

这里写图片描述
2. 使用google元素检查(其他的没用过不做介绍)来查看网页源码。

这里写图片描述
3. 使用左上角的元素检查来找到对应图片的代码。

这里写图片描述
4. 通过观察找到翻页的规律(有些网站的动态加载是完全看不出来的,这种方法不推荐)

这里写图片描述
从图中可以看到标签div,class=’dgControl hover’中的data-nexturl的内容随着我们滚动页面翻页first会一直改变,q=二进制码即我们关键字的二进制表示形式。加上前缀之后由此我们才得到了我们要用的url。 
5. 我们将网页的源码放进BeautifulSoup中,代码如下:

url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r&mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0' agent = {'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.165063 Safari/537.36 AppEngine-Google."}
page1 = urllib.request.Request(url.format(InputData, i*35+1), headers=agent)
page = urllib.request.urlopen(page1)
soup = BeautifulSoup(page.read(), 'html.parser')
    
  • 1
  • 2
  • 3
  • 4
  • 5

我们得到的soup是一个class ‘bs4.BeautifulSoup’对象,可以直接对其进行操作,具体内容自行查找。 
首先选取我们需要的url所在的class,如下图: 
这里写图片描述
波浪线是我们需要的url。 
我们由下面的代码得到我们需要的url:

if not os.path.exists("./" + word):#创建文件夹 os.mkdir('./' + word) for StepOne in soup.select('.mimg'):
    link=StepOne.attrs['src']#将得到的<class 'bs4.element.Tag'>转化为字典形式并取src对应的value。 count = len(os.listdir('./' + word)) + 1 SaveImage(link,word,count)#调用函数保存得到的图片。
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后调用urlretrieve()函数下载我们得到的图片url,代码如下:

 try:
        time.sleep(0.2)
        urllib.request.urlretrieve(link,'./'+InputData+'/'+str(count)+'.jpg') except urllib.error.HTTPError as urllib_err:
        print(urllib_err) except Exception as err:
        time.sleep(1)
        print(err)
        print("产生未知错误,放弃保存") else:
        print("图+1,已有" + str(count) + "张图")
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里需要强调是像前面的打开网址和现在的下载图片都需要使用try except进行错误测试,否则出错时程序很容易崩溃,大大浪费了数据采集的时间。 
以上就是对单个页面进行数据采集的流程,紧接着改变url中{1}进行翻页操作继续采集下一页。 
数据采集结果如下: 
这里写图片描述

有问题请留言。 

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档