Android Volley源码分析(2)

2023-05-16

1、 RequestQueue的add接口
我们从RequestQueue的add接口入手:

public <T> Request<T> add(Request<T> request) {
    // Tag the request as belonging to this queue and add it to the set of current requests.
    // 将Request与RequestQueue关联起来,毕竟用户是可以创建多个RequestQueue的
    request.setRequestQueue(this);

    synchronized (mCurrentRequests) {
        //mCurrentRequests用于保留RequestQueue正在处理的Request
        //当Request处理完毕后,回调RequestQueue的finish接口,就可以被mCurrentRequests移除了
        mCurrentRequests.add(request);
    }

    // Process requests in the order they are added.
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    // If the request is uncacheable, skip the cache queue and go straight to the network.
    if (!request.shouldCache()) {
        //对于无需Cache的Request,直接加入到网络队列中,进行下载操作
        mNetworkQueue.add(request);
        return request;
    }

    // Insert request into stage if there's already a request with the same cache key in flight.
    // mWaitingRequests相当于是RequestQueue的运行时缓存
    synchronized (mWaitingRequests) {
        //Request的getCacheKey是可以重载的,默认使用的Request的url
        String cacheKey = request.getCacheKey();

        //如果之前已经发送过同样url的Request,且这个Request正在被处理
        if (mWaitingRequests.containsKey(cacheKey)) {
            // There is already a request in flight. Queue up.
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);

            //用LinkedList保存重复的请求,不再触发后续下载操作
            if (stagedRequests == null) {
                stagedRequests = new LinkedList<Request<?>>();
            }
            stagedRequests.add(request);

            //当一个Request处理完毕,回调RequestQueue的finish接口后,
            //其对应的stagedRequests将从mWaitingRequests移除
            //全部被添加到mCacheQueue中
            //于是,如果之前的Request已经下载成功,有了cache信息,就不会再触发下载操作
            //否则,Volley框架会重新进行下载
            mWaitingRequests.put(cacheKey, stagedRequests);
            ....................
        } else {
            // Insert 'null' queue for this cacheKey, indicating there is now a request in
            // flight.
            // 非重复的请求,会立即加入到CacheQueue中
            // 根据之前博客的分析,我们知道CacheDispatcher将在物理缓存中,
            // 进一步查找是否有该请求对应的回复信息
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
         }
         return request;
    }
}

从RequestQueue的add接口来看,其中比较值得一提的是引入了mWaitingRequests。

