Android 编译出现 “Cannot fit requested classes in a single dex file (# methods: xxx > 65536)” 问题解析

 android  Android 编译出现 “Cannot fit requested classes in a single dex file (# methods: xxx > 65536)” 问题解析已关闭评论
7月 082020
 

测试使用google play service的location功能时编译出现:“Cannot fit requested classes in a single dex file (# methods: 71234 > 65536)”,  在官网以下地址有关于这个问题详细的说明: https://developer.android.com/studio/build/multidex#mdex-gradle

国内因为某些原因可能打不开这个url, 复制内容如下:

为方法数超过 64K 的应用启用 MultiDex

当您的应用及其引用的库包含的方法数超过 65536 时,您会遇到一个构建错误,指明您的应用已达到 Android 构建架构规定的引用限制:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

较低版本的构建系统会报告一个不同的错误,但指示的是同一问题:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

这两种错误情况会显示一个共同的数字:65536。此数字是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数。本页介绍如何通过启用称为 MultiDex 的应用配置(该配置使您的应用能够构建和读取多个 DEX 文件)越过这一限制。

关于 64K 引用限制

Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536,其中包括 Android 框架方法、库方法以及您自己的代码中的方法。在计算机科学领域内,术语千(简称 K)表示 1024(即 2^10)。由于 65536 等于 64 X 1024,因此这一限制称为“64K 引用限制”。

Android 5.0 之前版本的 MultiDex 支持

Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时执行应用代码。默认情况下,Dalvik 将应用限制为每个 APK 只能使用一个 classes.dex 字节码文件。为了绕过这一限制,您可以在项目中添加 MultiDex 支持库:

dependencies {
    def multidex_version = "2.0.1"
    implementation 'androidx.multidex:multidex:$multidex_version'
}
   

如需查看此库的当前版本,请参阅版本页面中有关 MultiDex 的信息。

如果您不使用 AndroidX,请改为添加以下支持库依赖项:

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

此库会成为应用的主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。如需了解详情,请参阅下面有关如何针对 MultiDex 配置应用的部分。

Android 5.0 及更高版本的 MultiDex 支持

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,它本身支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成单个 .oat 文件,以供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高的值,则默认情况下会启用 MultiDex,并且您不需要 MultiDex 支持库。

如需详细了解 Android 5.0 运行时,请阅读 ART 和 Dalvik

注意:使用 Android Studio 运行应用时,会针对您部署到的目标设备优化 build。这包括在目标设备搭载 Android 5.0 及更高版本时启用 MultiDex。由于此优化仅在使用 Android Studio 部署应用时应用,因此您可能仍需要为 MultiDex 配置发布 build,以规避 64K 限制。

规避 64K 限制

在将您的应用配置为支持使用 64K 或更多方法引用之前,您应该采取措施以减少应用代码调用的引用总数,包括由您的应用代码或包含的库定义的方法。以下策略可帮助您避免达到 DEX 引用限制:

  • 检查应用的直接依赖项和传递依赖项 – 确保您在应用中使用任何庞大依赖库所带来的好处多于为应用添加大量代码所带来的弊端。一种常见的反面模式是,仅仅为了使用几个实用方法就在应用中加入非常庞大的库。减少应用代码依赖项往往能够帮助您规避 DEX 引用限制。
  • 通过 R8 移除未使用的代码 – 启用代码缩减以针对发布 build 运行 R8。启用缩减可确保您交付的 APK 不含有未使用的代码。

使用这些技巧使您不必在应用中启用 MultiDex,同时还会减小 APK 的总体大小。

针对 MultiDex 配置应用

如果您的 minSdkVersion 设为 21 或更高的值,则默认情况下会启用 MultiDex,并且您不需要 MultiDex 支持库。

不过,如果您的 minSdkVersion 设为 20 或更低的值,则必须使用 MultiDex 支持库并对应用项目进行以下修改:

  1. 修改模块级 build.gradle 文件以启用 MultiDex,并将 MultiDex 库添加为依赖项,如下所示:
    android {
        defaultConfig {
            ...
            minSdkVersion 15
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      implementation 'com.android.support:multidex:1.0.3'
    }
    
  2. 根据您是否替换 Application 类,执行以下某项操作:(这条我自己测试MultiDexApplication类找不到,可以不需要)
    • 如果您不替换 Application 类,请修改清单文件以设置 <application> 标记中的 android:name,如下所示:
      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.myapp">
          <application
                  android:name="android.support.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • 如果您替换 Application 类,请对其进行更改以扩展 MultiDexApplication(如果可能),如下所示:
      public class MyApplication extends MultiDexApplication { ... }
      
    • 或者,如果您替换 Application 类,但无法更改基类,则可以改为替换 attachBaseContext() 方法并调用 MultiDex.install(this) 以启用 MultiDex:
      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(base);
           MultiDex.install(this);
        }
      }
      

      注意:在 MultiDex.install() 完成之前,不要通过反射或 JNI 执行 MultiDex.install() 或其他任何代码。MultiDex 跟踪功能不会追踪这些调用,从而导致出现 ClassNotFoundException,或因 DEX 文件之间的类分区错误而导致验证错误。

现在,当您构建应用时,Android 构建工具会根据需要构造主要 DEX 文件 (classes.dex) 和辅助 DEX 文件(classes2.dex 和 classes3.dex 等)。然后,构建系统会将所有 DEX 文件打包到 APK 中。

在运行时,MultiDex API 使用特殊的类加载器搜索适用于您的方法的所有 DEX 文件(而不是只在主 classes.dex 文件中搜索)。

MultiDex 支持库的局限性

MultiDex 支持库具有一些已知的局限性,将其纳入您的应用构建配置时,您应注意这些局限性并进行针对性的测试:

  • 启动期间在设备的数据分区上安装 DEX 文件的过程相当复杂,如果辅助 DEX 文件较大,可能会导致应用无响应 (ANR) 错误。为避免此问题,请启用代码缩减,以尽量减小 DEX 文件的大小,并移除未使用的代码部分。
  • 当搭载的版本低于 Android 5.0(API 级别 21)时,使用 MultiDex 不足以避开 linearalloc 限制(问题 78035)。此上限在 Android 4.0(API 级别 14)中有所提高,但这并未完全解决该问题。在低于 Android 4.0 的版本中,您可能会在达到 DEX 索引限制之前达到 linearalloc 限制。因此,如果您的目标 API 级别低于 14,请在这些版本的平台上进行全面测试,因为您的应用可能会在启动时或加载特定类组时出现问题。代码缩减可以减少甚至有可能消除这些问题。

入门OkHttp使用

 okhttp  入门OkHttp使用已关闭评论
10月 222018
 

OkHttp入门资料,推荐!来自:https://blog.csdn.net/mynameishuangshuai/article/details/51303446

       OkHttp官网地址:http://square.github.io/okhttp/ 
       OkHttp GitHub地址:https://github.com/square/okhttp 
官网的自我介绍: 
       HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth. 
OkHttp is an HTTP client that’s efficient by default:

•   HTTP/2 support allows all requests to the same host to share a socket.
•   Connection pooling reduces request latency (if HTTP/2 isn’t available).
•   Transparent GZIP shrinks download sizes.
•   Response caching avoids the network completely for repeat requests.

       OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails. 
       Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks. 
       OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7. 
       概括起来说OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。

配置方法
(一)导入Jar包 
点击下面链接下载最新v3.2.0 JAR 
http://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.2.0/okhttp-3.2.0.jar 
(二)通过构建方式导入 
MAVEN

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>3.2.0</version>
</dependency>

GRADLE

compile ‘com.squareup.okhttp3:okhttp:3.2.0’
基本要求
Requests(请求)
       每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。

Responses(响应)
       响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。

基本使用
       在日常开发中最常用到的网络请求就是GET和POST两种请求方式。

HTTP GET
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
Request是OkHttp中访问的请求,Builder是辅助类,Response即OkHttp中的响应。 
Response类:

public boolean isSuccessful()
Returns true if the code is in [200..300), which means the request was successfully received, understood, and accepted.
response.body()返回ResponseBody类
可以方便的获取string

public final String string() throws IOException
Returns the response as a string decoded with the charset of the Content-Type header. If that header is either absent or lacks a charset, this will attempt to decode the response body as UTF-8.
Throws:
IOException
当然也能获取到流的形式: 
public final InputStream byteStream()

HTTP POST
POST提交Json数据

public static final MediaType JSON = MediaType.parse(“application/json; charset=utf-8”);
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
    Response response = client.newCall(request).execute();
    f (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
使用Request的post方法来提交请求体RequestBody 
POST提交键值对 
OkHttp也可以通过POST方式把键值对数据传送到服务器

OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
    RequestBody formBody = new FormEncodingBuilder()
    .add(“platform”, “android”)
    .add(“name”, “bug”)
    .add(“subject”, “XXXXXXXXXXXXXXX”)
    .build();

    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();

    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else {
        throw new IOException(“Unexpected code ” + response);
    }
}
案例
布局文件:

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

    <LinearLayout
        android:layout_width=”match_parent”
        android:layout_height=”wrap_content”
        android:gravity=”center_horizontal”
        android:orientation=”horizontal”>

        <Button
            android:id=”@+id/bt_get”
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”
            android:text=”乌云Get请求”/>

        <Button
            android:id=”@+id/bt_post”
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”
            android:text=”乌云Post请求”/>

    </LinearLayout>

    <TextView
        android:id=”@+id/tv_show”
        android:layout_width=”match_parent”
        android:layout_height=”wrap_content”/>
</LinearLayout>
 
Java代码: 
由于android本身是不允许在UI线程做网络请求操作的,所以我们自己写个线程完成网络操作

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button bt_get;
    private Button bt_post;

    final OkHttpClient client = new OkHttpClient();

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

        bt_get=(Button)findViewById(R.id.bt_get);
        bt_post=(Button)findViewById(R.id.bt_post);

        bt_get.setOnClickListener(this);
        bt_post.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.bt_get:
                getRequest();
                break;

            case R.id.bt_post:
                postRequest();
                break;

        }
    }

    private void getRequest() {

        final Request request=new Request.Builder()
                .get()
                .tag(this)
                .url(“http://www.wooyun.org”)
                .build();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        Log.i(“WY”,”打印GET响应的数据:” + response.body().string());
                    } else {
                        throw new IOException(“Unexpected code ” + response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

    private void postRequest() {

        RequestBody formBody = new FormEncodingBuilder()
                .add(“”,””)
                .build();

        final Request request = new Request.Builder()
                .url(“http://www.wooyun.org”)
                .post(formBody)
                .build();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    if (response.isSuccessful()) {
                        Log.i(“WY”,”打印POST响应的数据:” + response.body().string());
                    } else {
                        throw new IOException(“Unexpected code ” + response);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

}
执行结果: 

官方Recipes
Synchronous Get(同步Get)
下载一个文件,打印他的响应头,以string形式打印响应体。 
       响应体的 string() 方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中。对于超过1MB的响应body,应使用流的方式来处理body。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + “: ” + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
  }
Asynchronous Get(异步Get)
       在一个工作线程中下载文件,当响应可读时回调Callback接口。读取响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
          System.out.println(responseHeaders.name(i) + “: ” + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
  }
Accessing Headers(提取响应头)
典型的HTTP头 像是一个 Map

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“https://api.github.com/repos/square/okhttp/issues”)
        .header(“User-Agent”, “OkHttp Headers.java”)
        .addHeader(“Accept”, “application/json; q=0.5”)
        .addHeader(“Accept”, “application/vnd.github.v3+json”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(“Server: ” + response.header(“Server”));
    System.out.println(“Date: ” + response.header(“Date”));
    System.out.println(“Vary: ” + response.headers(“Vary”));
  }
Posting a String(Post方式提交String)
       使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = “”
        + “Releases\n”
        + “——–\n”
        + “\n”
        + ” * _1.0_ May 6, 2013\n”
        + ” * _1.1_ June 15, 2013\n”
        + ” * _1.2_ August 11, 2013\n”;

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }

Post Streaming(Post方式提交流)

       以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。.

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8(“Numbers\n”);
        sink.writeUtf8(“——-\n”);
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(” * %s = %s\n”, i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + ” × ” + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting a File(Post方式提交文件)
以文件作为请求体是十分简单的。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse(“text/x-markdown; charset=utf-8”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File(“README.md”);

    Request request = new Request.Builder()
        .url(“https://api.github.com/markdown/raw”)
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting form parameters(Post方式提交表单)
       使用FormEncodingBuilder来构建和HTML标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add(“search”, “Jurassic Park”)
        .build();
    Request request = new Request.Builder()
        .url(“https://en.wikipedia.org/w/index.php”)
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Posting a multipart request(Post方式提交分块请求)
       MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

private static final String IMGUR_CLIENT_ID = “…”;
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse(“image/png”);

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart(“title”, “Square Logo”)
        .addFormDataPart(“image”, “logo-square.png”,
            RequestBody.create(MEDIA_TYPE_PNG, new File(“website/static/logo-square.png”)))
        .build();

    Request request = new Request.Builder()
        .header(“Authorization”, “Client-ID ” + IMGUR_CLIENT_ID)
        .url(“https://api.imgur.com/3/image”)
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
Parse a JSON Response With Gson(使用GSON解析JSON响应)
       Gson是一个在JSON和Java对象之间转换非常方便的api。这里我们用Gson来解析Github API的JSON响应。 
注意:ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8。

private final OkHttpClient client = new OkHttpClient();
  private final Gson gson = new Gson();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“https://api.github.com/gists/c2a7c39532239ff261be”)
        .build();
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
    for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
      System.out.println(entry.getKey());
      System.out.println(entry.getValue().content);
    }
  }

  static class Gist {
    Map<String, GistFile> files;
  }

  static class GistFile {
    String content;
  }
Response Caching(响应缓存)
       为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。 
       一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。 
       响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient.Builder()
        .cache(cache)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/helloworld.txt”)
        .build();

    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException(“Unexpected code ” + response1);

    String response1Body = response1.body().string();
    System.out.println(“Response 1 response:          ” + response1);
    System.out.println(“Response 1 cache response:    ” + response1.cacheResponse());
    System.out.println(“Response 1 network response:  ” + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException(“Unexpected code ” + response2);

    String response2Body = response2.body().string();
    System.out.println(“Response 2 response:          ” + response2);
    System.out.println(“Response 2 cache response:    ” + response2.cacheResponse());
    System.out.println(“Response 2 network response:  ” + response2.networkResponse());

    System.out.println(“Response 2 equals Response 1? ” + response1Body.equals(response2Body));
  }
       为了防止使用缓存的响应,可以用CacheControl.FORCE_NETWORK。为了防止它使用网络,使用CacheControl.FORCE_CACHE。需要注意的是:如果您使用FORCE_CACHE和网络的响应需求,OkHttp则会返回一个504提示,告诉你不可满足请求响应。 
       Canceling a Call(取消一个Call) 
       使用Call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当call没有必要的时候,使用这个api可以节约网络资源。例如当用户离开一个应用时。不管同步还是异步的call都可以取消。 
       你可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。.

 private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/2”) // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf(“%.2f Canceling call.%n”, (System.nanoTime() – startNanos) / 1e9f);
        call.cancel();
        System.out.printf(“%.2f Canceled call.%n”, (System.nanoTime() – startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    try {
      System.out.printf(“%.2f Executing call.%n”, (System.nanoTime() – startNanos) / 1e9f);
      Response response = call.execute();
      System.out.printf(“%.2f Call was expected to fail, but completed: %s%n”,
          (System.nanoTime() – startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf(“%.2f Call failed as expected: %s%n”,
          (System.nanoTime() – startNanos) / 1e9f, e);
    }
  }
Timeouts(超时)
       没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

private final OkHttpClient client;

  public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/2”) // This URL is served with a 2 second delay.
        .build();

    Response response = client.newCall(request).execute();
    System.out.println(“Response completed: ” + response);
  }
Per-call Configuration(每个Call的配置)
       使用OkHttpClient,所有的HTTP Client配置包括代理设置、超时设置、缓存设置。当你需要为单个call改变配置的时候,clone 一个 OkHttpClient。这个api将会返回一个浅拷贝(shallow copy),你可以用来单独自定义。下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://httpbin.org/delay/1”) // This URL is served with a 1 second delay.
        .build();

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(500, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println(“Response 1 succeeded: ” + response);
    } catch (IOException e) {
      System.out.println(“Response 1 failed: ” + e);
    }

    try {
      // Copy to customize OkHttp for this request.
      OkHttpClient copy = client.newBuilder()
          .readTimeout(3000, TimeUnit.MILLISECONDS)
          .build();

      Response response = copy.newCall(request).execute();
      System.out.println(“Response 2 succeeded: ” + response);
    } catch (IOException e) {
      System.out.println(“Response 2 failed: ” + e);
    }
  }
Handling authentication(处理验证)
       OkHttp会自动重试未验证的请求。当响应是401 Not Authorized时,Authenticator会被要求提供证书。Authenticator的实现中需要建立一个新的包含证书的请求。如果没有证书可用,返回null来跳过尝试。

 private final OkHttpClient client;

  public Authenticate() {
    client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            System.out.println(“Authenticating for response: ” + response);
            System.out.println(“Challenges: ” + response.challenges());
            String credential = Credentials.basic(“jesse”, “password1”);
            return response.request().newBuilder()
                .header(“Authorization”, credential)
                .build();
          }
        })
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url(“http://publicobject.com/secrets/hellosecret.txt”)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException(“Unexpected code ” + response);

    System.out.println(response.body().string());
  }
To avoid making many retries when authentication isn’t working, you can return null to give up. For example, you may want to skip the retry when these exact credentials have already been attempted:

if (credential.equals(response.request().header(“Authorization”))) {
    return null; // If we already failed with these credentials, don’t retry.
   }
You may also skip the retry when you’ve hit an application-defined attempt limit:
  if (responseCount(response) >= 3) {
    return null; // If we’ve failed 3 times, give up.
  }
This above code relies on this responseCount() method:
  private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }
OkHttp官方文档:https://github.com/square/okhttp/wiki 
参考链接:http://www.cnblogs.com/ct2011/p/3997368.html

Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 解析

 java, spring  Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 解析已关闭评论
1月 102017
 

这篇文章解释的很清楚http://www.ulewo.com/user/10001/blog/273,记录下:

我们在使用spring的时候经常会用到这些注解,那么这些注解到底有什么区别呢。我们先来看代码

同样分三层来看:

Action 层:

package com.ulewo.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class IocAction {
    @Autowired     private IocService service;
    
    public void add(){
        service.add();
    }
}

service层:(service就直接定义类了,没有定义接口,定义接口也是一样的)

package com.ulewo.ioc; import javax.annotation.Resource; import org.springframework.stereotype.Service; @Service public class IocService {
    @Resource     private IIocDao iocDao;
    public void add(){
        iocDao.add();
    }
}

Dao层

先定义一个接口

package com.ulewo.ioc; public interface IIocDao {
    public void add();
}

然后实现类:

package com.ulewo.ioc; import org.springframework.stereotype.Repository; @Repository public class IocDao implements IIocDao{
    public void add(){
        System.out.println("调用了dao");
    }
}

然后spring的配置,这个配置就很简单了,因为是基于注解的,我们不需要再xml中来定义很多

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>   <beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:jee="http://www.springframework.org/schema/jee"    xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:task="http://www.springframework.org/schema/task"    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
                       http://www.springframework.org/schema/tx 
                       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd 
                       http://www.springframework.org/schema/jee 
                       http://www.springframework.org/schema/jee/spring-jee-3.2.xsd 
                       http://www.springframework.org/schema/context 
                       http://www.springframework.org/schema/context/spring-context-3.2.xsd
                       http://www.springframework.org/schema/aop 
                       http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                       http://www.springframework.org/schema/task 
                       http://www.springframework.org/schema/task/spring-task-3.2.xsd">     <context:annotation-config />         <context:component-scan base-package="com.ulewo.ioc" >     </context:component-scan>   </beans>

让spring自动扫描包就行了。

然后是我们的测试类:

IocTest:

package com.ulewo.ioc; import junit.framework.TestCase; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class IocTest extends TestCase{
    
    public void testIoc(){
        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
        IocAction action = factory.getBean("iocAction", IocAction.class);
        action.add();
    }
}

运行后,我们会发现 控制台打印:调用了dao

@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 

这几个基本都用到了 除了 @Component  @Qualifier

我们观察会发现@Repository、@Service、@Controller 这几个是一个类型,其实@Component 跟他们也是一个类型的

Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service和 @Controller 其实这三个跟@Component 功能是等效的

@Service用于标注业务层组件(我们通常定义的service层就用这个)

@Controller用于标注控制层组件(如struts中的action)

@Repository用于标注数据访问组件,即DAO组件

@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

这几个注解是当你需要定义某个类为一个bean,则在这个类的类名前一行使用@Service(“XXX”),就相当于讲这个类定义为一个bean,bean名称为XXX; 这几个是基于类的,我们可以定义名称,也可以不定义,不定义会默认以类名为bean的名称(类首字母小写)。

然后我们在看后面的几个注解

@Resource、@Autowired、@Qualifier

当需要在某个类中定义一个属性,并且该属性是一个已存在的bean,要为该属性赋值我们就用着三个。我们看上面的代码可以看到这三个都是定义在一个属性上的,比如

@Resource private IIocDao iocDao;
@Autowired private IocService service;

那这几个到底有什么区别呢?

我们先看@Resource,它是javax.annotation.Resource; 这个包中,也就是说是javaEE中的,并不是spring中的

而且@Resource(“xxx”) 是可以定义bean名称的,就是说我这个属性要用那个bean来赋值。

@Autowired,它是org.springframework.beans.factory.annotation.Autowired 是这个包中,它是spring的包。

而且它没有@Autowired(“xxx”),那我要为这个bean定义名称怎么办这个时候可以用@Qualifier(“xxx”) 这个也是spring中的。这个xxx定义bean名称有什么用呢?我们回头看下刚才的代码。

在IIocDao 这个接口中,我们定义的实现类IocDao 只有一个,好那么我们再定义一个实现类:

package com.ulewo.ioc; import org.springframework.stereotype.Repository; @Repository public class IocDao2 implements IIocDao{
    public void add(){
        System.out.println("调用了dao2");
    }
}

其他不变,我们再运行:testIoc(),控制台打印出 调用了dao,所以在service层中

@Resource
private IIocDao iocDao;

这个iocDao 注入的是IocDao 这个实现。奇怪了,它怎么知道我要调用哪个实现呢?

好我们修改一下,把 private IIocDao iocDao;改一下,改成 private IIocDao iocDaox 把属性名改一下,再运行,会报错:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.ulewo.ioc.IIocDao] is defined: expected single matching bean but found 2: iocDao,iocDao2

