Netty HTTP on Android

Netty是一個(gè)NIO的客戶端服務(wù)器框架,它使我們可以快速而簡(jiǎn)單地開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用程序,比如協(xié)議服務(wù)器和客戶端。它大大簡(jiǎn)化了網(wǎng)絡(luò)編程,比如TCP和UDP socket服務(wù)器。

“快速而簡(jiǎn)單”并不意味著開(kāi)發(fā)出來(lái)的應(yīng)用可維護(hù)性或性能不好。Netty已經(jīng)實(shí)現(xiàn)了大量的協(xié)議,比如FTP,SMTP,HTTP,以及各種基于二進(jìn)制和文本的傳統(tǒng)協(xié)議??梢哉f(shuō)Netty已經(jīng)找到了一種方法來(lái)實(shí)現(xiàn)簡(jiǎn)單的開(kāi)發(fā),高性能,穩(wěn)定性,靈活性而不需要做妥協(xié)。

Netty的結(jié)構(gòu)大體如下圖這樣:

Netty Structure

就設(shè)計(jì)而言,Netty給不同的傳輸類(lèi)型,不管是阻塞的還是非阻塞的,提供了統(tǒng)一的接口。它基于一個(gè)靈活的和可擴(kuò)展的事件模型,這使得處理不同邏輯的部分可以有效的隔離開(kāi)來(lái)。它具有高度可定制的線程模型 - 單線程,一個(gè)或多個(gè)線程池,比如SEDA。它還提供無(wú)連接的datagram socket支持。

如Netty這般,功能如此強(qiáng)大,性能如此優(yōu)良的網(wǎng)絡(luò)庫(kù),不用在Android上真是可惜了。這里我們就嘗試將Netty用在Android上。

下載Netty

首先是下載Netty。Netty的官網(wǎng)地址,我們可以在這里找到下載Netty的地址,當(dāng)然還有許許多多的文檔。這里使用了當(dāng)前4.1.x最新版的Netty 4.1.4,下載地址:

http://dl.bintray.com/netty/downloads/netty-4.1.4.Final.tar.bz2

解壓之后,為了省事,直接將netty-4.1.4.Final/jar/all-in-one/下的netty-all-4.1.4.Final.jar拷貝進(jìn)了工程下app module的libs目錄下。

Netty的簡(jiǎn)單使用

不出意外,直接通過(guò)編譯。接著我們就參考netty/example/src/main/java/io/netty/example/http/snoop中client部分的代碼,將Netty用起來(lái),這主要有如下幾個(gè)類(lèi):

package io.netty.example.http.snoop;

import java.net.URI;
import java.net.URISyntaxException;

import javax.net.ssl.SSLException;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

public class HttpClient {
    public static final String TAG = "NettyClient";

    public static void getResponse(final String url) {
        new Thread() {
            @Override
            public void run() {
                try {
                    URI uri = new URI(url);
                    String scheme = uri.getScheme() == null ? "http" : uri.getScheme();
                    String host = uri.getHost();
                    int port = uri.getPort();

                    if (port == -1) {
                        if ("http".equalsIgnoreCase(scheme)) {
                            port = 80;
                        } else if ("https".equalsIgnoreCase(scheme)) {
                            port = 443;
                        }
                    }
                    if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
                        System.err.println("Only HTTP(S) is supported.");
                        return;
                    }

                    final boolean ssl = "https".equalsIgnoreCase(scheme);
                    final SslContext sslCtx;
                    if (ssl) {
                        sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
                    } else {
                        sslCtx = null;
                    }

                    EventLoopGroup group = new NioEventLoopGroup();
                    try {
                        Bootstrap bootstrap = new Bootstrap();
                        bootstrap.group(group).channel(NioSocketChannel.class)
                                .handler(new HttpClientInitializer(sslCtx));

                        // Make the connection attempt.
                        Channel channel = bootstrap.connect(host, port).sync().channel();

                        // Prepare the HTTP request.
                        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                                HttpMethod.GET, url);
                        request.headers().set(HttpHeaderNames.HOST, host);
                        request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE);
                        request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);

                        // Send the HTTP request.
                        channel.writeAndFlush(request);

                        // Wait for the server to close the connection.
                        channel.closeFuture().sync();
                    } finally {
                        group.shutdownGracefully();
                    }
                } catch (URISyntaxException e) {
                } catch (SSLException e) {
                } catch (InterruptedException e) {
                }
            }
        }.start();
    }
}