当访问同一个url的Request连续加入到RequestQueue时,只有第一个会立即被加入到mCacheQueue中进行处理,
其它后续的Request均被加入到mWaitingRequests中。
当第一个Request处理完毕回调Request的finish接口后,代码如下:

    <T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        // 首先从mCurrentRequests移除该Request
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

        //回调listener对应的接口
        synchronized (mFinishedListeners) {
            for (RequestFinishedListener<T> listener : mFinishedListeners) {
                listener.onRequestFinished(request);
            }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                //取出waitingRequests中Request对应的后续请求队列
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    ...............
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    // 将后续请求队列中的Request全部加入到CacheQueue中,供CacheDispatcher处理
                    // 如上文所述,若之前下载成功,CacheDispatcher就会从cache中获取到结果
                    // 否则,将重新触发下载
                    // 需要注意的是,如果请求队列中有多个Request,将被NetworkDispatcher并发处理
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

可以看出,RequestQueue通过mWaitingRequests,可以在一定程度上避免对同一个网络地址的重复访问。

2、BasicNetwork的performRequest接口
根据之前博客分析的CacheDispatcher和NetworkDispatcher的代码,
我们知道一个Request如果没有对应的缓存信息,
最终将被NetworkDispatcher交给BasicNetwork的performRequest函数处理。

在performRequest函数中,将进行实际的下载操作。
现在,我们来看看这部分代码:

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

    //此处while参数为true,必须返回结果或抛出异常才能结束
    //这么设计是为了便于重新下载
    while (true) {
        //以下均是用于保存返回结果的
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();

        try {
            // Gather headers.
            // 将用于保存http头部信息
            Map<String, String> headers = new HashMap<String, String>();

            //这部分代码对应于有cache,但需要重新更新信息的场景
            //如果有Cache,将Cache中的信息加入到headers中
            //其中比较重要的是,增加了"If-Modified-Since"字段,即要求从服务器获取xxx时间之后的数据
            addCacheHeaders(headers, request.getCacheEntry());

            //利用HttpStack进行实际的下载,得到httpResponse
            httpResponse = mHttpStack.performRequest(request, headers);

            //在这之后的代码比较繁杂,但主体的意思就是根据Response中的信息,进行相应的处理

            // Http Response中不同的statusCode
            // 定义了网络访问后不同的情况
            // 即用来说明网络访问是否成功、或者表明了网络访问失败的原因等
            StatusLine statusLine = httpResponse.getStatusLine();
            int statusCode = statusLine.getStatusCode();

            //将Response中的头部信息,按键值对存入到前文定义的responseHeaders中
            responseHeaders = convertHeaders(httpResponse.getAllHeaders());

            // Handle cache validation.
            // 网络访问返回304,说明该Request对应的Cache还是可以用的,服务端对应的资源并没有更新
            if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                Entry entry = request.getCacheEntry();

                if (entry == null) {
                    //构造Response返回
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                            responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // A HTTP 304 response does not have all header fields. We
                // have to use the header fields from the cache entry plus
                // the new ones from the response.
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                // 注释写的还是很清楚的,合并形成一个Response Header
                entry.responseHeaders.putAll(responseHeaders);

                //构造Response返回
                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                        entry.responseHeaders, true,
                        SystemClock.elapsedRealtime() - requestStart);
            }

            // Some responses such as 204s do not have content.  We must check.
            // 非304场景,保存httpResponse中的数据
            if (httpResponse.getEntity() != null) {
                responseContents = entityToBytes(httpResponse.getEntity());
            } else {
                // Add 0 byte response as a way of honestly representing a
                // no-content request.
                responseContents = new byte[0];
            }
            ............

            //网络返回结果失败,主动抛出IO异常,后文处理
            if (statusCode < 200 || statusCode > 299) {
                throw new IOException();
            }

            //正常情况,构造Response返回
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                    SystemClock.elapsedRealtime() - requestStart);

        //网络下载可能碰到的异常比较多,显得比较繁杂
        } catch (SocketTimeoutException e) {

            //attemptRetryOnException是用于判断是否还需要重试
            //如果不需要重试,就会抛出参数中的Error信息,结束performRequest函数
            //否则,就会回到while循环的起始部分,重新下载
            attemptRetryOnException("socket", request, new TimeoutError());
        } catch (ConnectTimeoutException e) {
            attemptRetryOnException("connection", request, new TimeoutError());
        } catch (MalformedURLException e) {
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
        } catch (IOException e) {
            //前文status code异常时,主动抛出了IOException
            int statusCode = 0;

            NetworkResponse networkResponse = null;
            if (httpResponse != null) {
                statusCode = httpResponse.getStatusLine().getStatusCode();
            } else {
                throw new NoConnectionError(e);
            }
             .....................
            if (responseContents != null) {
                networkResponse = new NetworkResponse(statusCode, responseContents,
                        responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);

                //401 Unauthorized 客户试图未经授权访问受密码保护的页面
                // 403 Forbidden 资源不可用
                if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                        statusCode == HttpStatus.SC_FORBIDDEN) {
                    attemptRetryOnException("auth",
                            request, new AuthFailureError(networkResponse));
                } else {
                    // TODO: Only throw ServerError for 5xx status codes.
                    // 这里抛出的异常,都会被NetworkDispatcher捕获,封装成VolleryError发送给UI线程
                    throw new ServerError(networkResponse);
                }
            } else {
                throw new NetworkError(networkResponse);
            }
        }
    }
}

BasicNetwork的performRequest看起来比较复杂,但它的逻辑其实还是比较简单的:
根据Http访问得到返回值中的status code,判断网络访问的结果,并将结果封装成NetworkResponse递交给UI线程。

此外,在某些错误场景下,BasicNetwork将调用attemptRetryOnException函数判断是否需要进行重传操作,
或者直接抛出异常让NetworkDispatcher捕获,形成递交给UI线程的VolleyError。

在进一步分析HttpStack的下载过程前,我们先来看看上文提到的attemptRetryOnException函数:

private static void attemptRetryOnException(String logPrefix, Request<?> request,
        VolleyError exception) throws VolleyError {
    //从Request中获取Retry Policy
    RetryPolicy retryPolicy = request.getRetryPolicy();
    int oldTimeout = request.getTimeoutMs();

    try {
        // 调用对应的retry接口,更新重传次数
        // 超过重传上限,抛出异常
        retryPolicy.retry(exception);
    } catch (VolleyError e) {
        request.addMarker(
                String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
        // 捕获retry接口抛出的异常后,再次抛出异常
        // 结束BasicNetwork的performRequest函数,返回错误信息给UI线程
        throw e;
    }
    request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}

目前Volley中原生的Request使用的均是DefaultRetryPolicy,我们看看对应的接口实现:

    .................

    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);

        // hasAttemptRemaining判断能否继续重传
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        //原生的设计是判断重传次数是否超过限制
        //mMaxNumRetries的值默认设计为1,即仅能重传一次
        return mCurrentRetryCount <= mMaxNumRetries;
    }
    ..............