错误很明显啊,有两个bean iocDao和iocDao2,但是我们的是iocDaox所以找不到了。所以可以看出来在用 @Repository注解来生成bean的时候,如果没有定义名称那么就会根据类名来生成。所以我们要调用第二个实现的时候可以 定义为private IIocDao iocDao2 。我们再运行:调用了dao2,所以可以根据属性名来区分,到底注入那个bean。但是有的人说,我不想定义bean名称跟类实现一样,我要定义其他的,那怎么玩呢,方法有2种:

第一种:我们在生成bean的时候就给bean定义个名称 

@Repository(“myIocDao”)
public class IocDao implements IIocDao{
    public void add(){
        System.out.println(“调用了dao”);
    }
}

当然@Service是一样的,这样就把这个实现定义为myIocDao了,而不是默认的类名 iocDao。

那么我们在使用这个bean的时候就要这么定义了:

@Resource
private IIocDao myIocDao;

运行 输出:调用了dao

如果你这里不是用的 myIocDao,你又多加了一个x,成了myIocDaox,你运行会是这样的:

 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.ulewo.ioc.IIocDao] is defined: expected single matching bean but found 2: myIocDao,iocDao2

所以,要自定义bean的名称可以在类注解的时候指定。

第二种:在注入bean的时候指定名称:

先看@Resource

我们这么定义下:

@Resource(name=”iocDao”)
private IIocDao xx;

注意:

@Repository
public class IocDao implements IIocDao{
    public void add(){
        System.out.println(“调用了dao”);
    }
}

这里还是用会默认的,也就是这个实现对应的是 iocDao这bean。如果你要为这个类指定别名bean,@Repository(“myIocDao”),那@Resource(name=”myIocDao”) 就要这么写了。就是这里的name要跟实现类对应的bean名称保持一致。private IIocDao xx; 这个属性名就随便写了。

运行:调用了dao

如果用Autowired就要这么写了

@Autowired
@Qualifier(“iocDao”)
private IIocDao xx;

因为Autowired 不能像Resource 那样带个参数指定一个name,就要用Qualifier来指定了。

而且还可以这么用

@Resource
@Qualifier(“iocDao”)
private IIocDao xx;

等同于

@Resource(name=”iocDao”)
private IIocDao xx;

记住一点:@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常 而@Resource默认按 byName自动注入罢了。其实spring注解,最常用的还是根据名称,根据类型啊,构造方法啊,用的非常少。所以在多个实现的时候我们定义好bean的名称就行,就不会错乱。

说了这么多,不知道对着几个注解是不是了解多一点了呢,貌似这些注解 没有根本上的区别,就看你习惯怎么用了。拿代码多跑几次,然后根据自己的想法改改,你就明白这几个注解的用处啦。