這個(gè)class提供給外部調(diào)用的接口。使用者可以傳入U(xiǎn)RL,將借由這個(gè)類(lèi),通過(guò)Netty來(lái)訪問(wèn)網(wǎng)絡(luò)并獲取響應(yīng)。然后來(lái)看HttpClientInitializer:

package io.netty.example.http.snoop;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.ssl.SslContext;

public class HttpClientInitializer extends ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public HttpClientInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();

        // Enable HTTPS if necessary.
        if (sslCtx != null) {
            p.addLast(sslCtx.newHandler(ch.alloc()));
        }

        p.addLast(new HttpClientCodec());

        // Remove the following line if you don't want automatic content decompression.
        p.addLast(new HttpContentDecompressor());

        // Uncomment the following line if you don't want to handle HttpContents.
        //p.addLast(new HttpObjectAggregator(1048576));

        p.addLast(new HttpClientHandler());
    }
}

這個(gè)class負(fù)責(zé)對(duì)Channel的Pipeline進(jìn)行初始化,這其中最關(guān)鍵的是HttpClientHandler:

package io.netty.example.http.snoop;

import android.util.Log;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;

public class HttpClientHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        if (msg instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) msg;

            Log.i(HttpClient.TAG, "STATUS: " + response.status());
            Log.i(HttpClient.TAG, "VERSION: " + response.protocolVersion());

            if (!response.headers().isEmpty()) {
                for (CharSequence name: response.headers().names()) {
                    for (CharSequence value: response.headers().getAll(name)) {
                        Log.i(HttpClient.TAG, "HEADER: " + name + " = " + value);
                    }
                }
            }
        }
        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            String responseContent = content.content().toString(CharsetUtil.UTF_8);
            Log.i(HttpClient.TAG, responseContent);

            if (content instanceof LastHttpContent) {
                ctx.close();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

我們正是通過(guò)實(shí)現(xiàn)HttpClientHandler,而獲取到Netty返回給我們的響應(yīng)的。

在我們的Android應(yīng)用代碼中調(diào)用HttpClient來(lái)通過(guò)Netty從網(wǎng)絡(luò)獲取響應(yīng):

package io.netty.example.http.snoop;

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

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private TextView mTextScreen;

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

        Button btnGetIpInfo = (Button) findViewById(R.id.btn_get_ip_info_with_netty);
        btnGetIpInfo.setOnClickListener(mBtnClickListener);

        mTextScreen = (TextView) findViewById(R.id.text_screen);
    }

    View.OnClickListener mBtnClickListener = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            String url = "http://ip.taobao.com/service/getIpInfo.php?ip=123.58.191.68";
            mTextScreen.setText("To access " + url);
            Log.i(TAG, "To access " + url);
            if (R.id.btn_get_ip_info_with_netty == v.getId()) {
                HttpClient.getResponse(url);
            }
        }
    };
}

當(dāng)然不能忘記了在AndroidManifest.xml中添加對(duì)INTERNET權(quán)限的請(qǐng)求:

    <uses-permission android:name="android.permission.INTERNET"/>

做完了所有這些之后,Netty基本上就可以跑起來(lái)了。不過(guò)意外還是發(fā)生了:

08-10 10:13:33.670 17720-17720/io.netty.example.http.snoop6.myapplication I/MainActivity: To access http://ip.taobao.com/service/getIpInfo.php?ip=123.58.191.68
08-10 10:13:33.818 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err: java.lang.NoClassDefFoundError: com.jcraft.jzlib.Inflater
08-10 10:13:33.818 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.compression.JZlibDecoder.<init>(JZlibDecoder.java:27)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.compression.ZlibCodecFactory.newZlibDecoder(ZlibCodecFactory.java:122)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.http.HttpContentDecompressor.newContentDecoder(HttpContentDecompressor.java:57)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:87)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:46)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:88)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:372)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:358)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:350)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:435)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:293)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:280)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:396)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:248)
08-10 10:13:33.826 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:250)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:372)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:358)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:350)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:372)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:358)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:129)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:571)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:474)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:428)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:398)
08-10 10:13:33.834 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:877)
08-10 10:13:33.841 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
08-10 10:13:33.841 17720-18708/io.netty.example.http.snoop6.myapplication W/System.err:     at java.lang.Thread.run(Thread.java:841)
08-10 10:13:33.841 17720-18708/io.netty.example.http.snoop6.myapplication I/NettyClient: --------- beginning of /dev/log/system
08-10 10:35:02.162 17720-17720/io.netty.example.http.snoop6.myapplication I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@41c10f28 time:1282964865