不难看出,目前attemptRetryOnException函数主要根据当前Request的重传次数及上限,
来判断是否可以进行一次下载操作。

3、HttpStack的performRequest接口
从前文的代码可以看出,BasicNetwork最终的下载操作依赖于它的HttpStack。

根据之前博客的分析,我们知道Volley在创建RequestQueue时,
生成了HurlStack和HttpClientStack,并且在Android的高版本中将使用HurlStack。

因此,我们就以HurlStack的performRequest为例,看看下载的具体操作。

public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError {
    String url = request.getUrl();

    //map中保存头信息
    HashMap<String, String> map = new HashMap<String, String>();
    map.putAll(request.getHeaders());
    map.putAll(additionalHeaders);

    if (mUrlRewriter != null) {
        //按需对url进行重写
        String rewritten = mUrlRewriter.rewriteUrl(url);
        if (rewritten == null) {
            throw new IOException("URL blocked by rewriter: " + url);
        }
        url = rewritten;
    }

    URL parsedUrl = new URL(url);
    //创建URLConnection,后文分析openConnection
    HttpURLConnection connection = openConnection(parsedUrl, request);

    //添加头部附加信息
    for (String headerName : map.keySet()) {
        connection.addRequestProperty(headerName, map.get(headerName));
    }

    //根据Request,进一步添加信息,后文再进一步看看这个函数
    setConnectionParametersForRequest(connection, request);

    // Initialize HttpResponse with data from the HttpURLConnection.
    ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);

    //利用HttpURLConnection的getResponseCode方法得到网络访问的返回的status code
    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.");
    }

    //将结果封装成org.apache.http.message.BasicHttpResponse的格式
    StatusLine responseStatus = new BasicStatusLine(protocolVersion,
            connection.getResponseCode(), connection.getResponseMessage());
    BasicHttpResponse response = new BasicHttpResponse(responseStatus);

    // 根据Request Method及response code,判断是否还有数据需要下载
    if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
        // 若有数据待下载,则调用entityFromConnection获取数据,并封装到response中
        // 后文再来进一步分析这个函数
        response.setEntity(entityFromConnection(connection));
    }

    //将Http的头部信息,写入到response中
    for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
        if (header.getKey() != null) {
            Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
            response.addHeader(h);
        }
    }

    return response;
}

通过上文的代码,我们终于明白了,Volley的底层通信实际上依赖的是HttpURLConnection。
而HttpURLConnection实际上是通过Socket建立实际通信链接的。
之后,我们在单独利用一篇博客专门分析一下HttpURLConnection的通信流程。

现在,我们先来看看HttpStack的performRequest中调用的几个函数。

openConnection

openConnection函数负责根据Request中的信息,建立一个HttpURLConnection,其源码如下:

    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
        // 利用URL的openConnection接口创建HttpURLConnection,并设置了Follow Redirects属性
        HttpURLConnection connection = createConnection(url);

        // 配置HttpURLConnection的一些属性
        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
        // 默认的HurlStack并没有设置SslSocketFactory
        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {

            //HttpURLConnection的SocketFactory将负责创建出实际通信用的Socket
            //之后的博客分析HttpURLConnection的通信流程时,再来分析这些细节
            ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
        }

        return connection;
    }

从上述代码可以看出,openConnection函数主要负责开启HttpURLConnection,并设置一些必要的参数。

setConnectionParametersForRequest

从函数名即可看出,该函数将用于进一步为HttpURLConnection设置参数,我们稍微看看细节。

    static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request<?> request) throws IOException, AuthFailureError {
        switch (request.getMethod()) {

            //这个Method被deprecated了,根据post body,来决定到底是get方法还是post方法
            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();

                //如果是post方法,则直接写入DataOutputStream
                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;

            //下文的方法,除了post、put和patch外,都只是修改HttpURLConnection的Method参数
            //post、put和patch均会利用addBodyIfExists函数,进一步写入信息

            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.");
        }
    }

