DBMNG数据库管理与应用

书籍是全世界的营养品。生活里没有书籍,就好像没有阳光;智慧里没有书籍,就好像鸟儿没有翅膀。
当前位置:首页 > 移动应用 > Android

Android 使用OkHttp扩展Volley

我们先来回忆一下Volley的用法,使用Volley前,我们一般会先构造出一个RequestQueue,然后不断往该对象中添加请求Request,之后Volley便会进行调度,至于走缓存还是走网络这就看就没有请求过了。而构造RequestQueue的方法如下。
 Volley.newRequestQueue(mContext);
而该方法,内部实际上是调用了两个参数的重载方法
 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
我们看下两个参数的重载方法的实现。
    private static final String DEFAULT_CACHE_DIR = volley;




    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);




        String userAgent = volley/0;
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + / + info.versionCode;
        } catch (NameNotFoundException e) {
        }




        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }




        Network network = new BasicNetwork(stack);




        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();




        return queue;
    }
在该方法中,进行了以下几步的操作
创建缓存目录,即第一行代码实现的内容。 获得了userAgent,默认情况下是volley/0,如果能够获取到包名,则会被重写为包名/版本号 然后根据第二个入参HttpStack,判断是否为空,其实我们默认的构造RequestQueue中就是传了一个null,如果不为null,则使用参数,如果为null,则根据android系统的版本进行判断,如果系统版本大于等于9,则使用HurlStack,如果系统版本小于9,则使用HttpClientStack 之后构造Network和Cache对象,将它们传入RequestQueue的构造函数中构造RequestQueue对象,调用该对象的start方法开始死循环,在该循环中如果消息队列中有新的请求则会进行处理。
在这个过程中我们需要关心的是HurlStack和HttpClientStack到底是什么。其实这两个对象都是实现了HttpStack接口的。该接口的定义如下。
public interface HttpStack {
    public HttpResponse performRequest(Request request, Map additionalHeaders)
        throws IOException, AuthFailureError;
}
该方法中的逻辑需要实现根据参数去完成一个请求,并返回请求结果。我们需要根据这两个实现类,去仿一个OkHttp的实现类,首先我们看一下HurlStack的实现。也就是通过HttpUrlConnection实现的
@Override
    public HttpResponse performRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap map = new HashMap();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException(URL blocked by rewriter:  + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion(HTTP, 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException(Could not retrieve response code from HttpUrlConnection.);
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }
首先拿到url,新建了一个Map对象,往该map对象中放入了请求头的内容,请求头来自两个地方,一个是入参Request中,通过request.getHeaders()获得,一个是入参additionalHeaders中,直接add进去即可
然后会判断一个叫mUrlRewriter的变量是否为空,如果不为空,则调用它的rewriteUrl方法返回新的url,其实它是一个接口,其定义如下,默认情况下这个变量是空,所以这一过程不会被调用。
public interface UrlRewriter {
        /**
         * Returns a URL to use instead of the provided one, or null to indicate
         * this URL should not be used at all.
         */
        public String rewriteUrl(String originalUrl);
    }
根据url创建URL对象 通过openConnection方法获得了一个HttpURLConnection对象
protected HttpURLConnection createConnection(URL url) throws IOException {
    return (HttpURLConnection) url.openConnection();
}
private HttpURLConnection openConnection(URL url, Request request) throws IOException {
    HttpURLConnection connection = createConnection(url);


    int timeoutMs = request.getTimeoutMs();
    connection.setConnectTimeout(timeoutMs);
    connection.setReadTimeout(timeoutMs);
    connection.setUseCaches(false);
    connection.setDoInput(true);


    // use caller-provided custom SslSocketFactory, if any, for HTTPS
    if (https.equals(url.getProtocol()) && mSslSocketFactory != null) {
        ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
    }


    return connection;
}
在该方法中的操作,主要是打开一个连接,设置超时时间,SSLSocketFactory,是否使用缓存等内容。
然后通过一个for循环往获得的HttpsURLConnection对象中添加请求头,也就是刚才的map
调用setConnectionParametersForRequest设置请求方法和请求体
static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST:
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    // Prepare output. There is no need to set Content-Length explicitly,
                    // since this is handled by HttpURLConnection using the size of the prepared
                    // output stream.
                    connection.setDoOutput(true);
                    connection.setRequestMethod(POST);
                    connection.addRequestProperty(HEADER_CONTENT_TYPE,
                            request.getPostBodyContentType());
                    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                    out.write(postBody);
                    out.close();
                }
                break;
            case Method.GET:
                // Not necessary to set the request method because connection defaults to GET but
                // being explicit here.
                connection.setRequestMethod(GET);
                break;
            case Method.DELETE:
                connection.setRequestMethod(DELETE);
                break;
            case Method.POST:
                connection.setRequestMethod(POST);
                addBodyIfExists(connection, request);
                break;
            case Method.PUT:
                connection.setRequestMethod(PUT);
                addBodyIfExists(connection, request);
                break;
            case Method.HEAD:
                connection.setRequestMethod(HEAD);
                break;
            case Method.OPTIONS:
                connection.setRequestMethod(OPTIONS);
                break;
            case Method.TRACE:
                connection.setRequestMethod(TRACE);
                break;
            case Method.PATCH:
                connection.setRequestMethod(PATCH);
                addBodyIfExists(connection, request);
                break;
            default:
                throw new IllegalStateException(Unknown method type.);
        }
    }
 private static void addBodyIfExists(HttpURLConnection connection, Request request)
            throws IOException, AuthFailureError {
        byte[] body = request.getBody();
        if (body != null) {
            connection.setDoOutput(true);
            connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(body);
            out.close();
        }
    }