用lxml解析HTML

 python  用lxml解析HTML已关闭评论
10月 212016
 

先演示一段获取页面链接代码示例:

#coding=utf-8

from lxml import etree

html = ”’

<html>
<head>
<meta name=”content-type” content=”text/html; charset=utf-8″ />
<title>友情链接查询 – 站长工具</title>
<!– uRj0Ak8VLEPhjWhg3m9z4EjXJwc –>
<meta name=”Keywords” content=”友情链接查询” />
<meta name=”Description” content=”友情链接查询” />

</head>
<body>
<h1 class=”heading”>Top News</h1>
<p style=”font-size: 200%”>World News only on this page</p>
Ah, and here’s some more text, by the way.
<p>… and this is a parsed fragment …</p>

<a href=”http://www.cydf.org.cn/” rel=”nofollow” target=”_blank”>青少年发展基金会</a> 
<a href=”http://www.4399.com/flash/32979.htm” target=”_blank”>洛克王国</a> 
<a href=”http://www.4399.com/flash/35538.htm” target=”_blank”>奥拉星</a> 
<a href=”http://game.3533.com/game/” target=”_blank”>手机游戏</a>
<a href=”http://game.3533.com/tupian/” target=”_blank”>手机壁纸</a>
<a href=”http://www.4399.com/” target=”_blank”>4399小游戏</a> 
<a href=”http://www.91wan.com/” target=”_blank”>91wan游戏</a>