我们看看addBodyIfExists函数的代码:

    //容易看出,Method.DEPRECATED_GET_OR_POST的处理一致,就是判断是否有需上传的数据
    //如果有数据的话,就写入DataOutputStream中
    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();
        }
    }

entityFromConnection

entityFromConnection主要用于获取HttpResponse中数据,其源码如下:

    private static HttpEntity entityFromConnection(HttpURLConnection connection) {
        BasicHttpEntity entity = new BasicHttpEntity();
        InputStream inputStream;
        try {
            //实际上就是获取HttpURLConnection的InputStream
            //然后从InputStream中的到信息,填充到HttpEntity中
            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;
    }

至此,HurlStack的下载方式基本分析完毕,可以看出HurlStack主要根据Http头部的一些标志,
利用HttpURLConnection来完成实际的上传和下载工作。

4、ExecutorDelivery返回结果给UI线程
截至到这里,我们已经分析了Volley服务端处理Request的流程,也明白了Volley具体的下载操作,
现在是时候看看Volley框架如何将结果返回给UI线程了。

根据之前的代码,我们知道Volley在创建RequestQueue时,在RequestQueue的构造函数中创建了ExecutorDelivery。
ExecutorDelivery中封装了主线程对应的Handler。

我们看看ExecutorDelivery的构造函数:

    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                //Executor将实际的工作交给主线程的Handler处理
                handler.post(command);
            }
        };
    }

根据之前的代码,无论是CacheDispatcher还是NetworkDispatcher,
在处理完NetworkRequest后,均会调用ExecutorDelivery的postResponse接口发送处理结果,
或者利用postError接口发送错误信息。

我们一起来看一下ExecutorDelivery的函数:

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

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

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

从这段代码可以看出,最终将在主线程中运行ResponseDeliveryRunnable的run方法:

        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

容易看出,上文均是回调Request的接口,这些接口就可以由其子类来实现。
例如,StringRequest的deliverResponse函数:

    @Override
    protected void deliverResponse(String response) {
        //回调Listener的onResponse接口
        mListener.onResponse(response);
    }

至此,Volley框架的主要流程分析完毕,其它的ImageLoader、NetworkImageView均是在当前框架的RequestQueue、ImageRequest
基础上做到进一步封装,就不做进一步分析了。

5、Google提供的原理图
最后,结合Google提供的Volley原理图,我们一起回顾一下整个Volley的工作流程。


如上图所示,整个Volley框架共有三类线程。
主线程的工作主要是创建和启动RequestQueue的各组件,
然后由封装主线程Handler的ExecutorDelivery传递信息。

 

缓存查找线程的工作主要由CacheDispatcher来完成。
CacheDispatcher将在物理文件中查找是否有Request对应的信息,
如果能够查找到信息,就会利用ExecutorDelivery向UI线程返回结果。
否则,将Request递交给NetworkDispatcher处理。

Volley中默认会有4个NetworkDispatcher,
它们将从支持并发访问的BlockingQueue中获取Request进行处理。
NetworkDispatcher主要以HttpURLConnection来进行实际的下载操作,
完成下载工作后,同样通过ExecutorDelivery向UI线程返回结果,
并按照需要将结果写入到物理缓存中。

ExecutorDelivery将在主线程中,回调Request的接口,
这些接口将由Request的子类来实现。