该方法会会根据请求的方法调用setRequestMethod设置对应的字符串,并且如果是POST,PUT,PATCH方法,则会调用addBodyIfExists方法设置请求体
之后是新建一个ProtocolVersion对象,传的是HTTP 1.1 获得响应码,如果为-1则扔出异常 构造响应状态行StatusLine 构造响应体,会根据hasResponseBody函数判断是否有响应体内容,如果有则进行设置。
private static boolean hasResponseBody(int requestMethod, int responseCode) {
    return requestMethod != Request.Method.HEAD
        && !(HttpStatus.SC_CONTINUE <= responseCode && responseCode < HttpStatus.SC_OK)
        && responseCode != HttpStatus.SC_NO_CONTENT
        && responseCode != HttpStatus.SC_NOT_MODIFIED;
}
private static HttpEntity entityFromConnection(HttpURLConnection connection) {
    BasicHttpEntity entity = new BasicHttpEntity();
    InputStream inputStream;
    try {
        inputStream = connection.getInputStream();
    } catch (IOException ioe) {
        inputStream = connection.getErrorStream();
    }
    entity.setContent(inputStream);
    entity.setContentLength(connection.getContentLength());
    entity.setContentEncoding(connection.getContentEncoding());
    entity.setContentType(connection.getContentType());
    return entity;
}
之后将响应头添加到HttpResponse对象中去,并返回这个对象
整个过程其实也是相当简单的,接下来看HttpClientStack的实现,该实现和HurlStack十分相似。
@Override
public HttpResponse performRequest(Request request, Map additionalHeaders)
        throws IOException, AuthFailureError {
    HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
    addHeaders(httpRequest, additionalHeaders);
    addHeaders(httpRequest, request.getHeaders());
    onPrepareRequest(httpRequest);
    HttpParams httpParams = httpRequest.getParams();
    int timeoutMs = request.getTimeoutMs();
    // TODO: Reevaluate this connection timeout based on more wide-scale
    // data collection and possibly different for wifi vs. 3G.
    HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
    HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
    return mClient.execute(httpRequest);
}
首先调用createHttpRequest方法设置请求的内容
static HttpUriRequest createHttpRequest(Request request,
            Map additionalHeaders) throws AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST: {
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    HttpPost postRequest = new HttpPost(request.getUrl());
                    postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
                    HttpEntity entity;
                    entity = new ByteArrayEntity(postBody);
                    postRequest.setEntity(entity);
                    return postRequest;
                } else {
                    return new HttpGet(request.getUrl());
                }
            }
            case Method.GET:
                return new HttpGet(request.getUrl());
            case Method.DELETE:
                return new HttpDelete(request.getUrl());
            case Method.POST: {
                HttpPost postRequest = new HttpPost(request.getUrl());
                postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(postRequest, request);
                return postRequest;
            }
            case Method.PUT: {
                HttpPut putRequest = new HttpPut(request.getUrl());
                putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(putRequest, request);
                return putRequest;
            }
            case Method.HEAD:
                return new HttpHead(request.getUrl());
            case Method.OPTIONS:
                return new HttpOptions(request.getUrl());
            case Method.TRACE:
                return new HttpTrace(request.getUrl());
            case Method.PATCH: {
                HttpPatch patchRequest = new HttpPatch(request.getUrl());
                patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(patchRequest, request);
                return patchRequest;
            }
            default:
                throw new IllegalStateException(Unknown request method.);
        }
    }