有一個(gè)class com.jcraft.jzlib.Inflater找不到。這還需要添加對(duì)jzlib的依賴:

    compile 'com.jcraft:jzlib:1.1.2'

至此順利地將Netty跑起來(lái):

{
    "code":0,
    "data":{
        "country":"中國(guó)",
        "country_id":"CN",
        "area":"華東",
        "area_id":"300000",
        "region":"浙江省",
        "region_id":"330000",
        "city":"杭州市",
        "city_id":"330100",
        "county":"",
        "county_id":"-1",
        "isp":"網(wǎng)易網(wǎng)絡(luò)",
        "isp_id":"1000119",
        "ip":"123.58.191.68"
    }
}

Netty的裁剪

Netty很強(qiáng)大,all-in-one jar用起來(lái)很方便,這很不錯(cuò)。但all-in-one jar有點(diǎn)大,其中包含的一些諸如對(duì)memcache,redis,stomp,sctp和udt等的支持,我們?cè)谝苿?dòng)端并不會(huì)用掉。因而需要對(duì)它做一點(diǎn)裁剪。

既然不能用all-in-one包,那就把需要的幾個(gè)jar文件單獨(dú)copy進(jìn)我們的工程好了。對(duì)于android而言,目測(cè)netty-4.1.4.Final/jar/下我們需要拷貝的jar文件主要有下面這些:

netty-codec-4.1.4.Final.jar
netty-codec-http-4.1.4.Final.jar
netty-codec-http2-4.1.4.Final.jar
netty-codec-socks-4.1.4.Final.jar
netty-common-4.1.4.Final.jar
netty-handler-4.1.4.Final.jar
netty-transport-4.1.4.Final.jar

用這些文件來(lái)替換之前的netty-all-4.1.4.Final.jar。遇到了編譯錯(cuò)誤:

:app:compileDebugJavaWithJavac
Full recompilation is required because at least one of the classes of removed jar 'netty-all-4.1.4.Final.jar' requires it. Analysis took 0.364 secs.
/media/data/MyProjects/MyApplication/app/src/main/java/io/netty/example/http/snoop/myapplication/HttpClient.java:67: 錯(cuò)誤: 無(wú)法訪問(wèn)ByteBufHolder
                        request.headers().set(HttpHeaderNames.HOST, host);
                               ^
  找不到io.netty.buffer.ByteBufHolder的類(lèi)文件
/media/data/MyProjects/MyApplication/app/src/main/java/io/netty/example/http/snoop/myapplication/HttpClientInitializer.java:39: 錯(cuò)誤: 無(wú)法訪問(wèn)ByteBufAllocator
            p.addLast(sslCtx.newHandler(ch.alloc()));
                                                ^
  找不到io.netty.buffer.ByteBufAllocator的類(lèi)文件
/media/data/MyProjects/MyApplication/app/src/main/java/io/netty/example/http/snoop/myapplication/HttpClientHandler.java:48: 錯(cuò)誤: 找不到符號(hào)
            String responseContent = content.content().toString(CharsetUtil.UTF_8);
                                            ^
  符號(hào):   方法 content()
  位置: 類(lèi)型為HttpContent的變量 content
注: /media/data/MyProjects/MyApplication/app/src/main/java/io/netty/example/http/snoop/myapplication/HttpClient.java使用或覆蓋了已過(guò)時(shí)的 API。
注: 有關(guān)詳細(xì)信息, 請(qǐng)使用 -Xlint:deprecation 重新編譯。
3 個(gè)錯(cuò)誤
:app:compileDebugJavaWithJavac FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

看來(lái)是少了一些東西了,ByteBufHolder。那就把netty-buffer-4.1.4.Final.jar也加進(jìn)工程里。再次編譯,繼續(xù)出錯(cuò),這是在產(chǎn)生APK時(shí)遇到了麻煩:

:app:transformResourcesWithMergeJavaResForDebug FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/INDEX.LIST
    File1: /media/data/MyProjects/MyApplication/app/libs/netty-codec-4.1.4.Final.jar
    File2: /media/data/MyProjects/MyApplication/app/libs/netty-transport-4.1.4.Final.jar
    File3: /media/data/MyProjects/MyApplication/app/libs/netty-buffer-4.1.4.Final.jar
    File4: /media/data/MyProjects/MyApplication/app/libs/netty-codec-socks-4.1.4.Final.jar
    File5: /media/data/MyProjects/MyApplication/app/libs/netty-handler-4.1.4.Final.jar
    File6: /media/data/MyProjects/MyApplication/app/libs/netty-codec-http2-4.1.4.Final.jar
    File7: /media/data/MyProjects/MyApplication/app/libs/netty-codec-http-4.1.4.Final.jar


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 15.352 secs
Duplicate files copied in APK META-INF/INDEX.LIST
    File1: /media/data/MyProjects/MyApplication/app/libs/netty-codec-4.1.4.Final.jar
    File2: /media/data/MyProjects/MyApplication/app/libs/netty-transport-4.1.4.Final.jar
    File3: /media/data/MyProjects/MyApplication/app/libs/netty-buffer-4.1.4.Final.jar
    File4: /media/data/MyProjects/MyApplication/app/libs/netty-codec-socks-4.1.4.Final.jar
    File5: /media/data/MyProjects/MyApplication/app/libs/netty-handler-4.1.4.Final.jar
    File6: /media/data/MyProjects/MyApplication/app/libs/netty-codec-http2-4.1.4.Final.jar
    File7: /media/data/MyProjects/MyApplication/app/libs/netty-codec-http-4.1.4.Final.jar

11:03:39: External task execution finished 'assembleDebug'.

總是報(bào)Duplicate files copied in APK META-INF/INDEX.LIST的錯(cuò)誤。這主要是因?yàn)檫@些jar文件里,不同的jar文件中的META-INF/INDEX.LIST包含了相同的內(nèi)容所致。這需要在build.gradle中的android元素里添加如下的配置:

    packagingOptions {
        exclude 'META-INF/INDEX.LIST'
    }

再次編譯,這次則是包Duplicate files copied in APK META-INF/io.netty.versions.properties。這證明了前面的方法是行之有效的,于是把META-INF/io.netty.versions.properties也加進(jìn)packagingOptions的exclude列表:

    packagingOptions {
        exclude 'META-INF/INDEX.LIST'
        exclude 'META-INF/io.netty.versions.properties'
    }

再次編譯,編譯通過(guò)。但是在運(yùn)行的時(shí)候遇到了一點(diǎn)小麻煩:

11:29:59.685 9005-9050/com.example.hanpfei0306.myapplication W/dalvikvm: threadid=11: thread exiting with uncaught exception (group=0x41959ce0)
08-11 11:29:59.685 9005-9050/com.example.hanpfei0306.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-1197
                                                                                     Process: com.example.hanpfei0306.myapplication, PID: 9005
                                                                                     java.lang.NoClassDefFoundError: io.netty.resolver.DefaultAddressResolverGroup
                                                                                         at io.netty.bootstrap.Bootstrap.<clinit>(Bootstrap.java:53)
                                                                                         at com.example.hanpfei0306.myapplication.HttpClient$1.run(HttpClient.java:57)

提示找不到class io.netty.resolver.DefaultAddressResolverGroup,看來(lái)我們的jar文件是加少了,把netty-resolver-4.1.4.Final.jar也加進(jìn)來(lái)。終于,我們前面編寫(xiě)的HttpClient能夠正常地跑起來(lái)了。經(jīng)過(guò)一番裁剪,Netty的大小大概從3.4MB減小到2.9MB。

總結(jié)一下,若不用體型巨大的netty-all jar文件,則我們需要導(dǎo)入如下的這些jar文件以編譯和運(yùn)行Netty:

netty-buffer-4.1.4.Final.jar
netty-codec-4.1.4.Final.jar
netty-codec-http-4.1.4.Final.jar
netty-codec-http2-4.1.4.Final.jar
netty-codec-socks-4.1.4.Final.jar
netty-common-4.1.4.Final.jar
netty-handler-4.1.4.Final.jar
netty-resolver-4.1.4.Final.jar
netty-transport-4.1.4.Final.jar

TODO

Netty的諸多抽象,比如Bootstrap,Channel,EventLoopGroup,Handler,codec,Buffer等諸多高級(jí)特性,以及它的NIO接口,這里都沒(méi)有涉及,線程模型也沒(méi)有仔細(xì)厘清。這里只是最最簡(jiǎn)單的一個(gè)使用范例,要把Netty很好地應(yīng)用在實(shí)際的項(xiàng)目中,還需要對(duì)Netty本身更深入的研究。

同時(shí),對(duì)Netty的裁剪可能也過(guò)于粗糙,或許還有更多的東西可以裁剪掉,以減小最終的APP的大小。

要使用Netty來(lái)支持HTTP/2也還需要做更多的事情。

但窺探到Netty的靈活強(qiáng)大,還是讓我們對(duì)這個(gè)庫(kù)充滿期待。

參考文檔

多個(gè)jar包的合并

Netty4.x中文教程系列

netty-4-user-guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容