从整体上来讲,理解Volley框架还是比较简单的,
其关键的特点就是缓存、并发,当然Volley本身还会进行一些重传操作。
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Volley源码分析(2) 的相关文章

  • 在应用程序简历中隐藏软键盘

    我有一个 Android 应用程序 使用 Xamarin 用 C 编写 我已将应用程序简化为包含 TextView 和用于横幅广告的 Google admod AdView 的 LinearLayout 我不希望软键盘出现在应用程序中 这不
  • Android 通知进度条冻结

    这是我正在使用的代码 http pastebin com 3bMCKURu http pastebin com 3bMCKURu 问题是 一段时间后 文件变得更重 通知栏下拉速度变慢 最后它就冻结了 你的通知太频繁了 这就是它冻结的原因 让
  • 使用 Android 前台服务为 MediaPlayer 创建通知

    问题就在这里 我目前正在开发一个应用程序 该应用程序必须提供 A 广播播放器 来自 URL 的 AAC 直播 还有一个播客播放器 来自 URL 的 MP3 流 该应用程序必须能够在后台运行 Android 服务 并通过以下方式向用户公开持续
  • 如何从 SQLite 获取记录总数

    我正在尝试从 Sqlite DB 获取行的总数 以下是我想要做的代码片段 我不知道我在这里做错了什么 public static int getTotalCount Context context Cursor c null try c g
  • Phonegap - 如何将.txt文件保存在Android手机的根目录中

    我正在尝试使用phonegap 将 txt 文件保存在Android 手机的根目录中 我已经安装了这些插件 cordova plugin file 和 cordova plugin file transfer 在 config xml 文件
  • 菜单未显示在应用程序中

    由于某种原因 我的操作菜单在我的 Android Studio 应用程序中消失了 我正在按照教程学习如何创建 Android 应用程序 但最终遇到了这个问题 我正在使用 atm 的教程 http www raywenderlich com
  • 接近语法错误(代码1)插入Android SQLite

    我正在创建一个通讯录应用程序 用户可以在其中输入姓名 电子邮件地址和号码 我希望将此数据保存在数据库中 但我似乎无法使插入方法起作用 我收到的错误是 android database sqlite SQLiteException near
  • 无法在自定义 AOSP 上安装 Google Play 中的某些应用程序:项目不可用。理由:9

    我在尝试从 Google Play 安装某些应用程序时收到以下错误 LibraryUtils isAvailable not available restriction 9 DocUtils getAvailabilityRestricti
  • 线程自动利用多个CPU核心?

    假设我的应用程序运行 2 个线程 例如渲染线程和游戏更新线程 如果它在具有多核 CPU 当今典型 的移动设备上运行 我是否可以期望线程在可能的情况下自动分配给不同的核心 我知道底层操作系统内核 Android linux内核 决定调度 我的
  • Android 原理图内容提供程序库配置?

    Jake Wharton 在最近的一次演讲中提到了这个库 它看起来是避免大量样板文件的好方法 所以我尝试了一下 但没有任何成功 https github com SimonVT schematic https github com Simo
  • Android 版 Robotium - solo.searchText () 不起作用

    我在使用 Robotium 时遇到 searchText 函数问题 我正在寻找这个字符串
  • Android 启动器快捷方式

    我制作了一个简单的打卡 打卡时钟应用程序 我想向用户添加在主屏幕上创建快捷方式的选项 该快捷方式将切换应用程序的状态 超时 超时 但我根本不希望此快捷方式在屏幕上打开应用程序 这是我的 setupShortcut private void
  • 检查 Android 手机上的方向

    如何查看Android手机是横屏还是竖屏 当前配置用于确定要检索的资源 可从资源中获取Configuration object getResources getConfiguration orientation 您可以通过查看其值来检查方向
  • 材质设计图标颜色

    应该是哪种颜色 暗 材质图标 在官方文档上 https www google com design spec style icons html icons system icons https www google com design s
  • 调节麦克风录音音量

    我们正在尝试调整录音时的音量级别 麦克风似乎非常敏感 会接收到很多静电 我们查看了 setVolumeControlStream 但找不到传入其中来控制麦克风的流 将您的音频源设置为 MIC using MediaRecorder Audi
  • Android 设备上的静默安装

    我已经接受了一段时间了 在 Android 上静默安装应用程序是不可能的 也就是说 让程序安装捆绑为 APK 的应用程序 而不提供标准操作系统安装提示并完成应用程序安装程序活动 但现在我已经拿到了 Appbrain 快速网络安装程序的副本
  • 下载后从谷歌照片库检索图像

    我正在发起从图库中获取照片的意图 当我在图库中使用 Nexus 谷歌照片应用程序时 一切正常 但如果图像不在手机上 在 Google Photos 在线服务上 它会为我下载 选择图像后 我将图像发送到另一个活动进行裁剪 但在下载的情况下 发
  • Android - 将 ImageView 保存到具有全分辨率图像的文件

    我将图像放入 ImageView 中 并实现了多点触控来调整 ImageView 中的图像大小和移动图像 现在我需要将调整大小的图像保存到图像文件中 我已经尝试过 getDrawingCache 但该图像具有 ImageView 的大小 我
  • Android 如何聚焦当前位置

    您好 我有一个 Android 应用程序 可以在谷歌地图上找到您的位置 但是当我启动该应用程序时 它从非洲开始 而不是在我当前的城市 国家 位置等 我已经在developer android com上检查了信息与位置问题有关 但问题仍然存在
  • 在 Android 中,如何将字符串从 Activity 传递到 Service?

    任何人都可以告诉如何将字符串或整数从活动传递到服务 我试图传递一个整数 setpossition 4 但它不需要 启动时总是需要 0 Service 我不知道为什么我不能通过使用 Service 实例从 Activity 进行操作 publ

随机推荐