private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest,
        Request request) throws AuthFailureError {
    byte[] body = request.getBody();
    if (body != null) {
        HttpEntity entity = new ByteArrayEntity(body);
        httpRequest.setEntity(entity);
    }
}
这一步,和HurlStack中的setConnectionParametersForRequest的作用是类似的
根据上一步的返回结果HttpUriRequest 对象,调用addHeaders对该对象设置请求头,请求头的来源和HurlStack是一样的
private static void addHeaders(HttpUriRequest httpRequest, Map headers) {
    for (String key : headers.keySet()) {
        httpRequest.setHeader(key, headers.get(key));
    }
}
设置超时时间 然后调用HttpClient对象的execute方法,获得HttpResponse对象
可以从上面两个类中看到很多类似的地方
设置请求头 设置请求方法和请求体 设置超时时间等参数 设置响应头和响应体
根据这些步骤,我们写一个使用OkHttp实现的HttpStack的实现类。首先创建类OkHttpStack,实现HttpStack接口
public class OkHttpStack implements HttpStack {




    @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError {




        return null;
    }


}
提供一个构造函数,传入OkHttpClient 对象
private final OkHttpClient mOkHttpClient;


public OkHttpStack(OkHttpClient okHttpClient) {
    if (okHttpClient == null) {
        throw new IllegalArgumentException(OkHttpClient can't be null);
    }
    mOkHttpClient = okHttpClient;
}
然后实现performRequest方法,首先我们设置一下超时时间
OkHttpClient client = mOkHttpClient.clone();
int timeoutMs = request.getTimeoutMs();
client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);
client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
为了不对外部的OkHttpClient 造成影响,我们这里调用了clone方法克隆了一个。当然也可以直接使用原对象,即
OkHttpClient client = mOkHttpClient;
int timeoutMs = request.getTimeoutMs();
client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);
client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
接下来设置请求头,请求头的来源有两个,上面已经解析过了。
com.squareup.okhttp.Request.Builder builder=new com.squareup.okhttp.Request.Builder();
builder.url(request.getUrl());


Map headers=request.getHeaders();


for(String name:headers.keySet()){
    builder.addHeader(name,headers.get(name));
}