</body>
</html>

”’

page = etree.HTML(html.lower().decode(‘utf-8’))

hrefs = page.xpath(u”//a”)

for href in hrefs:

print href.attrib

 

打印出的结果为:

{‘href’: ‘http://www.cydf.org.cn/’, ‘target’: ‘_blank’, ‘rel’: ‘nofollow’}
{‘href’: ‘http://www.4399.com/flash/32979.htm’, ‘target’: ‘_blank’}
{‘href’: ‘http://www.4399.com/flash/35538.htm’, ‘target’: ‘_blank’}
{‘href’: ‘http://game.3533.com/game/’, ‘target’: ‘_blank’}
{‘href’: ‘http://game.3533.com/tupian/’, ‘target’: ‘_blank’}
{‘href’: ‘http://www.4399.com/’, ‘target’: ‘_blank’}
{‘href’: ‘http://www.91wan.com/’, ‘target’: ‘_blank’}

如果要取得<a></a>之间的内容,

for href in hrefs:

print href.text

结果为:

青少年发展基金会
洛克王国
奥拉星
手机游戏
手机壁纸
4399小游戏
91wan游戏

 

使用lxml前注意事项:先确保html经过了utf-8解码,即code = html.decode(‘utf-8’, ‘ignore’),否则会出现解析出错情况。因为中文被编码成utf-8之后变成 ‘/u2541’ 之类的形式,lxml一遇到 “/”就会认为其标签结束。

 

XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点(注意,不是指文档最外层的tag节点,而是指文档本身)。比如对于一个HTML文件来说,最外层的节点应该是”/html”。

定位某一个HTML标签,可以使用类似文件路径里的绝对路径,如page.xpath(u”/html/body/p”),它会找到body这个节点下所有的p标签;也可以使用类似文件路径里的相对路径,可以这样使用:page.xpath(u”//p”),它会找到整个html代码里的所有p标签:

<p style=”font-size: 200%”>World News only on this page</p>
Ah, and here’s some more text, by the way.
<p>… and this is a parsed fragment …</p>

注意:XPATH返回的不一定就是唯一的节点,而是符合条件的所有节点。如上所示,只要是body里的p标签,不管是body的第一级节点,还是第二级,第三级节点,都会被取出来。

如果想进一步缩小范围,直接定位到“<p style=”font-size: 200%”>World News only on this page</p>”要怎么做呢?这就需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。lxml里有个过滤语法:

p = page.xpath(u”/html/body/p[@style=’font-size: 200%’]”)

或者:p = page.xpath(u”//p[@style=’font-size:200%’]”)

这样就取出了bodystylefont-size:200%p节点,注意:这个p变量是一个lxml.etree._Element对象列表p[0].text结果为World News only on this page,即标签之间的值;p[0].values()结果为font-size: 200%,即所有属性值。其中 @style表示属性style,类似地还可以使用如@name, @id, @value, @href, @src, @class….

如果标签里面没有属性怎么办?那就可以用text(),position()等函数来过滤,函数text()的意思则是取得节点包含的文本。比如:<div>hello<p>world</p>< /div>中,用“div[text()=’hello’]”即可取得这个div,而world则是p的text()。函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点,它也可以被省略为“li[2]”。

不过要注意的是数字定位和过滤 条件的顺序。比如“ul/li[5][@name=’hello’]”表示取ul下第五项li,并且其name必须是hello,否则返回空。而如果用 “ul/li[@name=’hello’][5]”的意思就不同,它表示寻找ul下第五个name为”hello“的li节点。

此外,“*”可以代替所有的节点名,比如用”/html/body/*/span”可以取出body下第二级的所有span,而不管它上一级是div还是p或是其它什么东东。

而 “descendant::”前缀可以指代任意多层的中间节点,它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的 div,可以用“/descendant::div[@id=’leftmenu’]”,也可以简单地使用“ //div[@id=’leftmenu’]”。

text = page.xpath(u”/descendant::*[text()]”)表示任意多层的中间节点下任意标签之间的内容,也即实现蜘蛛抓取页面内容功能。以下内容使用text属性是取不到的:

复制代码
<div class="news"> 1. <b>无流量站点清理公告</b>&nbsp;&nbsp;2013-02-22<br /> 取不到的内容 </div> <div class="news"> 2. <strong>无流量站点清理公告</strong>&nbsp;&nbsp;2013-02-22<br />
取不到的内容
</div> <div class="news"> 3. <span>无流量站点清理公告</span>&nbsp;&nbsp;2013-02-22<br />
取不到的内容
</div> <div class="news"> 4. <u>无流量站点清理公告</u>&nbsp;&nbsp;2013-02-22<br />
取不到的内容
</div>
复制代码

这些“取不到的内容”使用这个是取不到的。怎么办呢?别担心,lxml还有一个属性叫做“tail”,它的意思是结束节点前面的内容,也就是说在“<br />”与“</div>”之间的内容。它的源码里面的意思是“text after end tag”

至于“following-sibling::”前缀就如其名所说,表示同一层的下一个节点。”following-sibling::*”就是任意下一个节点,而“following-sibling::ul”就是下一个ul节点。

如果script与style标签之间的内容影响解析页面,或者页面很不规则,可以使用lxml.html.clean模块。模块 lxml.html.clean提供 一个Cleaner 类来清理 HTML 页。它支持删除嵌入或脚本内容、 特殊标记、 CSS 样式注释或者更多。

cleaner = Cleaner(style=True, scripts=True,page_structure=False, safe_attrs_only=False)

print cleaner.clean_html(html)

注意,page_structure,safe_attrs_only为False时保证页面的完整性,否则,这个Cleaner会把你的html结构与标签里的属性都给清理了。使用Cleaner类要十分小心,小心擦枪走火。

 

忽略大小写可以:

page = etree.HTML(html)
keyword_tag = page.xpath(“//meta[translate(@name,’ABCDEFGHJIKLMNOPQRSTUVWXYZ’, ‘abcdefghjiklmnopqrstuvwxyz’)=’keywords’]”)

 

这里有详细的Cleaner类初始化参数说明:http://lxml.de/api/lxml.html.clean.Cleaner-class.html

转自:http://www.cnblogs.com/descusr/archive/2012/06/20/2557075.html