for(String name:additionalHeaders.keySet()){
    builder.addHeader(name,additionalHeaders.get(name));
}
设置请求头完毕后自然就是请求方法和请求体了,为了保持命名的约定,还是使用setConnectionParametersForRequest函数名,其实现如下
static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request request) throws IOException, AuthFailureError {
    switch (request.getMethod()) {
        case Request.Method.DEPRECATED_GET_OR_POST:
            byte[] postBody = request.getPostBody();
            if (postBody != null) {
                builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()),postBody));
            }
            break;
        case Request.Method.GET:
            builder.get();
            break;
        case Request.Method.DELETE:
            builder.delete();
            break;
        case Request.Method.POST:
            builder.post(createRequestBody(request));
            break;
        case Request.Method.PUT:
            builder.put(createRequestBody(request));
            break;
        case Request.Method.HEAD:
            builder.head();
            break;
        case Request.Method.OPTIONS:
            builder.method(OPTIONS,null);
            break;
        case Request.Method.TRACE:
            builder.method(TRACE,null);
            break;
        case Request.Method.PATCH:
            builder.patch(createRequestBody(request));
            break;
        default:
            throw new IllegalStateException(Unknown method type.);
    }
}
内部还调用了createRequestBody方法设置请求体,其实现如下
private static RequestBody createRequestBody(Request r) throws AuthFailureError{
    final byte[] body = r.getBody();
    if (body == null) return null;


    return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
}
设置完毕后,自然就是开始请求了
com.squareup.okhttp.Request okRequest=builder.build();


Call call=client.newCall(okRequest);
Response okresponse=call.execute();
请求完毕,对响应头和响应体进行设置。
首先获得请求的协议
private static ProtocolVersion parseProtocol(final Protocol p)
{
    switch (p)
    {
        case HTTP_1_0:
            return new ProtocolVersion(HTTP, 1, 0);
        case HTTP_1_1:
            return new ProtocolVersion(HTTP, 1, 1);
        case SPDY_3:
            return new ProtocolVersion(SPDY, 3, 1);
        case HTTP_2:
            return new ProtocolVersion(HTTP, 2, 0);
    }


    throw new IllegalAccessError(Unkwown protocol);
}
构造响应状态行
BasicStatusLine responseStatus = new BasicStatusLine(
            parseProtocol(okresponse.protocol()),
            okresponse.code(),
            okresponse.message()
    );
设置响应体
BasicHttpResponse response=new BasicHttpResponse(responseStatus);
response.setEntity(entityFromOkHttpResponse(okresponse));
entityFromOkHttpResponse函数的实现如下,包括对响应内容,响应内容长度,响应内容编码,响应类型进行设置
private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException
{
    BasicHttpEntity entity = new BasicHttpEntity();
    ResponseBody body = r.body();


    entity.setContent(body.byteStream());
    entity.setContentLength(body.contentLength());
    entity.setContentEncoding(r.header(Content-Encoding));


    if (body.contentType() != null)
    {
        entity.setContentType(body.contentType().type());
    }
    return entity;
}
设置响应头
int size=responseHeaders.size();
String name=null;
String value=null;
for(int i=0;i
最后返回HttpResponse对象
return response;
贴一下全部的代码
public class OkHttpStack implements HttpStack {
    private final OkHttpClient mOkHttpClient;


    public OkHttpStack(OkHttpClient okHttpClient) {
        if (okHttpClient == null) {
            throw new IllegalArgumentException(OkHttpClient can't be null);
        }
        mOkHttpClient = okHttpClient;
    }


    @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError {
        OkHttpClient client = mOkHttpClient;
        int timeoutMs = request.getTimeoutMs();
        client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
        client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);
        client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);


        com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder();
        builder.url(request.getUrl());


        Map headers = request.getHeaders();


        for (String name : headers.keySet()) {
            builder.addHeader(name, headers.get(name));
        }


        for (String name : additionalHeaders.keySet()) {
            builder.addHeader(name, additionalHeaders.get(name));
        }


        setConnectionParametersForRequest(builder, request);
        com.squareup.okhttp.Request okRequest = builder.build();


        Call call = client.newCall(okRequest);
        Response okresponse = call.execute();


        BasicStatusLine responseStatus = new BasicStatusLine(
                parseProtocol(okresponse.protocol()),
                okresponse.code(),
                okresponse.message()
        );


        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromOkHttpResponse(okresponse));
        Headers responseHeaders = okresponse.headers();


        int size = responseHeaders.size();
        String name = null;
        String value = null;
        for (int i = 0; i < size; i++) {
            name = responseHeaders.name(i);
            if (name != null) {
                response.addHeader(new BasicHeader(name, value));
            }
        }


        return response;
    }


    private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException {
        BasicHttpEntity entity = new BasicHttpEntity();
        ResponseBody body = r.body();


        entity.setContent(body.byteStream());
        entity.setContentLength(body.contentLength());
        entity.setContentEncoding(r.header(Content-Encoding));


        if (body.contentType() != null) {
            entity.setContentType(body.contentType().type());
        }
        return entity;
    }


    static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {
            case Request.Method.DEPRECATED_GET_OR_POST:
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody));
                }
                break;
            case Request.Method.GET:
                builder.get();
                break;
            case Request.Method.DELETE:
                builder.delete();
                break;
            case Request.Method.POST:
                builder.post(createRequestBody(request));
                break;
            case Request.Method.PUT:
                builder.put(createRequestBody(request));
                break;
            case Request.Method.HEAD:
                builder.head();
                break;
            case Request.Method.OPTIONS:
                builder.method(OPTIONS, null);
                break;
            case Request.Method.TRACE:
                builder.method(TRACE, null);
                break;
            case Request.Method.PATCH:
                builder.patch(createRequestBody(request));
                break;
            default:
                throw new IllegalStateException(Unknown method type.);
        }
    }


    private static ProtocolVersion parseProtocol(final Protocol p) {
        switch (p) {
            case HTTP_1_0:
                return new ProtocolVersion(HTTP, 1, 0);
            case HTTP_1_1:
                return new ProtocolVersion(HTTP, 1, 1);
            case SPDY_3:
                return new ProtocolVersion(SPDY, 3, 1);
            case HTTP_2:
                return new ProtocolVersion(HTTP, 2, 0);
        }


        throw new IllegalAccessError(Unkwown protocol);
    }


    private static RequestBody createRequestBody(Request r) throws AuthFailureError {
        final byte[] body = r.getBody();
        if (body == null) return null;


        return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
    }
}
至于用法,也很简单,在创建RequestQueue对象的时候,第二个参数传入我们的OkHttpStack对象即可。
Volley.newRequestQueue(this,new OkHttpStack(new OkHttpClient()));
此外,网上还有一种更加简洁的实现,但是得添加okhttp-urlconnection依赖
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'
该实现不是通过实现HttpStack接口实现的,而是继承HurlStack类实现的
public class SimpleOkHttpStack extends HurlStack {
    private final OkUrlFactory okUrlFactory;
    public SimpleOkHttpStack() {
        this(new OkHttpClient());
    }
    public SimpleOkHttpStack(OkHttpClient okHttpClient) {
        if (okHttpClient == null) {
            throw new NullPointerException(Client must not be null.);
        }
        this.okUrlFactory = new OkUrlFactory(okHttpClient);
    }
    @Override
    protected HttpURLConnection createConnection(URL url) throws IOException {
        return okUrlFactory.open(url);
    }
}
甚至你可以选择继承HttpClientStack ,传入OkHttp内部的OkApacheClient实现类,当然也需要添加依赖
compile 'com.squareup.okhttp:okhttp-apache:2.5.0'
public class SimpleHttpClientStack extends HttpClientStack {
    public SimpleHttpClientStack(OkHttpClient client) {
        super(new OkApacheClient(client));
    }
}
基本上所有的扩展方法都说了一遍,第一种是完全自己实现,第二种是选择继承现有的类去实现,这个思路和Androd开发中自定义View的思路基本一致,至于选择哪一种,完全看你自己需要。








本站文章内容,部分来自于互联网,若侵犯了您的权益,请致邮件chuanghui423#sohu.com(请将#换为@)联系,我们会尽快核实后删除。
Copyright © 2006-2023 DBMNG.COM All Rights Reserved. Powered by DEVSOARTECH            豫ICP备11002312号-2

豫公网安备 41010502002439号