Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

7、volley 源码解析之缓存线程工作流程

$
0
0

文章摘要
1、volley 缓存线程运行流程
2、volley 实现分解步骤


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下缓存线程的工作原理:

一、CacheDispatcher线程

  • 1、Volley中有三个线程,CacheDispatcher是其中的缓存线程。

  • 2、CacheDispatcher缓冲线程的目的是在缓冲池中,执行分流,将Request请求分发出去。
    线程循环运行,线程原料来自mCacheQueue,在主线程可通过RequestQueue.add方法将请求加入mCacheQueue。

  • 3、可以将工作流程简单归纳为以下几步:

    • 1、线程循环运行,线程原料来自mCacheQueue。
    • 2、优先从缓冲区获得数据,如果缓存区中存在数据,则直接返回数据给主线程。
    • 3、如果缓存区【没有命中数据】或者【缓存数据过期】,则将请求(Request)分发给NetworkDispatcher(网络线程),网络线程会重新同步数据。

附:流程图

  • Volley Cache Thread流程图.png

二、实现分析

1、线程循环运行,获得Request对象

while (true) {
  try {
    // Get a request from the cache triage queue, blocking until
    // at least one is available.
    //1、hlwang:CacheDispatcher原料来自mCacheQueue,第一步,获得Request
    final Request<?> request = mCacheQueue.take();
    request.addMarker("cache-queue-take");
    ... ...
  }
}

2、如果Request被用户取消了,则不再需要继续执行了

// If the request has been canceled, don't bother dispatching it.
//2、hlwang:如果request已取消,已经不需要继续了
if (request.isCanceled()) {
    request.finish("cache-discard-canceled");
    continue;
}

3、优先检查缓存区,如果数据没有命中(即:数据不存在),则交给Network线程去同步数据

// Attempt to retrieve this item from cache.
//3、hlwang:如果在缓存中,不存在数据,说明是新数据,则:交给mNetworkQueue去同步新数据。
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
    request.addMarker("cache-miss");
    // Cache miss; send off to the network dispatcher.
    mNetworkQueue.put(request);
    continue;
}

4、如果缓存数据过期了,依旧交给Network线程去同步数据

// If it is completely expired, just send it to the network.
//4、hlwang:如果缓存过期,那么说明数据太旧了,交给mNetworkQueue去同步新数据。
if (entry.isExpired()) {
    request.addMarker("cache-hit-expired");
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
}

5、缓存的数据被命中,则解析缓存数据,构建Response对象

// We have a cache hit; parse its data for delivery back to the request.
//5、wanghailu:我们命中了一条缓存数据(w找到了一个保质期内的缓存hl),解析数据并构建响应对象Response。
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
        new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");

6、如果缓存数据,不需要刷新,则将响应数据,通过Delivery回调给用户

if (!entry.refreshNeeded()) {
    // Completely unexpired cache hit. Just deliver the response.
    //6、如果entry数据不需要刷新,则使用mDelivery将响应传递出去
    mDelivery.postResponse(request, response);
} 

7、缓存的数据需要再次更新,那么现将缓存数据返回给用户,接着通过主线程发起同步数据请求

    // Soft-expired cache hit. We can deliver the cached response,
    // but we need to also send the request to the network for
    // refreshing.
    //7、虽然被缓存命中,但数据轻微过期。我们可以将缓存响应数据传递分发,
    //但我们同样需要将请求发送到mNetworkQueue去刷新、更新。
    request.addMarker("cache-hit-refresh-needed");
    request.setCacheEntry(entry);

    // Mark the response as intermediate.
    //7.1、更新response状态为  媒介
    response.intermediate = true;

    // Post the intermediate response back to the user and have
    // the delivery then forward the request along to the network.
    //7.2、主线程分发
    mDelivery.postResponse(request, response, new Runnable() {
        @Override
        public void run() {
            try {
                mNetworkQueue.put(request);
            } catch (InterruptedException e) {
                // Not much we can do about this.
            }
        }

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:30:57 原文链接
阅读:60 评论:0 查看评论

8、volley 源码解析之网络线程工作流程

$
0
0

文章摘要
1、volley 网络线程工作原理
2、volley 实现 分解原理


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下网络线程的工作原理:

1、NetworkDispatcher 网络线程

1、Volley中有三个线程,NetworkDispatcher是其中的网络线程。

2、NetworkDispatcher网络线程的目的是同步网络数据、保存网络数据。

网络线程,只负责同步网络数据,不负责解析。解析工作交给请求来做,因为不同的请求,解析的方法、流程多不同,将这些工作交给发起方也就是Request来处理。

线程循环运行,线程原料来自mNetworkQueue,其中的来源主要包括两部分:
- a)、主线程可通过RequestQueue.add方法将请求加入mNetworkQueue。
- b)、mCacheQueue发现缓存数据已过期、或者需要再次从网络上来同步。

3、可以将工作流程简单归纳为以下几步:

  • a)、线程循环运行,线程原料来自mNetworkQueue。
  • b)、同步网络数据,并将网络返回的原始数据,封装成NetworkResponse。
  • c)、调用请求发起方(Request)的解析函数,得到result。

附:流程图

2、实现分析

1、线程循环运行,获得Request对象

while (true) {
  try {
    // Get a request from the cache triage queue, blocking until
    // at least one is available.
    //1、hlwang:CacheDispatcher原料来自mCacheQueue,第一步,获得Request
    final Request<?> request = mCacheQueue.take();
    request.addMarker("cache-queue-take");
    ... ...
  }
}

2、如果Request被用户取消了,则不再需要继续执行了

// If the request has been canceled, don't bother dispatching it.
//2、hlwang:如果request已取消,已经不需要继续了
if (request.isCanceled()) {
    request.finish("cache-discard-canceled");
    continue;
}

3、同步网络,得到原始数据,保存在NetworkResponse。

// Perform the network request.
//3、网络处理requests,并返回NetworkResponse
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");

4、不需要将响应回调给主线程的情况。

// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
//4、如果服务器返回状态码 = 304,同时该请求Request已经传递过,则不需要再次响应传递。
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
    request.finish("not-modified");
    continue;
}

5、如何解析原始数据,只有请求方才知道,故:解析工作交给请求方

// Parse the response here on the worker thread.
//5、解析请求响应数据,得到Response数据
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");

6、缓存操作,如果数据需要缓存,那么调用传递的缓存接口来缓存。

// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
//6、是否需要缓存并含有缓存数据,如果是,将cache数据加入到mCache
if (request.shouldCache() && response.cacheEntry != null) {
    mCache.put(request.getCacheKey(), response.cacheEntry);
    request.addMarker("network-cache-written");
}

7、通过mDelivery,将数据返回给主线程。

// Post the response back.
//7、将reponse 返回
request.markDelivered();
mDelivery.postResponse(request, response);

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:32:33 原文链接
阅读:72 评论:0 查看评论

9、volley 源码解析之消息分发工的工作流程

$
0
0

文章摘要
1、volley 消息传递工 工作原理


附:获取Volley源代码
Demos案例源码:https://github.com/HailouWang/DemosForApi

简介:

volley有两个主要的民工,CacheDispatcher以及NetworkDispatcher,也是两个线程,管理并处理Request任务。

volley为了保证大批量的网络请求以及数据解析不会影响到主线程的用户交互,使用了很多线程以及线程封装技巧。包括这里的Cache。

在用户发起网络请求后,volley就将用户的请求,丢到了本文介绍的缓存进程,缓存线程如果没有能力处理,就丢给网络线程,并告诉它,老大需要数据结果,你赶紧去网络上去拿。老大只要结果,不要过程。

主线程很忙,ResponseDelivery负责传递消息,伴君如伴虎,为了防止打扰到主线程的工作,ResponseDelivery也可以有一个线程,在目前的源码里,ResponseDelivery充分利用Handler的MessageQueue优势,管理并小心的将结果传递给主线程。

那么,本文就来介绍下消息传递工(ResponseDelivery)的工作原理:

1、传递工人(ResponseDelivery)初始化。

在初始化RequestQueue工具类时,会初始化传递工。传递工使用Handler来管理队列,Handler的Looper来自主线程。

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *创建一个工作池,非start方法调用前,不会开始执行
     * @param cache A Cache to use for persisting responses to disk
     * 1、缓存者。将相应数据持久化到硬盘
     * @param network A Network interface for performing HTTP requests
     * 2、网络处理者。处理HTTP请求的Network 接口
     * @param threadPoolSize Number of network dispatcher threads to create
     * 3、网络请求分发者。默认4个分发线程池
     * @param delivery A ResponseDelivery interface for posting responses and errors
     * 4、响应传递者。传递响应数据,以及错误日志信息
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

在这里,原生提供了一种默认的实现:

new ExecutorDelivery(new Handler(Looper.getMainLooper()))

在这段代码中,需要传入一个Handler对象,对象中的Loop来自主线程,可以这么说,Handler工作在主线程中。

备注:我们也可以创建自己的传递工(ResponseDelivery),原生提供的实现中,类图如下:

2、传递工人(ResponseDelivery)接收回调任务。

我们之前介绍过了,volley有两个默默工作的劳工,它们就是CacheDispatcher以及NetworkDispatcher,劳工们在做完工作后,是没有权利告诉主线程的,因为这是传递工人的工作。

NetworkDispatcher或者CacheDispatcher通过如下方式告诉ResponseDelivery,需要向主线程传递结果。

mDelivery.postResponse(request, response);

无论是Response还是Error,所有的这些响应都发给了ResponseDelivery中的线程池。

3、传递工人对接收到的任务的处理

    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }
    @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));
    }

线程池会将得到的消息,包装一个Runnable,利用Handler发送给主线程。

// If this request has canceled, finish it and don't deliver.
//1、如果请求已经取消,则不必传递
if (mRequest.isCanceled()) {
    mRequest.finish("canceled-at-delivery");
    return;
}

// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
    //2、如果Response没有错误,则分发result
    mRequest.deliverResponse(mResponse.result);
} else {
    //3、如果Reponse有错误,则分发error
    mRequest.deliverError(mResponse.error);
}

// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
//4、如果 响应属于媒介,标记marker
if (mResponse.intermediate) {
    mRequest.addMarker("intermediate-response");
} else {
    mRequest.finish("done");
}

4、传递工人将消息回调给主线程。

通过3中可以看到,发往主线程的回调,是通过mRequest.deliverResponse来实现的。

/**
 * Subclasses must implement this to perform delivery of the parsed
 * response to their listeners.  The given response is guaranteed to
 * be non-null; responses that fail to parse are not delivered.
 * @param response The parsed response returned by
 * {@link #parseNetworkResponse(NetworkResponse)}
 */
abstract protected void deliverResponse(T response);

通过Request#deliverResponse,我们看到,这是一个Abstract方法,不同的实现,传递方式也不同。这里介绍下原生的实现:
StringRequest.java

@Override
protected void deliverResponse(String response) {
    mListener.onResponse(response);
}

关注我的技术公众号,查看更多优质技术文章推送

微信扫一扫下方二维码即可关注:

关注我的技术公众号,查看更多优质技术文章推送

作者:hailushijie 发表于2017/7/30 13:33:17 原文链接
阅读:78 评论:0 查看评论

编程路上,对于迷失者的一些小小建议

$
0
0

前几天,在半梦半醒中写了一篇《编程路上,送给处于迷茫中的你和自己》,没想到还挺受欢迎,同时收到了一些朋友的留言和感谢,意外之余也挺开心。

这里写图片描述

大多人都会经历的迷茫

其实这也都难免的,现在计算机技术更新那么快,日新月异,各种技术、各种语言爆发式增长,我一个好朋友在小日本(没有鄙视的意思,习惯这么称呼了)从事开发工作,经常和我说想转行,做不下去了,公司一会让她学PHP,一会让她做HTML+CSS,过一段时间又是jsp,说不定哪一天又是让她做数据库,每次打电话都要诉苦一会,做为过来人,我也很明白她的苦楚,刚工作的一两年太累了,一个女生在异国他乡做着这样的工作,确实很不容易。最初不懂她那边情况,建议让她好好学一门,喜欢哪门技术就走哪条路子,可现实是残酷的,白天还要上班,上班期间做的可能是另外一门语言,下班时候已经头昏脑热了,吃个饭、散散步时间就不早了,哪有那么多时间再去学习别的知识,当一门语言熟悉了点后,项目更换了,又要接手其它语言。工作一年了,总是在几门语言中徘徊,而且对编程兴趣不大,目前做着类似于产品经理的事情,我曾推荐她,如果真的做不下去了,还不如早点转产品,这职位挺合适她。

建议:迷茫不可怕,可怕的是不知道接下来的路该怎么走。如果目前这份工作真的让自己不开心了,如果真的觉得自己做不下去了,转行要趁早。既然铁了心走下去,跪着也要走完自己选择的路,每个人都会经历这种迷茫,不妨把手头能做的事情做得更好,能学的东西学得更好。

这里写图片描述

贪多嚼不烂

中国有句古话叫做“贪多嚼不烂”,这句话在软件学习中也挺适用的,在最初工作的时候,我加了好多群,静静地看着群里那些人讨论各种技术,从前期学习角度来说,个人觉得QQ群是一个很好的平台,非常适合新人和学生,同样一个知识点,不同的人会有不同的想法和解释,总有一种解释适合你,实在看不懂去问,一般都能找到自己想要的答案。我是科班出生,然并卵,之前也提到了,入行的时候,我连最基本的九九乘法表都写不出来,工作时候什么都要自学,自学最大的弊端就是对很多名词不是很理解,遇到一些自己不懂的名词可以去查一下,时间充足的情况下,一天可以多了解几个,重在学会而不在多,第一份工作最大的好处就是时间多,多的怀疑人生,每天就是无脑的看视频和看书,看到烦的时候就去QQ群看消息,每当在QQ群里看到一些不理解或者陌生的名词,我就默默去百度(是的,那时候很少用Google,一方面是不会翻墙,另一方面是觉得百度就够了,当学会翻墙后,才看到质的区别,一个是送外卖,一个是推动人类发展的),有个群聊得多了,慢慢的混成了管理,在里面结识了好几个哥们,其中一个后来成了我很好的同事,现在是个全能型技术大牛,猿粪^_^。

建议:对于类似QQ群这样平台,鱼龙混杂,我也加过一些技术讨论群,群里就是吹吹牛、斗斗图,良禽择木而栖,对于要学的东西,贵在精与会,而不在多。

出门遇贵人

在之前那篇《编程路上,送给处于迷茫中的你和自己》中,从江阴的第一份工作离职到后来去了南京,中间有三个多月的时间没写上去,那时候我先去了上海,其实那时候拿到好几份offer,不知道是不想上班还是面试上瘾,都推掉了,又去了苏州,到了苏州,最多一天面试四家,又拿到了几份offer,后来又去了上海面试,已经过了年后找工作的黄金时间,offer没那么好拿了,继续找了一周工作,没合适的又去了苏州,入职金阊区的一家公司,不到一个月便换了工作,去了相城的一家公司,这家公司时间也不长就离职了,但这家公司给了我很大的收获,短短的二十多天时间里,我遇到职业生涯的第一位贵人,他叫Z汉生(他也是我入行以来最感谢的两个人之一),是做java的,对于我认知的java世界,就没发现有什么问题他不会的,上篇《线程池原理》就是出自他分享给我的博客,认识他的三年多,一直如此,神一样的存在,他很喜欢把自己会的教给别人,再加上本身就是学霸级别的,跟他共事的那段时间,感觉飞一般的进步。好景不长,因为种种原因,我们都从那家公司走了,他去了上海,我去了南京。

对于很多人来说,可能一生都不会遇到汉生那样的贵人相助,这件事,我一直感到很幸运,有时候在我自我感觉很良好的时候,每当和汉生大神一起吃饭,总感觉自己像个刚入行的小学生一样,坐在那里默默的听着老师讲着信手拈来的课。昨天再看《深入理解java虚拟机》的时候,下载XX笔记,很惊喜,大神给我分享好几篇我最近正想学的东西,(^__^) 嘻嘻……

建议:伯乐难寻,或许我也不是千里马,但是遇到这样亦师亦友的伙伴,还是要好好珍惜,很宝贵的一笔财富。很多时候,别人没必要对你好,只是认为值得才会不求回报的付出,感谢生命中指点过我的每个人。

这里写图片描述

提高自学能力

工作的几年里面,通过面试和被面试,还有各种聊天工具上沟通过的开发人员数不胜数,发现好多工作好几年的,说出的话和工作年限完全不符合,面向对象的六大基本原则更是不清晰,就不说代码质量了,对于学习设计模式,这些都是基础课程,一味地control C和control V没什么意思,那是刚工作该做的事,而对于开发的工作生涯,设计模式相当于兵家的《孙子兵法》和《三十六计》,可以使人更加聪明的写代码,基础的有《大话设计模式》、《head first 设计模式》,讲的深一点的有《设计模式之禅》、《java与模式》,如果对C#代码无障碍,个人更推荐《大话设计模式》和《java与模式》,这几本书我都有,做过对比,《大话设计模式》更通俗易懂,入门经典,《java与模式》讲的更全面更深入,可以更上一层楼,当然,另外两本书也都是良心之作,挺好。

现在专业APP也很多,csdn、简书、开源中国、博客园等,还有更加方便的微信公众号,上下班路上或者晚上睡觉前都可以逛一逛,看不懂的,看看热闹也挺好,扩展下知识面。

对于处于迷茫期的新人,很多人都有去培训的想法,为啥培训就一定能学好?既然培训能学好,为啥自学就学不好?是因为花了钱心疼还是因为有人指点才能学好?工作以后主要靠自学而不是被培训,公司更看重一个人的自学能力,Android路上,我是一路自学走过来的,对于这条路的辛苦知根知底,现在部分培训机构无德,不想着好好教学生知识,总是教学生怎么吹牛,背面试宝典,忽悠到高工资然后给培训机构好招人,被坑的却是企业,基本的职业道德都没有,满满的嫌弃,但还是有些培训机构挺不错的,我最初入门的时候看的也是培训机构流出来的视频,质量相当的高。

建议:自学的态度,很大一部分决定一个人的高度,战胜别人容易,战胜自己太难。

这里写图片描述

总结

建议已经给了好几条了,最后就想说一句话,基础才是重中之重,坚实的基础才能建造宏伟的建筑。

微信扫我^_^

这里写图片描述

作者:pangpang123654 发表于2017/7/30 19:49:10 原文链接
阅读:919 评论:8 查看评论

APP界面框架初窥---标签导航

$
0
0

这里写图片描述


标签导航是十大界面框架设计里最常用的界面框架设计,也是被业界之内公认的一种普遍使用的页面框架设计。那么这种页面框架设计在作业方面对一个用户来说也是最常见的一种页面框架设计,比如说微博、微信、手机百度、支付宝、淘宝,这些我们所谓的超级APP都是运用的标签导航,无一例外。

这里写图片描述

从这个角度也可以看出来,优秀的产品用标签导航这种页面框架设计是非常普及的。标签导航位于页面底部,标签的分类最好可以控制在5个之内。


使用这种框架的优点在于:

1.标签导航能够让用户清楚当前所在的入口位置。比如对于微信来说,无论用户在“发现”还是“对话框”里面,用户都能通过底部的高亮区域来划分当前所处的这个产品结构的区域。无论是当前位置的判断,还是要找这个入口,都比较方便,比如对于微信来说,很容易都过标签导航找到“朋友圈”。

这里写图片描述

2.轻松在各入口间频繁跳转且不会迷失方向。比如对于微信来说,微信团队不仅希望我们拿微信来聊天,还希望我们拿微信来逛朋友圈、购物、支付、滴滴打车等等,那么如果能够让用户在不同的入口间实现频繁的跳转,那这时用标签导航是最合适不过的。

这里写图片描述

3.直接展现最重要入口的内容信息。这有两层意思,第一层就是它能展示出来最重要的入口,比如拿微信来说有那么多的重要入口,显然“微信对话框”最重要,那么他们默认的把微信对话框作为主入口。同样微博最重要的是首页,所以默认把微博首页作为最主要的入口。其次,入口不仅可以展示,入口里面的信息也可以展示。

但是这种模式的缺点也是存在的:

功能入口过多时,该模式显得笨重不实用。怎么理解“功能模块过多”,比如说现在标签导航,一般情况下功能入口控制在5个以内,我们也会遇到6个的情况,但那种产品一般来说比较复杂,最少会是3个,最多5到6个。如果过多的话,标签导航会弹不开,那这种模式就失效了。过多不行,过少也不太方便,如果说就一个Tab、两个Tab,那么标签导航下面就会显得特别的空,也不太好看,所以这个时候我们就会知道标签导航的适用范围最好在3至5 个之间,并且这几个功能希望用户能够频繁的去操作。

好了,理论知识就普及到这边,下一篇我就来说说如何用代码来具体实现”标签导航”,效果如下:

这里写图片描述


好了。祝大家生活愉快。多多收获友谊和爱情。如果想获取更多的讯息,请扫描下方二维码关注我的微信公众号:

这里写图片描述

作者:shenjie12345678 发表于2017/7/30 21:26:17 原文链接
阅读:410 评论:5 查看评论

Android 内存查看常用命令

$
0
0

事情的终局强如事情的起头;存心忍耐的,胜过居心骄傲的。—传道书7:8

RAM(random-access memory)即内存的使用情况对系统的性能影响很大,OOM问题、内存泄露、程序卡顿等诸多问题,都跟不合理的内存使用相关,并且这类问题一般都比较隐晦,要解决该类问题,熟悉内存查看的方法很有必要。
本篇博文介绍Android平台上常用的内存观测方法。

procrank与procmem

这两个命令均需要root权限才能运行。
procrank
procrank可以快速的总览当前系统各个进程的内存使用情况,其主要展示了4个指标:

  • Vss(virtual set size): 进程可访问的内存大小,包含了共享内存。Android系统中大量的内存空间会被多个进程共享,比如一个进程在运行期间会调用到其他进程,那么Vss也就将调用到的其它进程的内存统计进来了。
  • Rss(resident set size): 进程实际使用的内存大小,同样也包含了共享内存,但它只将实际用到的共享内存统计入内。
  • Pss(proportional set size): 进程自身内存大小+按比例分配的共享内存大小。
  • Uss(unique set size): 进程自身的内存大小,不包括使用到的其他共享内存。程序退出后,系统就会多出Uss大小的内存出来。

举个例子说明下:假如有进程A,其自身运行期间的私有内存大小为a,进程B运行期间占据了b大小的内存。
A运行期间需要依赖B,则Vss=a+b。
A依赖B,但只是用到了B进程下的部分内存区域,大小为b1。则Rss=a+b1(b1<=b)
除了A以外,还有3个进程也同样在使用B,则Pss=a+b/(1+3)
只统计A进程自身占用的内存,则Uss=a

procrank的使用说明

# procrank -h
Usage: procrank [ -W ] [ -v | -r | -p | -u | -s | -h ]
    -v  Sort by VSS.
    -r  Sort by RSS.
    -p  Sort by PSS.
    -u  Sort by USS.
    -s  Sort by swap.
        (Default sort order is PSS.)
    -R  Reverse sort order (default is descending).
    -c  Only show cached (storage backed) pages
    -C  Only show non-cached (ram/swap backed) pages
    -k  Only show pages collapsed by KSM
    -w  Display statistics for working set only.
    -W  Reset working set of all processes.
    -h  Display this help screen.

其运行结果如下:

  PID       Vss      Rss      Pss      Uss    cmdline
 3062  1735540K  131548K   85448K   78648K    system_server
 3428  1028392K  115896K   74462K   68900K    com.android.systemui
26426  1012992K   83272K   42890K   39664K    com.android.settings
 3806  1001248K   81064K   41639K   38840K    com.android.phone
  353   152208K   40524K   28086K   26516K    /system/bin/cameraserver
 3414   978800K   64344K   27342K   25120K    com.google.android.inputmethod.pinyin
 9885  1643528K   64880K   24821K   22012K    com.android.mms

分析内存问题主要关注的是Uss跟Pss,尤其是Uss.因为Vss和Rss包含了共享内存,单个进程分析内存大小时,会有干扰。

由于procrank会列出整个系统进程的内存信息,而更多时候我们往往只关心特定进程的内存信息。可以用下面的shell语句过滤出我们关心的内容。

while true;do adb shell procrank|grep <proc-keywords>; sleep 6;done

上述命令会每隔6s打印出关心的进程内存信息。

procmem
procrank命令从宏观上给出了进程的内存总体情况,但如果需要详细分析某个进程的内存分配细节,这个时候就需要procmem出场了。
procmem使用说明:

# procmem
Usage: procmem [ -w | -W ] [ -p | -m ] [ -h ] pid
    -w  Displays statistics for the working set only.
    -W  Resets the working set of the process.
    -p  Sort by PSS.
    -m  Sort by mapping order (as read from /proc).
    -h  Hide maps with no RSS.

比如我们查看settings应用的内存分布:

# procmem -m 26426 (26426为settings的pid)
    Vss      Rss      Pss      Uss     ShCl     ShDi     PrCl     PrDi  Name
-------  -------  -------  -------  -------  -------  -------  -------  
  2084K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space (deleted)
194524K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space (deleted)
 12560K   10492K   10492K   10492K       0K       0K   10488K       4K  /dev/ashmem/dalvik-main space 1 (deleted)
  1780K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space 1 (deleted)
182268K       0K       0K       0K       0K       0K       0K       0K  /dev/ashmem/dalvik-main space 1 (deleted)
  7436K    7436K    4277K    4172K    3264K       0K    4172K       0K  /data/dalvik-cache/arm/system@framework@boot.art
 31116K   15260K    1696K     124K   15136K       0K     124K       0K  /data/dalvik-cache/arm/system@framework@boot.oat
     4K       4K       0K       0K       4K       0K       0K       0K  /data/dalvik-cache/arm/system@framework@boot.oat
          0K       0K       0K       0K       0K       0K       0K       0K  [vectors]
-------  -------  -------  -------  -------  -------  -------  -------  
1130688K  148940K  108346K  104332K   44512K      96K   62100K   42452K  TOTAL

中间大量的输出结果省略。

从procmem输出的结果可以看到它给出了内存是如何分配的细节,但面对这茫茫多的输出数据,我们该如何从中寻找问题点呢?

内存异常问题往往伴随着内存泄露,而内存泄露的本质就是申请的内存区域得不到正常释放,表现在procmem的输出结果就是多条同名的记录出现。因此如果发现procmem输出的结果里某条记录出现次数过多,比如五六百次,那么我们应当小心对待了。

dumpsys meminfo

dumpsys meminfo 也是分析内存的一把利器,如果不跟进程名或者进程号,则输出系统整体的内存情况,跟进程名则输出该进程的内存分配情况。

# adb shell dumpsys meminfo 26426
Applications Memory Usage (in Kilobytes):
Uptime: 206814437 Realtime: 374715375

** MEMINFO in pid 26426 [com.android.settings] **
                   Pss  Private  Private     Swap     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    32062    32016        0        0    67584    27961    39622
  Dalvik Heap     9623     9504        0        0    21384     9096    12288
 Dalvik Other     4857     4824        0        0                           
        Stack      404      404        0        0                           
       Ashmem        2        0        0        0                           
      Gfx dev    40220    40220        0        0                           
    Other dev        6        0        4        0                           
     .so mmap     1258      184       16        0                           
    .jar mmap        8        8        0        0                           
    .apk mmap      865        0      392        0                           
    .ttf mmap       63        0        8        0                           
    .dex mmap     6464        8     6456        0                           
    .oat mmap     1694        0      124        0                           
    .art mmap     4280     4172        0        0                           
   Other mmap      563        4      156        0                           
   EGL mtrack    13056    13056        0        0                           
      Unknown      601      600        0        0                           
        TOTAL   116026   105000     7156        0    88968    37057    51910

 App Summary
                       Pss(KB)
                        ------
           Java Heap:    13676
         Native Heap:    32016
                Code:     7196
               Stack:      404
            Graphics:    53276
       Private Other:     5588
              System:     3870

               TOTAL:   116026      TOTAL SWAP (KB):        0

 Objects
               Views:      985         ViewRootImpl:        3
         AppContexts:       11           Activities:        7
              Assets:        5        AssetManagers:        4
       Local Binders:       83        Proxy Binders:       40
       Parcel memory:      389         Parcel count:       26
    Death Recipients:        0      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:      339
  PAGECACHE_OVERFLOW:      178          MALLOC_SIZE:      117

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4      228             95      469/191/4  /data/user_de/0/com.android.settings/databases/search_index.db

横轴的几个重要参数:

  • Pss Total:按比例分配占用内存 (PSS) 总量,与procrank里的Pss含义一样。
  • Private Dirty: 进程私有的内存分配量,当进程退出,将有 Private Dirty大小的内存被系统回收。Private Dirty是已被修改而必须保持在 RAM 中的 RAM 页,相对应的Private Clean指RAM已从某个持久性文件(例如正在执行的代码)映射的 RAM 页,如果一段时间不用,可以移出分页。
  • Heap Size:进程当前可访问到的堆内存大小,包含了共享的其他进程内存。
  • Heap Alloc:进程当前已经占有的堆内存大小。

纵轴的几个重要参数:

  • Unknown:系统无法将其分类到其他更具体的一个项中的内存,当Unknown指持续增大,且不会明显回落,很可能是native的内存泄漏了。
  • AppContexts & Activities:进程中当前活动的应用 Context 和 Activity 对象数量,它们不断增长则表示有内存泄漏。

proc节点下隐藏的内存信息

/proc/[pid]节点下的内容包含了该进程更为详细的信息。不过查看它需要root权限。事实上不光内存信息,有关该进程的各种信息基本都能在该节点下找到。

# cd /proc/26426
# ls
attr   clear_refs coredump_filter exe    limits       maps      mounts     ns        oom_score_adj reclaim   sessionid stat   syscall 
auxv   cmdline    cwd             fd     loginuid     mem       mountstats oom_adj   pagemap       root      smaps     statm  task    
cgroup comm       environ         fdinfo make-it-fail mountinfo net        oom_score personality   schedstat stack     status wchan

以上展现了进程26426的相关信息,它们都被记录在对应的文件节点上。分析内存问题主要看maps节点,它会记录26426进程的详细内存映射关系

# cat maps |head 
12c00000-12e09000 ---p 00000000 00:04 12528      /dev/ashmem/dalvik-main space (deleted)
12e09000-1ec00000 ---p 00209000 00:04 12528      /dev/ashmem/dalvik-main space (deleted)
32c00000-33844000 rw-p 00000000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
33844000-33a01000 ---p 00c44000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
33a01000-3ec00000 rw-p 00e01000 00:04 12529      /dev/ashmem/dalvik-main space 1 (deleted)
70e4b000-7158e000 rw-p 00000000 103:0c 497764    /data/dalvik-cache/arm/system@framework@boot.art
7158e000-733f1000 r--p 00000000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f1000-733f2000 r-xp 01e63000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f2000-733f3000 r--p 01e64000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat
733f3000-733f4000 rw-p 01e65000 103:0c 497763    /data/dalvik-cache/arm/system@framework@boot.oat

procmem获取到的信息就跟maps节点记录的一样,maps更加详细的标示出了内存分配的起至位置。

参考链接

http://stevevallay.github.io/blog/2014/11/17/memory-leak/
https://androidzhibinw.github.io/android/app/startup/activity/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/%E5%90%AF%E5%8A%A8/%E5%88%86%E6%9E%90/2015/09/21/android-app-startup-process/
http://shooting.logdown.com/posts/318965-android-memory-allocation
https://developer.android.com/studio/profile/investigate-ram.html?hl=zh-cn

作者:azhengye 发表于2017/7/30 22:41:35 原文链接
阅读:236 评论:0 查看评论

Android系统服务(一)解析ActivityManagerService(AMS)

$
0
0

相关文章
Android系统启动流程系列
Android应用进程系列
Android深入四大组件系列

前言

此前在Android系统启动流程、应用进程以及深入四大组件这三个系列文章中,都提及到了AMS,但都没有系统的来讲解它,本文就以AMS为主来进行讲解,其中会有一些知识点与这些系列文章有所重合,这里会尽量做到详尽讲解。阅读此文章前,最好阅读相关文章中列出的系列文章,否则我不敢保证这篇文章你能看的懂。

1.概述

AMS是系统的引导服务,应用进程的启动、切换和调度、四大组件的启动和管理都需要AMS的支持。从这里可以看出AMS的功能会十分的繁多,当然它并不是一个类承担这个重责,它有一些关联类,这在文章后面会讲到。AMS的涉及的知识点非常多,这篇文章主要会讲解AMS的以下几个知识点:

  • AMS的启动流程。
  • AMS与进程启动。
  • AMS家族。

2.AMS的启动流程

AMS的启动是在SyetemServer进程中启动的,在Android系统启动流程(三)解析SyetemServer进程启动过程这篇文章中提及过,这里从SyetemServer的main方法开始讲起:
frameworks/base/services/java/com/android/server/SystemServer.java

public static void main(String[] args) {
       new SystemServer().run();
   }

main方法中只调用了SystemServer的run方法,如下所示。
frameworks/base/services/java/com/android/server/SystemServer.java

private void run() {
       ...
           System.loadLibrary("android_servers");//1
       ...
           mSystemServiceManager = new SystemServiceManager(mSystemContext);//2
           LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
       ...    
        try {
           Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartServices");
           startBootstrapServices();//3
           startCoreServices();//4
           startOtherServices();//5
       } catch (Throwable ex) {
           Slog.e("System", "******************************************");
           Slog.e("System", "************ Failure starting system services", ex);
           throw ex;
       } finally {
           Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
       }
       ...
   }

在注释1处加载了动态库libandroid_servers.so。接下来在注释2处创建SystemServiceManager,它会对系统的服务进行创建、启动和生命周期管理。在注释3中的startBootstrapServices方法中用SystemServiceManager启动了ActivityManagerService、PowerManagerService、PackageManagerService等服务。在注释4处的startCoreServices方法中则启动了BatteryService、UsageStatsService和WebViewUpdateService。注释5处的startOtherServices方法中启动了CameraService、AlarmManagerService、VrManagerService等服务。这些服务的父类均为SystemService。从注释3、4、5的方法可以看出,官方把系统服务分为了三种类型,分别是引导服务、核心服务和其他服务,其中其他服务是一些非紧要和一些不需要立即启动的服务。系统服务总共大约有80多个,我们主要来查看引导服务AMS是如何启动的,注释3处的startBootstrapServices方法如下所示。

frameworks/base/services/java/com/android/server/SystemServer.java

   private void startBootstrapServices() {
        Installer installer = mSystemServiceManager.startService(Installer.class);
        // Activity manager runs the show.
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();//1
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);
      ...
    }

在注释1处调用了SystemServiceManager的startService方法,方法的参数是ActivityManagerService.Lifecycle.class:
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

  @SuppressWarnings("unchecked")
    public <T extends SystemService> T startService(Class<T> serviceClass) {
        try {
           ...
            final T service;
            try {
                Constructor<T> constructor = serviceClass.getConstructor(Context.class);//1
                service = constructor.newInstance(mContext);//2
            } catch (InstantiationException ex) {
              ...
            }
            // Register it.
            mServices.add(service);//3
            // Start it.
            try {
                service.onStart();//4
            } catch (RuntimeException ex) {
                throw new RuntimeException("Failed to start service " + name
                        + ": onStart threw an exception", ex);
            }
            return service;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

startService方法传入的参数是Lifecycle.class,Lifecycle继承自SystemService。首先,通过反射来创建Lifecycle实例,注释1处得到传进来的Lifecycle的构造器constructor,在注释2处调用constructor的newInstance方法来创建Lifecycle类型的service对象。接着在注释3处将刚创建的service添加到ArrayList类型的mServices对象中来完成注册。最后在注释4处调用service的onStart方法来启动service,并返回该service。Lifecycle是AMS的内部类,代码如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   public static final class Lifecycle extends SystemService {
        private final ActivityManagerService mService;
        public Lifecycle(Context context) {
            super(context);
            mService = new ActivityManagerService(context);//1
        }
        @Override
        public void onStart() {
            mService.start();//2
        }
        public ActivityManagerService getService() {
            return mService;//3
        }
    }

上面的代码结合SystemServiceManager的startService方法来分析,当通过反射来创建Lifecycle实例时,会调用注释1处的方法创建AMS实例,当调用Lifecycle类型的service的onStart方法时,实际上是调用了注释2处AMS的start方法。在SystemServer的startBootstrapServices方法的注释1处,调用了如下代码:

 mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();

我们知道SystemServiceManager的startService方法最终会返回Lifecycle类型的对象,紧接着又调用了Lifecycle的getService方法,这个方法会返回AMS类型的mService对象,见注释3处,这样AMS实例就会被创建并且返回。

3.AMS与进程启动

Android系统启动流程(二)解析Zygote进程启动过程这篇文章中,我提到了Zygote的Java框架层中,会创建一个Server端的Socket,这个Socket用来等待AMS来请求Zygote来创建新的应用程序进程。要启动一个应用程序,首先要保证这个应用程序所需要的应用程序进程已经被启动。AMS在启动应用程序时会检查这个应用程序需要的应用程序进程是否存在,不存在就会请求Zygote进程将需要的应用程序进程启动。Service的启动过程中会调用ActiveServices的bringUpServiceLocked方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

  private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
  ...
  final String procName = r.processName;//1
  ProcessRecord app;
  if (!isolated) {
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);//2
            if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                        + " app=" + app);
            if (app != null && app.thread != null) {//3
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode,
                    mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg);//4
                    return null;
                } catch (TransactionTooLargeException e) {
              ...
            }
        } else {
            app = r.isolatedProc;
        }
 if (app == null && !permissionsReviewRequired) {//5
            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                    "service", r.name, false, isolated, false)) == null) {//6
              ...
            }
            if (isolated) {
                r.isolatedProc = app;
            }
        }
 ...     
}

在注释1处得到ServiceRecord的processName的值赋值给procName ,其中ServiceRecord用来描述Service的android:process属性。注释2处将procName和Service的uid传入到AMS的getProcessRecordLocked方法中,来查询是否存在一个与Service对应的ProcessRecord类型的对象app,ProcessRecord主要用来记录运行的应用程序进程的信息。注释5处判断Service对应的app为null则说明用来运行Service的应用程序进程不存在,则调用注释6处的AMS的startProcessLocked方法来创建对应的应用程序进程,
具体的过程请查看Android应用程序进程启动过程(前篇)

4.AMS家族

ActivityManager是一个和AMS相关联的类,它主要对运行中的Activity进行管理,这些管理工作并不是由ActivityManager来处理的,而是交由AMS来处理,ActivityManager中的方法会通过ActivityManagerNative(以后简称AMN)的getDefault方法来得到ActivityManagerProxy(以后简称AMP),通过AMP就可以和AMN进行通信,而AMN是一个抽象类,它会将功能交由它的子类AMS来处理,因此,AMP就是AMS的代理类。AMS作为系统核心服务,很多API是不会暴露给ActivityManager的,因此ActivityManager并不算是AMS家族一份子。
为了讲解AMS家族,这里拿Activity的启动过程举例,Activity的启动过程中会调用Instrumentation的execStartActivity方法,如下所示。
frameworks/base/core/java/android/app/Instrumentation.java

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
      ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

execStartActivity方法中会调用AMN的getDefault来获取AMS的代理类AMP。接着调用了AMP的startActivity方法,先来查看AMN的getDefault方法做了什么,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

 static public IActivityManager getDefault() {
        return gDefault.get();
    }
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");//1
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);//2
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }+
    };
}

getDefault方法调用了gDefault的get方法,我们接着往下看,gDefault 是一个Singleton类。注释1处得到名为”activity”的Service引用,也就是IBinder类型的AMS的引用。接着在注释2处将它封装成AMP类型对象,并将它保存到gDefault中,此后调用AMN的getDefault方法就会直接获得AMS的代理对象AMP。注释2处的asInterface方法如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }
    return new ActivityManagerProxy(obj);
}

asInterface方法的主要作用就是将IBinder类型的AMS引用封装成AMP,AMP的构造方法如下所示。

frameworks/base/core/java/android/app/ActivityManagerNative.java

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }
...
 }

AMP的构造方法中将AMS的引用赋值给变量mRemote ,这样在AMP中就可以使用AMS了。
其中IActivityManager是一个接口,AMN和AMP都实现了这个接口,用于实现代理模式和Binder通信。
再回到Instrumentation的execStartActivity方法,来查看AMP的startActivity方法,AMP是AMN的内部类,代码如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
           String resolvedType, IBinder resultTo, String resultWho, int requestCode,
           int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
     ...
       data.writeInt(requestCode);
       data.writeInt(startFlags);
     ...
       mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);//1
       reply.readException();+
       int result = reply.readInt();
       reply.recycle();
       data.recycle();
       return result;
   }

首先会将传入的参数写入到Parcel类型的data中。在注释1处,通过IBinder类型对象mRemote(AMS的引用)向服务端的AMS发送一个START_ACTIVITY_TRANSACTION类型的进程间通信请求。那么服务端AMS就会从Binder线程池中读取我们客户端发来的数据,最终会调用AMN的onTransact方法,如下所示。
frameworks/base/core/java/android/app/ActivityManagerNative.java

   @Override
   public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
           throws RemoteException {
       switch (code) {
       case START_ACTIVITY_TRANSACTION:
       {
       ...
           int result = startActivity(app, callingPackage, intent, resolvedType,
                   resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
           reply.writeNoException();
           reply.writeInt(result);
           return true;
       }
   }

onTransact中会调用AMS的startActivity方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Override
 public final int startActivity(IApplicationThread caller, String callingPackage,
         Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
         int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
     return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
             resultWho, requestCode, startFlags, profilerInfo, bOptions,
             UserHandle.getCallingUserId());
 }

startActivity方法会最后return startActivityAsUser方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Override
 public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
         Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
         int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
     enforceNotIsolatedCaller("startActivity");
     userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
             userId, false, ALLOW_FULL_ONLY, "startActivity", null);
     return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
             resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
             profilerInfo, null, null, bOptions, false, userId, null, null);
  }           

startActivityAsUser方法最后会return ActivityStarter的startActivityMayWait方法,这一调用过程已经脱离了本节要讲的AMS家族,因此这里不做介绍了,具体的调用过程可以查看Android深入四大组件(一)应用程序启动过程(后篇)这篇文章。

在Activity的启动过程中提到了AMP、AMN和AMS,它们共同组成了AMS家族的主要部分,如下图所示。

AMS家族(7).png

AMP是AMN的内部类,它们都实现了IActivityManager接口,这样它们就可以实现代理模式,具体来讲是远程代理:AMP和AMN是运行在两个进程的,AMP是Client端,AMN则是Server端,而Server端中具体的功能都是由AMN的子类AMS来实现的,因此,AMP就是AMS在Client端的代理类。AMN又实现了Binder类,这样AMP可以和AMS就可以通过Binder来进行进程间通信。

ActivityManager通过AMN的getDefault方法得到AMP,通过AMP就可以和AMN进行通信,也就是间接的与AMS进行通信。除了ActivityManager,其他想要与AMS进行通信的类都需要通过AMP,如下图所示。

参考资料
[深入理解Android卷二 全文-第六章]深入理解ActivityManagerService
Framework源码分析(一):ActivityManagerService
ActivityManager与Proxy模式的运用

作者:itachi85 发表于2017/7/31 0:09:44 原文链接
阅读:452 评论:1 查看评论

一起Talk Android吧(第三十二回:Android中的Activity四)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,介绍完Activity的状态后,我们在这一回中重点对Activity生命周期图形中的箭头进行分析,就是说让图形随着箭头动起来。

我们沿着Activity生命周期从开始到结束这个过程来介绍。首先Activity是没有的,从onCreate方法开始执行后,它会初始化Activity,接着箭头从onCreate流动到onStart。这个时候Activity完成了初始化过程,并且即将以画面的形式呈现在我们面前,等到onStrat方法执行完成后,我们就可以看到Activity的真面目了。

这时箭头从onStart方法流向onResume方法,Activity不但可以被看到,而且还能响应用户对它的操作,比如你可以滑动它或者点击它上面的各个控件,它会很快地做出相应的响应。

onResume方法执行完成后,箭头流向了onPause方法,这个时候Activity还能被看到,不过它不会响应用户的操作,我们称Activity此时正在睡觉,你喊它名字他都听不到,哈哈,看来它是困了呀。

接着箭头流向了onStop方法,此时的Activity进入后台,我们已经看不到它了,因此我们称Activity潜水了,或者说隐身了也是合适的,总之它已经从我们的视野中消失的无影无踪。

Activity虽然消失了,不过它还在系统中占用着资源,此时箭头流向了onDestroy方法,访方法会释放Activity使用的资源,它相当于把系统的资源从隐身的Activity手中收回来,以后就可以给其它Activity使用了。

这时有看官问,还有其它几个箭头没有介绍呢,看官莫急我们在下一回中会给大家介绍的。请放心。

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 7:27:16 原文链接
阅读:248 评论:0 查看评论

Android逆向之旅---Native层的Hook神器Cydia Substrate使用详解

$
0
0

一、前言

在之前已经介绍过了Android中一款hook神器Xposed,那个框架使用非常简单,方法也就那几个,其实最主要的是我们如何找到一个想要hook的应用的那个突破点。需要逆向分析app即可。不了解Xposed框架的同学可以查看:Android中hook神器Xposed使用详解;关于hook使用以及原理不多解释了。今天我们再来看另外一个hook神器Cydia Substrate,关于这个神器网上也已经介绍了,应该有的同学已经使用过了,因为最近在破解一个游戏,奈何想hook他的一个so中的一个方法,Xposed几乎没法用,所以得操刀这个框架了,所以就单独抽出来介绍这个框架。他的一个优点就在于Hook底层方法非常方便,对so中的方法hook操作非常便捷。


二、环境搭建

下面就来介绍这个框架如何安装使用,本文会介绍这个框架如何hook Java层和Native层功能,首先我们来看一下如何安装这个框架,本人操作的环境:

设备系统:小米三+原生CM 4.4系统

框架版本:0.9.4010

是否root:必须root

关于这个环境,可能有的同学操作最大的问题就在于设备和系统,不同设备不同系统,这个框架或许安装会失败,具体问题可能需要你们自己去解决了。关于框架apk和功能jar包下载地址可以去官网:http://www.cydiasubstrate.com


三、Hook Java层功能

搭建好了环境,下面就直接操作了,首先来看看如何Hook Java层功能

第一步:导入jar包



第二步:编写hook入口类


具体api这里不多介绍了,就那么几个,没必要详细介绍,这里对系统的imei进行hook操作了。网上很多人都对系统颜色值进行了hook,这里也顺带操作一下:



第三步:配置xml信息


在AndroidManifest.xml中需要配置两个地方,一个是使用权限,一个是声明hook的入口类即可。


第四步:安装运行

代码编写完成之后,直接运行安装即可,前提是你需要正确安装Cydia框架apk,安装成功界面如下:


然后我们安装hook工程apk,会出现这个提示:


点击,进入框架界面,点击重启即可。然后我们查看系统界面颜色以及返回的imei值:

  

查看颜色的确变成骚气的粉色了,再看看imei值的修改:


imei值也成功的hook成功了。到这里我们就用Substrate框架hook了Java层功能。当然这些功能Xposed也是可以做到的哦。


四、Hook Native层功能

那么下面继续来看如何hook native层的功能,也是本文的重点哦。

第一步:创建一个Native工程


这里用Eclipse操作,简单便捷,有很多人问我为什么不用AS,我想说在我心中Eclipse最好用,AS真心好丑,不想用而已。


第二步:导入Substrate的native功能包

上图可以看到,需要导入一个substrate.h头文件,和两个so功能包。native层应用都是这么干的,提供一个头文件告诉你api,具体实现在so包中。


第三步:寻找hook的函数名

这里网上没有好的hook代码,这里我们为了更好的查看这个工具的牛逼之处,弄一个比较实际的案例就是hook系统加载dex的函数,这样我们就可以获取到每个应用的dex文件了,这种方式对于早期加固是一个比较好的脱壳方案。在之前介绍脱壳我们会使用IDA在指定函数处下个断点,那么我们这里如果要hook的话,就需要找到这个加载dex的函数名称,这里一定要记的是导出的函数名,首先我们导出设备的libdvm.so文件:system/lib/libdvm.so


然后使用IDA打开,寻找加载dex函数:


切换到Exports视图页面,然后搜索dexFileParse函数,点进去:


看到了,我们需要得到的是EXPORT的函数名,需要hook的是他,这个一定要注意,不然hook没效果的。找到函数之后还得获悉函数的参数类型和返回类型,这个也好办,因为我们有Android源码,所以直接在源码中找这个函数参数说明已经返回值说明即可。因为Native层hook的其实是函数指针的替换,所以如果想hook原来的函数,必须新建一个和原来一样的函数功能,然后传递函数指针即可。这个函数的参数和返回值定义如下:

DexFile* dexFileParse(const unsigned __int8 *, unsigned int, int);

参数含义非常简单,第一个参数表示dex文件的起始地址,第二个参数是dex文件的长度,有这两个参数我们就可以写入文件了。这里我们需要获取DexFile类型,这个直接在Android源码目录下找到这个头文件DexFile.h即可。然后导入工程中。这样我们就找到了需要hook的函数已经说明了,下面就开始编写hook代码了。


第四步:编写hook代码

在编写hook代码之前,我们需要考虑这几件事:

第一件事:我们hook之后的dex存在哪?怎么存?我们这里直接通过当前的pid值获取进程名,然后将其凭借作为dex的文件名,这样每个进程的dex文件就不会冲突了。这里要理解一点:一个进程对应一个DVM,加载一个dex文件。所以这里hook其实就是注入每个进程,在每个进程中在hook每个函数功能。

第二件事:需要过滤系统进程,并不是所有的进程都是我们想要hook的,而且这些进程未必有dex文件,比如鼻祖进程zygote,而这些进程过滤规则,需要我们自己打印看结果。然后构造。

下面开始写代码了,首先定义我们想要hook的so文件:

MSConfig(MSFilterLibrary, "/system/lib/libdvm.so");

主要是第二个参数,是需要hook的so路径。然后在入口处开始hook代码:


这里首先找到so中需要hook函数符号,然后直接调用MSHookFunction传入符号,新函数地址,旧函数地址即可。这里可以看到在C中指针是多么强大,实现了函数的回调机制,而且非常方便。然后继续来看新定义的hook函数功能:


这里先获取当前进程名称,然后构造dex文件名,保存dex文件,最后一定要记得返回原始的函数,不能影响正常的流程。这里还要记得过滤规则,不要对每个进程都进行操作,并不是每个进程都是有效的。而这些过滤规则是根据自己打印进程名来自行添加即可。


第五步:编写MK文件

上面代码已经编写完成了,下面来编写编译脚本吧,主要注意编译之后的文件名一定要有cy结尾,不然是hook失败的,然后就是需要导入substrate的so库文件:



第六步:安装并运行

和之前一样,运行之后,需要重启设备,然后先看看native层的log信息:


然后再去目录中查看保存的dex文件信息:


dex文件都保存成功了,这样会发现如果对于早期的加壳,可以采用这种方式进行脱壳操作的。也不需要用IDA进行调试dump出dex文件了。


五、说明

关于native层hook就介绍完了,这里还是需要说明几点:

第一点:hook之前需要分析so获取需要hook的函数名称,参数返回值定义,这个和hook Java层一样,必须先找到突破点才能进行下一步。

第二点:hook可能会有一些错误,因为是native层比java层错误信息难发现,所以最好是在某些地方加一些日志观察结果。

如果在使用过程中发现hook失败,注意检查这几个条件:

第一个:xml中是否配置了权限和入口

第二个:编译脚本MK中的后缀名是否为cy


项目下载地址:https://github.com/fourbrother/CydiaSubstrateHook


六、总结

关于CydiaSubstrate框架就介绍到这里了,后面会分析如何hook游戏的so文件来进行破解工作,有了这个框架再也不怕hook难了,native层代码也可以一览无余了。


更多内容:点击这里

关注微信公众号,最新技术干货实时推送

编码美丽技术圈
微信扫一扫进入我的"技术圈"世界

扫一扫加小编微信
添加时请注明:“编码美丽”非常感谢!

作者:jiangwei0910410003 发表于2017/7/31 8:36:41 原文链接
阅读:802 评论:1 查看评论

Android6.0之后的权限机制对App开发的影响

$
0
0
随着Android系统的更新换代,每次重大更新的方面也逐步扩展,从4.*主要是增强功能,到5.*主要是美化界面,到6.*主要提高系统安全性,再到7.*和8.*主要支撑各种大屏设备,因此开发者需要对每个大版本的Android重新进行适配。其中6.*主要影响开发工作的升级包括权限管理和休眠模式。

对于权限管理,原本开发者只要在AndroidManifest.xml中声明相关权限,App安装完成之后即可默认获得这些权限。但是6.0引入了新的运行时权限管理机制,即使开发者实现已经声明App的权限,Android在App初次启动之时,仍会提示用户是否允许该App开启相关功能。倘若用户不同意App获得某些权限,毫无疑问App在运行过程中就可能无法正常工作。

对于休眠模式,即当手机屏幕关闭的时候,系统会自动进入休眠模式,这样原本正在运行的App将进入挂起模式,不能再进行访问网络等常用操作。当然为了保证App不被完全挂死,系统也会定时退出休眠模式,好比青蛙从冬眠之中苏醒过来,在苏醒期间,系统允许挂起的App重新恢复运行,继续先前设定好的任务。可是这个苏醒期是短暂的(通常只有几秒),一旦苏醒期结束,系统又重新进入休眠模式,于是那些App再次挂起,等待下次苏醒期的到来,如此往复。当然,只要手机恢复亮屏,比如用户按下电源键、用户给手机插上电源、手机接到来电等等,系统便自动退出休眠模式,所有挂起的App都会恢复正常运转。

下面逐个说明一下Android6.0的权限管理和休眠模式给App开发带来的影响,注意这些影响可对照《Android Studio开发实战:从零基础到App上线》一书的相应章节:
1、App的SD卡访问权限可能会被用户关闭,导致App无法正常读写SD卡。这点影响《Android Studio开发实战:从零基础到App上线》一书第4章的“4.3 SD卡文件操作”和“4.5 实战项目:购物车”。手机上查看App是否开启存储卡访问功能的界面如下图所示:


2、手机在休眠期间,原本在系统闹钟服务AlarmManager中设定好的定时任务,即使定时的时刻到达,也要等到苏醒期间才会得到执行。如果一定要在休眠期唤醒闹钟,就得调用setAndAllowWhileIdle代替set方法,或者调用setExactAndAllowWhileIdle代替setExact方法。
这点影响《Android Studio开发实战:从零基础到App上线》一书第5章的“5.5.2 定时器AlarmManager”和“5.6 实战项目:日历/日程表”。

3、Android6.0之后,App如果通过jni接口调用了so库,则这个so库必须把相关库编译进去,因为系统将不允许App访问系统库。具体到编码工作,只能在build.gradle中采用externalNativeBuild方式编译jni工程,即在jni目录下提供专门的Android.mk编译文件,由该mk文件配置jni文件的编译规则;倘若在build.gradle的defaultConfig节点下补充ndk的编译说明,则编译出来的so文件无法在Android6.0上正常调用。这点影响《Android Studio开发实战:从零基础到App上线》一书第14章的“14.2 JNI开发”和“14.4 实战项目:WIFI共享器”。

4、Android6.0对蓝牙功能的校验也变严格了,即使已经在AndroidManifest.xml声明蓝牙的相关权限如BLUETOOTH_ADMIN和BLUETOOTH,App也不能正常检测到附近的蓝牙设备。解决办法是在AndroidManifest.xml中增加下面两个权限声明:
    <!-- Android6.0 蓝牙搜索不到设备,需要补充下面两个权限 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
这点影响《Android Studio开发实战:从零基础到App上线》一书第14章的“14.3.2 蓝牙BlueTooth”。



点此查看Android开发笔记的完整目录



作者:aqi00 发表于2017/7/31 9:53:34 原文链接
阅读:182 评论:0 查看评论

android应用开发-从设计到实现 4-5资源的导入

$
0
0

资源的导入

在前面讲解产品原型设计的章节中,我们已经创建出了各种开发时需要使用的各种图标了,包括应用图标、系统图标等等。

图标导入

现在,我们先把开发过程中要使用到的图片资源添加到Android Studio工程目录当中。

  1. Sketch创建的静态高保真原型中的图标,根据导出的尺寸,将它们分类整理到对应的文件夹当中,

     4_5_drawable_resource_scale
  2. 打开天气预报所在的Android Studio的工程文件夹-app -> src -> main -> res,将图片替换到对应的mipmap目录当中;

     import_icons
  3. 重新部署应用到设备上,桌面上的天气预报应用图标,已经从以前的安卓机器人,变成了新的图标了,

     app_icon_changed

版本管理

我们新增了约90个图片资源,需要将它们加入到Git的版本控制之中。

  1. 查看Version Control: -> Local Changes -> browse;
  2. 在弹出的对话框中,选中需要加入版本控制的90个图片文件,点击左边的+

     icon_resources_add_to_git
  3. 需要管理的文件被添加到了stage当中;

     icon_resources_add_to_git_2
  4. 之后再把这些改动,按照上一张讲到提交到本地库和远程GitHub当中的方式进行操作就可以了;

     icon_resources_commit

图片的使用

对于那些导入的图片资源,Android Studio会做进一步处理,自动生成一个R.java文件,文件中给每一张图片生成一个对应的id值。

public final class R {

    public static final class mipmap {
        public static final int ic_air_quality=0x7f030000;
        public static final int ic_cloudy_l=0x7f030001;
        public static final int ic_cloudy_s=0x7f030002;
        public static final int ic_fog_l=0x7f030003;
        public static final int ic_fog_s=0x7f030004;
        public static final int ic_humidity_level=0x7f030005;
        public static final int ic_launcher=0x7f030006;
        public static final int ic_rainy_l=0x7f030007;
        public static final int ic_rainy_s=0x7f030008;
        public static final int ic_snow_l=0x7f030009;
        public static final int ic_snow_s=0x7f03000a;
        public static final int ic_sport_level=0x7f03000b;
        public static final int ic_sunny_cloudy_l=0x7f03000c;
        public static final int ic_sunny_cloudy_s=0x7f03000d;
        public static final int ic_sunny_l=0x7f03000e;
        public static final int ic_sunny_s=0x7f03000f;
        public static final int ic_ultraviolet_level=0x7f030010;
        public static final int ic_wind_direction=0x7f030011;
        public static final int ic_wind_level=0x7f030012;
    }

}

当我们要使用对应资源的时候,直接使用java代码

int id = R.mipmap.ic_launcher;

就可以了。

通过这种方式,完成了资源代码的转换。

资源的条件命名法

图片资源文件夹的命名方式很有特点,在主名字之后,使用-间隔了一些扩展名字。例如,

mipmap-hdpi mipmap-xhdpi

目录名用“-”增加了限制条件。也就是给目录里资源的使用增加了约束。

这里用mipmap-hdpi文件夹举例说明:如果当前的设备属于hdpi类型的屏幕,那么在使用图片的时候,系统优先使用其中的图片。

除了上面的目录结构,有时你还会看到如下的目录形式:

  1. mipmap-hdpi:给属于hdpi的设备使用;
  2. values-zh:给当前语言是中文的系统使用;
  3. layout-land:给处于横屏状态的系统使用;

图片的优化

屏幕密度选择性匹配

并不是说一定要为所有的屏幕密度做对应图片尺寸的匹配,因为如果做全部匹配会让程序的体积快速增大。

大多数时候,我们会选择先做高清晰度的尺寸,例如xxhdpi。其他屏幕密度的设备需要使用资源时,就会把xxhdpi中的资源,做适度的缩放,以达到匹配的效果。

对于个别系统缩放也达不到显示要求的图片,才做针对不同屏幕密度的配套图片。

我们的天气预报把所有尺寸的屏幕密度都做了适配。

矢量图标

另外,对于很多系统图标,我们也可以不使用png图片,而采用矢量图,让安卓系统绘制图标。这样一来,程序的体积可以跟小,显示效果也更为出众。

不过大家初次接触安卓开发,为了减少不必要的枝节,我就隐去了这部分内容,将来再和大家详细的讲述。


本文是《从设计到实现-手把手教你做android应用开发》系列文档中的一篇。感谢您的阅读和反馈,对本文有任何的意见和建议请留言,我都会尽量一一回复。

如果您觉得本文对你有帮助,请推荐给更多的朋友;或者加入我们的QQ群348702074和更多的小伙伴一起讨论;也希望大家能给我出出主意,让这些文档能讲的更好,能最大化的帮助到希望学习开发的伙伴们。

除了CSDN发布的文章,本系列最新的文章将会首先发布到我的专属博客book.anddle.com。大家可以去那里先睹为快。


同时也欢迎您光顾我们在淘宝的网店安豆的杂货铺。店中的积木可以搭配成智能LED灯,相关的配套文档也可以在这里看到。

这些相关硬件都由我们为您把关购买,为大家节省选择的精力与时间。同时也感谢大家对我们这些码农的支持。

最后再次感谢各位读者对安豆的支持,谢谢:)

作者:anddlecn 发表于2017/7/31 10:46:53 原文链接
阅读:515 评论:3 查看评论

解决ListView显示不全、滑动冲突问题

$
0
0

    在开发中,ListView是使用得最多的控件,有时候会遇到ListView因为各种原因引起的数据显示不全,例如,嵌套了ScrollView等。这个解决的办法很多,这里记录一下我自己用过的方法,本人亲测有效。
第一种:
/**
* 设置listview高度的方法
* @param listView
*/
public void setListViewHeight(ListView listView) {
//获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {//listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView); //获得每个子item的视图
listItem.measure(0, 0); //先判断写入的widthMeasureSpec和heightMeasureSpec是否和当前的值相等,如果不等,重新调用onMeasure(),计算子项View 的宽高
totalHeight += listItem.getMeasuredHeight(); //累加不解释,统计所有子项的总高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); //加上每个item之间的距离,listView.getDividerHeight()获取子项间分隔符占用的高度
listView.setLayoutParams(params);//params.height最后得到整个ListView完整显示需要的高度
}
这个方法需要在ListView.setAdapter(adapter)之后调用,这样就可以把数据显示完全了,这个方法可以写到工具类里面,然后直接调用。

第二种方法:
/*
* 設置listview的高度結局滑動衝突
*
* @param statisticsTO
*/
public void setListvHeight(StatisticsTO statisticsTO) {
int size = statisticsTO.getData().size();
int totalHeight = 0;
for (int i = 0; i < size; i++) {
View listItem = myAdapter.getView(i, null,
liststatistics);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = liststatistics.getLayoutParams();
params.height = totalHeight
+ (liststatistics.getDividerHeight() * (size - 1));
liststatistics.setLayoutParams(params);
}
这种方法其实和第一种方法是类似的,只不过这里传入的是数据的实体。

第三种方法:
第三种方法就是自定义ListView,然后重写onMeasure方法,这个可以解决ListView的滑动冲突,百度上说的最多的就是这种方法。
public class CustomListView extends ListView{
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, spec);
}
}

转载自 :http://blog.csdn.net/u014727709/article/details/76423318
欢迎start,欢迎评论,欢迎指正

作者:u014727709 发表于2017/7/31 11:40:43 原文链接
阅读:194 评论:0 查看评论

每日一文简单版

$
0
0

1. 简介

前段时间学习Kotlin,准备找个东西下手,复杂的内容暂时也处理不了,所以选了“每日一文”的接口作为测试,本以为内容比较少,很容易实现,最后发现,还是有一些功能不是很好处理,所以到目前为止还只是一个半成品,后面有空了再来改善吧。现在只是为了练习Kotlin。


2. 内容

  • Kotlin基本语法
  • anko-common使用
  • anko-sqlite使用
  • Navigation+DrawerLayout实现左右抽屉菜单
  • Retrofit + Okhttp + Rxjava2 + Gson 处理网络请求
  • Material Design

3. 思路

启用开发者模式,打开“显示边界布局”,你可以看到应用唯一的一个主界面采用是一体的,没有控件的分离,猜测采用的是混合式开发,所以呢,对我们没有什么帮助。只能按照自己的方式,实现它的功能。毕竟是为了熟悉语言,不深究。


4. 依赖库

    ext.kotlin_version = '1.1.2-5'
    ext.anko_version = '0.10.0'
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.anko:anko-common:$anko_version"
    compile "org.jetbrains.anko:anko-sqlite:$anko_version"
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.android.support:support-v13:25.3.1'
    compile 'com.google.code.gson:gson:2.7'
    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.x.y'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.android.support:support-vector-drawable:25.3.1'
    compile 'info.hoang8f:android-segmented:1.0.6'
    compile 'com.kyleduo.switchbutton:library:1.4.6'

5. 效果

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述


6. Github

OneArticle


7. 备注

每日一文接口采用非正式方式获取,仅供学习,若有侵权请告知,即刻删除。

作者:poorkick 发表于2017/7/31 13:48:23 原文链接
阅读:213 评论:0 查看评论

Android 和 Dagger 2 中的依赖注入

$
0
0

原文:Dependency Injection in Android with Dagger 2
作者:Joe Howard
译者:kmyhy

在现代开发团队中到处充斥着“你一定要用依赖注入”的叫嚣。依赖注入(简称 DI)变成了一个威风十足的名字,足以让每个开发者都胆战心惊。

无处不在的依赖注入和它的名字一样复杂,它是一个重要的、可维护和可测试的软件构建工具。此外,通过依赖注入你可以极大地简化你的代码并允许用一种更简单的方式编写可测试的代码。

在这篇教程中,我们会将一个现成的 app “DeezFoods” 升级为依赖注入。这个 app 显示一张食品列表(来自 USDA 食品成分数据库)。用户可以点击一种食品查看它的糖分,然后对这个糖分含量点赞或或者拍砖。

这个 app 使用常见的 Android 库和设计模式,比如 Retrofi 和 MVP。我们会用流行的 Java/Android 依赖注入框架 Dragger 2 来进行依赖注入。

下图是 DeezFoodz 的详情页:

在此之前,请到 USDA 网站(前面的链接)获得访问权限。你需要获得它的 API key,否则无法在 app 中使用它。

介绍

到底什么是依赖?任何重要的软件程序都会包含多个组件,这些组件之间会来回调用并传递数据。

例如,在使用面向对象语言(比如 Android 中的 Java)时,对象会调用其引用的其它对象的方法。简单的依赖就是一个对象依赖于其它对象的具体实现。

具体到 Java 代码中,我们可以识别出自己代码中的依赖,比如你在一个对象中使用 new 关键字创建一个新对象。这时,你唯一责任就是创建这个对象,并在创建时正确配置这个对象。例如,有一个类 A:

public class A {
  private B b;
  public A() {
    b = new B();
  }
}

A 对象在构造函数中创建了变量 b。A 对象完全依赖了 B 的具体实现,为了能够正确调用到 b 的属性,我们配置了 b 变量。

这表明了 A 类对 B 类是耦合的或者是依赖的。如果 B 对象的创建比较复杂,则 A 类中也会得到体现。任何对 B 对象的配置的改变则必然对 A 类造成影响。

如果 B 类依赖于 C,C 依赖于 D,所有的复杂性都会在代码库中传递,并导致 app 中组件的紧耦合。

依赖注入是一个术语,用于描述了这种松散耦合的描述。在这个例子中,我们只需要做一小点修改:

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}

好了——依赖注入出现了。比起在 A 的构造器中创建一个 b 对象,现在 b 对象是以注入的方式传递到 A 的构造器中。配置 b 的任务可以在任何地方进行,A 类只是简单地对 B 类进行消费。

依赖反转原则

依赖注入经常和面向对象设计中的 SOLID 五原则之一相提并论:即依赖反转原则。关于 SOLID 的介绍,尤其是针对 Android 系统,请参考“依赖反转的王国”。

简单来说,依赖反转原则是说,依赖于抽象而不要依赖于具体实现。

还是之前的例子,将 B 用 Java 的接口来替换 Java 类。这样,就可以将不同的实现了 B 接口的具体的 B 的类型传递给 A 的构造函数了。这会带来几个好处:

  • 可以用各种 B 对象来测试 A 类。
  • 在某种测试场景中,根据需要可以使用假的 B 对象。
  • 测试 A 并不会依赖于 B 的具体实现。

虽然经常二者并提,但依赖注入和依赖反转原则是不一样的。基本上,依赖注入是一种遵循了依赖反转原则的技术。

Dagger 2

在 Java 的世界,有许多用于简化依赖注入的使用的框架。这些框架减少了大量重复的代码,提供了在软件系统中使用依赖注入的机制。

早期有一个 Java 依赖注入框架,叫做 Guice,由 Google 所创建。后来 Square 团队开发了 Dagger 框架,主要针对于 Android 系统。

虽然 Dagger 总的来说很棒,但第一版的 Dagger 框架也有几个缺点。例如,由于运行时反射导致的性能问题,以及 ProGuard 的难用。

因此,Dagger 2 框架问世了,它生成的代码更小,通过在编译时进行注入解决了性能问题。

Dagger (短剑,匕首)这个名字来自于开发中依赖的本质。依赖发生在对象之间,比如 A、B、C …,导致一种所谓的“有向非循环图”的结构。Dagger 和 Dagger 2 用于降低 Java 和 Android 项目中此类图的出现。

在本文后面,Dagger 一次专指 Dagger 2.

理论课上完了!开始编写代码吧!

开始

这里下载开始项目。

用 Android Studio 打开开始项目。你需要使用 Android Studio 2.2.2 以上版本。低于此版本,或者提示你升级 gradle 版本,请照提示进行。

修改 app 包下面的 Constants.java 文件,将 API_KEY 替换成你自己的值。

在模拟器或设备上运行 app,确认你可以编译通过。

这个 app 有两个页面。第一个是一个来自 USDA 食品成分数据库的食品列表,点击任何单元格,显示详情页,它会列出该食品的糖分数据。

查看项目结构和类文件。你会看到 app 使用了 MVP 模型来搭建代码结构。还使用了 Square 的 Retrofit 2 库来作为连接 USDA API 的网络层。

main 包下面的子包分别是 app、model、network、ui。如果你查看 ui 包,你会看到这两个页面的子包。每个页面都有自己的 View 类和 present 类。

打开 app 的 build.gradle 文件,你会看到 app 用 Butterknife 进行视图绑定,和一个类似 Java 8 streams 的lightweight stream 库

MVP

如果你不熟悉 MVP 模式,你可以先阅读一些在线资源。

MVP 和其它结构模式类似,实现了关注分离,就像 MVC 和 MVVM。对于 Android 的 MVP 来说,你的 activity 和 fragment 就是典型的 view 对象,实现了 View 的界面,处理 app 于用户之间的交互。

view 将用户动作传递给 presenter,后者负责业务逻辑,和数据库存储交互,比如服务端 API 或者数据库。model 层则用于表示构成 app 内容的对象。

以 DeezFoodz 来说,FoodActivity 类是详情页,实现了 FoodView 接口。FoodActivity 有一个对 FoodPresenter 接口的引用,这个接口负责访问 Food 类型的模型对象。

对 Retrofit 2 的使用放在了 network 包下面的 UsdaApi 类中。这个类定义了 app 所要调用的 USDA API 接口。有两个 GET 类型的调用,一个获取一个 FoodzList 对象,另一个通过查询参数获取指定 Food 的详情。

注意:Retrofit 是一个强大的类型安全的 HTTP 客户端。它将 USDA REST API 重新暴露成 Java 接口。Retrofit 简化了进行同步异步 web 请求的工作,将响应数据转换成 POJO。更多内容,请看 Android 网络教程:开始

DeezFoodz 中的依赖

打开 ui.food 下面的 FoodActivity 类。在 onCreate() 方法, 这几句创建和配置了 FoodPresenter:

presenter = new FoodPresenterImpl();
presenter.setView(this);
presenter.getFood(foodId);

这里,我们创建了一个 FoodPresenter 的具体实现类。打开 FoodPresenterImpl.java 看一下 getFood()。Retrofit 对象和 UsdaApi 对象都在这个方法中以工厂方法的方式创建和配置。

这导致了对模型、视图和 presenter 层的紧耦合。针对这个 view,换一个假的 presenter 就必须修改 View 的代码。创建 Retrofit 对象和 UsdaApi 对象的代码在两个 presenter 中重复了:FoodPresenterImpl 和 FoodzPresenterImpl。

接下来,我们用 Dagger 实习对 DeezFoodz 进行依赖注入,移除不同层之间的耦合和重复代码。

最后,是写代码的时候了!

项目中 Dagger 2 的相关配置

打开 app 的 build.gradle 添加 Dagger 依赖:

depencies {
  ...
  // Dependency Injection
  apt "com.google.dagger:dagger-compiler:2.2"
  compile "com.google.dagger:dagger:2.2"
  provided 'javax.annotation:jsr250-api:1.0'
  ...
}

省略号代表项目中的已有依赖。Android Studio 会提示你的 gradle 文件已改变,需要同步,因此请根据提示进行,确保你的 Dagger 能够正确引入。注意你还导入了 javax 的注解库,因为 Dagger 的许多功能都是通过 Java 注解来实现的。

模块

你要用到的第一个注解就是@Module。在 app 的 main 包下面新建一个 dagger 包,右键点击 main 包,选择 New/Package:

然后,在 dagger 包下面新建文件。右键点击 dagger,选择 New/Java Class。类名命名为 AppModule。

在新文件中添加类声明:

@Module
public class AppModule {
}

这里,我们创建了一个名为 AppModule 的类,并用 @Module 进行注解。Android Studio 会自动创建必须的 import 语句,如果没有,请用 Alt-Enter 创建。

@Module 注解告诉 Dagger,AppModule 类为 app 提供依赖。在一个项目中使用多个 Dagger 模块是比较常见的,通常用对于 app 级别的依赖会专门用一个模块来提供。

在类体中添加下列代码:

private Application application;

public AppModule(Application application) {
  this.application = application;
}

@Provides
@Singleton
public Context provideContext() {
  return application;
}

我们添加了一个私有成员保存对 app 的引用,用一个构造函数来配置 application 成员,以及一个 provideContext() 方法返回这个 application。注意这个方法有两个 Dagger 注解: @Provides and @Singleton。

@Provides 注解告诉 Dagger 这个方法提供了某种类型的依赖,在我们的例子中,就是 Context 对象。当 app 请求 Dagger 注入一个 Context 时,@Provides 注解告诉 Dagger 去哪里找到这个对象。

注意:这个 provider 的方法名,叫做 provideContext() 或者别的什么一点也不重要。Dagger 只关心返回类型。

@Singleton 注解告诉 Dagger 这个依赖只有一个单例对象,并且为你减少了许多重复的代码。

组件

现在你有了一个 Dagger 模块,它包含了一个可注入的依赖,我们要怎么使用它呢?

这需要用到另外了一个 Dagger 注解 @Component。新建一个 Java 文件在 dagger 包下,命名为 AppComponent>

在这个文件中,编写代码:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
}

我们告诉 Dagger 这个 AppComponent 是一个单例组件。@Component 注解用一个模块数组作为参数,对于这个参数,我们在数组中添加了一个 AppModule。

这个组件用于将对象和它们的依赖进行绑定,通常需要覆盖 injecdt() 方法。在第一版的 Dagger 中,这个过程在反射对象图的时候进行。为了使用组件,应该让它在 app 中所有需要注入的地方被访问。一般,就是 app 的 Applicatoin 子类。

首先,添加下列变量声明以及 Applicationd 的 get 方法:

private AppComponent appComponent;

public AppComponent getAppComponent() {
  return appComponent;
}

然后来初始化 AppCompoent.z DeezFoodzApplication 中添加如下方法:

protected AppComponent initDagger(DeezFoodzApplication application) {
  return DaggerAppComponent.builder()
      .appModule(new AppModule(application))
      .build();
}

AndroidStudio 会在 DaggerAppComponnet 上报错。这个类还没有被生成。通过 Android Studio 的 Build 菜单,选择 Make Module ‘app’ 。这会产生编译错误,但会清除掉代码中的报错。

再次执行 Make Module ‘app’,清除编译错误。然后,你会在 appModule()方法上出现一个警告,这个很快就能搞定。

最终,在 DeezFoodzApplication 修改 onCreate() 方法:

@Override
public void onCreate() {
  super.onCreate();
  appComponent = initDagger(this);
}

当 app 第一次启动,初始化了 appComponent 变量。

用 Dagger 2 进行依赖注入

在 AppComponent 接口中添加这个方法:

void inject(FoodzActivity target);

这里,我们指定 FoodzActivity 类需要依靠 AppComponent 进行注入。接下来我们将对应的对象注入到 FoodzActivity。

在 dagger 包中新建类 PresenterModule。在这个类中声明:

@Module
public class PresenterModule {
  @Provides
  @Singleton
  FoodzPresenter provideFoodzPresenter() {
    return new FoodzPresenterImpl();
  }
}

这个类会提供一个 FoodzPresenter ,这个方法会返回一个由 FoodzPresenterImpl 来具体实现的 presenter 对象。

然后,在 AppComponent 中的 @Component 注解中,添加 PresenterModule,和 AppComponent 放在一起。

@Component(modules = {AppModule.class, PresenterModule.class})

最后,打开 FoodzActivity ,它位于 ui.foodz 包下面。首先,在 presenter 字段前添加一个 @Inject 注解:

@Inject
FoodzPresenter presenter;

然后,在 onCreate() 方法中,将下列创建 presenter 的语句删除:

presenter = new FoodzPresenterImpl();

@Inject 注解告诉 Dagger,我们对 presenter 字段进行依赖注入。

运行 app,app 崩溃,报 NPE(空指针异常)错误。我们还有一个步骤忘了做。

修改 onCreate() 方法,在其中加入对 AppComponent.inject() 的调用:

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

  ((DeezFoodzApplication)getApplication()).getAppComponent().inject(this);

  ButterKnife.bind(this);
  ...

我们从 DeezFoodzApplication 获得了 AppComponent 的引用,然后用它将所有依赖注入到 FoodzActivity。因为 presenter 被声明为 @Inject,Dagger 会将一个 FoodzPresenter 实现注入给 FoodzActivity。

Dagger 知道在 PresenterModule 类中有一个 ProvideFoodzPresenter() 方法,并用它来创建用于注入的FoodzPresenter 对象。

运行 app。结果和之前一样,同时我们也解决了 NPE 问题。现在我们已经用 Dagger 2 实现了我们的第一个依赖注入!

根据前面我们对代码的修改,我们可以总结出一些规律。以 Dagger 向 FoodzActivity 中进行的依赖注入为例:

  1. 在 AppComponent 新建一个 inject() 方法,使用一个 FoodzActivity 对象做参数。
  2. 在 PresenterModule 中新增方法 provideFoodzPresenter()。
  3. 在 FoodzActivity 的 presenter 之前添加注解 @Inject。
  4. 在 FoodzActivity 的 onCreate() 方法中添加 DeezFoodzApplication.getAppComponent().inject(this)。

假设将 FoodzActivity 称作 Target 类,FoodzPresenter 用将被注入的接口 Source,那么上述步骤可以抽象为向任何目标类注入源接口的步骤:

  1. 在 AppCompoent 中增加 inject() 方法,用 Target 类作为参数。
  2. 在 PresenterModule 中,针对每个注入 Source 对象的方法用 @Provides 进行修饰。
  3. 在 Target 类中,对每个 Source 成员变量,用 @Inject 进行修饰。
  4. 在 Target 的 OnCreate() 方法中,添加 DeezFoodzApplication.getAppComponent().inject(this)。

给你出道题,将 FoodPresenter 详情页面注入到 FoodActivity 中(在 ui.food 包下面),文件中的这行代码要删除:

presenter = new FoodPresenterImpl();

这和我们在 FoodzActivity 中所做的步骤是一样的。使用上面的模板,如果做不出来,可以参考最终项目中的源代码。

注入 Network Graph

在读到这里的时候,列表页面和详情页面的 presenter 仍然在各自创建自己的网络依赖。在一个正常的使用了 Dagger 2 和 Retrofit 的 app 中,Retrofit 应该是以依赖注入的方式使用的。

这里列出了一些使用依赖注入和 Dagger 2 的好处,包括:

  • 解决代码复用问题
  • 解决依赖配置问题
  • 自动生成依赖图谱

在 dagger 包中新建文件 NetworkModule,文件内容为:

@Module
public class NetworkModule {
}

这里我们需要注入一个 UsdaApi 对象给 app 的 presenter 实现,以便 presenter 能够使用这些 API。

例如,打开当前的 FoodPresenterImpl 文件,你会发现 UsdaApi 依赖了一个 Retrofit 对象,而创建一个 Retrofit 对象需要一个字符串形式的 API base URL 和一个 Conver.factory 对象。

首先,在 NetworkModule 中新增两个方法和一个字符串常量:

private static final String NAME_BASE_URL = "NAME_BASE_URL";

@Provides
@Named(NAME_BASE_URL)
String provideBaseUrlString() {
  return Constants.BASE_URL;
}

@Provides
@Singleton
Converter.Factory provideGsonConverter() {
  return GsonConverterFactory.create();
}

这里有一个新注解出现了。@Named。你要注入一个字符串对象,在 Android app 中字符串是一种普通类型,我们可以通过 @Named 注解指定某个字符串应当是被提供的(类似 @Provides)。如果我们有很多变量需要注入时,也可以在自定义类型上使用同样的方法。

现在,你拥有了一个 String 和 一个 GsonConverterFactory, 在 NetworkModule 底部添加:

@Provides
@Singleton
Retrofit provideRetrofit(Converter.Factory converter, @Named(NAME_BASE_URL) String baseUrl) {
  return new Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(converter)
    .build();
}

@Provides
@Singleton
UsdaApi provideUsdaApi(Retrofit retrofit) {
  return retrofit.create(UsdaApi.class);
}

我们添加了两个 provide 方法,一个返回 Retrofit 对象,一个返回 UsdaApi 对象。这就让 Dagger 能够构建出一个依赖图谱,当一个对象请求一个 UsdaApi 对象注入时,Dagger 首先会向 provideUsdaApi(Retrofit retrofit) 方法提供一个 Retrofit 对象。

然后 Dagger 在图谱中查找 converter 和 baseUrl 以便提供给 provideRetrofit(Converter.Factory converter, @Named(NAME_BASE_URL) String baseUrl) 方法。

通过 @Singleton 注解, UsdaApi 和 Retrofit 对象都只会创建一次,然后在不同的 activity 中共用。

将 NetworkModule 添加到 AppComponent 的 @Component 注解:

@Component(modules = {AppModule.class, PresenterModule.class, NetworkModule.class})

然后,修改 PresenterModule 的 provide 方法,以便 presenter 的构造函数能够增加一个 Context 参数:

@Provides
@Singleton
FoodzPresenter provideFoodzPresenter(Context context) {
  return new FoodzPresenterImpl(context);
}

@Provides
@Singleton
FoodPresenter provideFoodPresenter(Context context) {
  return new FoodPresenterImpl(context);
}

想 AppComponent 中添加两个注入方法:

void inject(FoodzPresenterImpl target);
void inject(FoodPresenterImpl target);

然后,修改 FoodzPresenterImpl 和 FoodPresenterImpl 增加两个构造函数:

public FoodzPresenterImpl(Context context) {
  ((DeezFoodzApplication)context).getAppComponent().inject(this);
}
public FoodPresenterImpl(Context context) {
  ((DeezFoodzApplication)context).getAppComponent().inject(this);
}

在两个类中增加以下注入式字段:

@Inject
UsdaApi usdaApi;

现在,我们已经向 UsdaApi 注入两个 presenter 实现了,可以将它们中的这些重复的语句删除了:

Converter.Factory converter = GsonConverterFactory.create();

Retrofit retrofit = new Retrofit.Builder()
  .baseUrl(Constants.BASE_URL)
  .addConverterFactory(converter)
  .build();

UsdaApi usdaApi = retrofit.create(UsdaApi.class);

最后一次运行 app。这回,你会发现 app 行为和之前没有区别,但我们成功地使用了依赖注入,你的 app 维护性更好,更容易测试了。

结束

这里下载最终项目。

对 DeezFoodz 的改造花费了我们大量的工作。但在现实生活中使用依赖注入和 Dagger 2 这样的框架的 app 变得非常普遍,因为这些 app 的依赖图谱是非常复杂的。

Dagger 2 和依赖注入在测试 app 时尤其有用,它允许在测试中模拟后台 API 和数据存储。

更多关于 Dagger 2 的内容用法,还有:

  • Scopes
  • Subcomponents
  • Testing with Mockito

在网上有许多优秀的资源介绍了这些主题。终于结束了,祝你在注入时开心!

有任何问题和评论,请在下面留言。

作者:kmyhy 发表于2017/7/31 16:31:12 原文链接
阅读:182 评论:0 查看评论

android ndk 02 C语言 二级指针 指针运算 指针与数组

$
0
0

c函数速查: https://pan.baidu.com/s/1sllYdul

通过几个例子 复习 二级指针 指针的运算 指针与数组 函数指针 练习

指针为什么要有类型

指针和地址的区别, 指针有类型 地址没有类型.


#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <math.h>
#include <time.h>
//这是引用的头文件

举例: 1

// 1.指针为什么要有类型?

//指针有类型,地址没有类型

//地址只是开始的位置,类型读取到什么位置结束

void main(){
    int i = 89;
    //int 类型的指针
    int *p = &i;
    double j = 78.9;
    //赋值为double类型变量的地址
    p = &j;
    printf("double size:%d\n", sizeof(double));
    printf("%#x,%lf\n",p,*p); //想通过4字节读取8字节变量的值,是不行的    

    getchar();
}

2. NULL空指针 指针初始化


//2.NULL空指针  指针一般需要初始化 置空
void main(){
    int i = 9;
    int *p = NULL;
    //p = &i;

    //空指针的默认值为0
    printf("%#x\n",p);
    //访问内存地址0x000000操作系统不允许
    //p = 100; //操作系统不允许访问
    printf("%d\n",*p);
    getchar();
}

3, 多级指针 使用

//3.多级指针(二级指针)
//指针保存的是变量的地址,保存的这个变量还可以是一个指针变量
//动态内存分配给二维数组
void main(){
    int a = 50;
    //p1上保存的a的地址
    int* p1 = &a;

    //p2上保存的p1的地址
    int** p2 = &p1;

    //int*** p3 = &p2;

    printf("p1:%#x,p2:%#x\n",p1,p2);
    **p2 = 90;

    printf("%d\n",a);

    getchar();
}

上面的例子 通过操作二级指针改变了 a 的值

个人理解 : 二级指针 可以理解成二维数组 三级指针可以理解成三位数组

一级指针是一条线 , 二级指针是一个面, 三级指针 是一个立方体 .

image

— 分割线 —

第4个例子 指针的运算

发现markdown > 操作不错

//4 指针的运算

void main(){

    int ids[] = { 78, 90, 23, 65, 19 };
    printf("%#x\n", ids);
    printf("%#x\n", &ids);
    printf("%#x\n", ids[0]);

    //指针变量
    int *p = ids;
    printf("%d\n", *p);//默认打印第0个

    //指针的加法
    p++;
    printf("p的值 %d\n", *p);
    printf("16进制 %#x\n", *p);


    getchar();
}

效果图

image

第五个例子 通过指针给数组赋值


//5 通过指针给数组赋值

void main(){

    int uids[5];
    //int i = 0;
    //高级写法 现在的写法  这里没有操作指针 实际上内部操作了
    /*for (i; i < 5; i++)
    {
    uids[i] = i;
    }*/

    //早些版本的写法   操作指针了 
    int* p = uids;
    printf("%#x\n", p);
    int i = 0; //i是数组元素的值
    for (; p < uids + 5; p++){
        *p = i;
        i++;
    }
    int j = 0;
    for (; j < 5; j++)
    {
        printf("数组 uids[%d] = %d \n", j, uids[j]);
    }
    getchar();
}

iamge

第六个例子 函数指针

函数指针1


int msg(char *msg, char *title){
    messagebox(0, msg, title, 0); 
    return 0;
}

void main(){

    //函数返回值类型,函数指针的名称,函数的参数列表
    //函数指针 这是把一个函数赋给指针
    int(*fun_p)(char *msg, char *title) = msg;
    fun_p("内容", "title");
    getchar();

}

image

函数指针2 例子


int add(int a, int b){
    return a + b;
}

int minus(int a, int b){
    return a - b;
}

void msg(int(*func_p)(int a, int b), int m, int n){
    printf("执行一段代码...\n");
    printf("执行回调函数...\n");
    int r = func_p(m, n);
    printf("执行结果:%d\n", r);
}

void main(){
    //加法
    //int(*func_p)(int a, int b) = add;//把这句 直接放到msg 作为参数穿进去了

    msg(add, 3 , 5);

    getchar();

}

运行结果

image

最后一个例子


//案例:用随机数生成一个数组,写一个函数查找最小的值,并返回最小数的地址,在主函数中打印出来

int* getMinPointer(int ids[], int len){
    int i = 0;
    int *p = &ids[0];
    for (; i < len; i++)
    {
        if (ids[i] < *p){
            p = &ids[i];
        }
    }
    return p;
}

void main(){
    int ids[10];
    int i = 0;
    //初始化随机数发生器,设置种子,种子不一样,随机数才不一样
    //当前时间作为种子 有符号 int -xx - > +xx
    //看c语言速查表
    srand((unsigned)time(NULL));
    for (i; i < 10; i++)
    {
        //100范围内
        ids[i] = rand() % 100;
        printf("%d\n", ids[i]);
    }

    //调用
    int* p = getMinPointer(ids, sizeof(ids) / sizeof(int));
    printf("指针地址: %#x, 最小值: %d", p, *p);
    getchar();

}

运行结果:

image

作者:liudao7994 发表于2017/7/31 17:27:39 原文链接
阅读:143 评论:0 查看评论

一起Talk Android吧(第三十四回:Android中的Activity六)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,我们在前面章回中介绍了Activity的状态和各个状态的变换,在本章回中我们继续来说Activity的生命周期。Activity的生命周期看上去是一个整体,其实可以对它进行详细的划分,我们可以将其分为三个小的部分:

  • 完整生命周期
  • 可见生命周期
  • 前台生命周期

这样的划分方法是官方认可的,大家可以官方网站中查找到。接下来我们分别介绍它们。

1.完整生命周期

从Activity的初始化状态开始,经历启动、运行、暂停、停止,这些状态,直到Activity被销毁为止。当然了,其中也包括我们在前面章回中提到的三个“波折的箭头”。随着Activity的状态变化,各个回调方法也在交替执行。我们在前面章回介绍过各个状态以及与状态匹配的回调方法,如果有看官忘记了,可以回顾一个前面章回中的内容。

2.可见生命周期

从Activity的启动状态开始,经历运行和暂停状态,直到Activist的停止状态为止,当然了,其中也包括两个“波折的箭头”。在整个生命周期中,我们都可以看到Activity,因此给它取名叫可见生命周期,这是一个十分形象的名字。

3.前台生命周期

从Activity的运行状态开始,到暂停状态为止,当然了,其中也包括一个“波折的箭头”。在整个生命周期中,Activity一直可以获取焦点,我们对它操作后,它也会做出相应的响应。鉴于这个原因,我们称这个生命周期为前台生命周期。

看官们,下面展示一个我在很早之前总结出来的图形,虽然图的内容有点旧(大约是1.6版本时的图形),但是内容还是正确的,从图形可以可以更加直观地看到这三个生命周期.

这里写图片描述

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 23:13:12 原文链接
阅读:177 评论:0 查看评论

一起Talk Android吧(第三十五回:Android中的Activity七)

$
0
0

各位看官们,大家好,上一回中咱们说的是Android中Activity的例子,这一回咱们继续说该例子。闲话休提, 言归正转。让我们一起Talk Android吧!


看官们,我们在前面章回中介绍了Activity的状态以及生命周期,这部分内容都是偏向于理论的知识,我们一直推崇实践,今天给大家介绍一下,如何去实践。其实也不是什么新的方法,就是使用断点进行调试,我简单介绍一下,算是新瓶装旧酒吧,希望大家喝的开心。

首先建立一个新的工程,在MainActivity中重写生命周期中的方法就可以。下面是我写的代码,供大家参考:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

重写完这些方法后,在每个方法上都打上断点,然后开始调试刚才新建的项目。打断点调试是常见的调试方法,相信大家都会操作。因此,我就不啰嗦了。在调试过程中希望大家结合我们给出的生命周期图来分析程序的运行过程,这样有助于大家理解Activity的生命周期。

各位看官,关于Android中的Activity的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!


作者:talk_8 发表于2017/7/31 23:40:26 原文链接
阅读:234 评论:0 查看评论

58 同城 Android 端 HTTPS 实践之旅

$
0
0

自 WWDC 2016 苹果传递出从 2017 年 1 月起强制启用应用程序安全传输协议(App Transport Security)的信号,各大厂均开始了 HTTPS 化的征程。虽然目前苹果将此计划延期,但 HTTPS 协议已经在各大厂开花结果。

前言

HTTPS 协议是以 SSL 协议为基础的安全版 HTTP 协议,好处不言自明,即为安全。对于用户来说,HTTPS 协议不仅能保障自己的隐私与数据安全,同时也降低了“页面小弹窗”的困扰,极大地提升了用户体验。本文将介绍 58 同城 App 在 HTTPS 改造方面的一些经验,并对 Android 端 HTTPS 实践中遇到的问题进行总结。

项目准备

58 同城平台为了推动各业务线进行 HTTPS 改造,需要提供各端的完整改造方案。所以,我们在项目准备阶段,主要做了两部分事情:

  • 调研 HTTPS 协议与部署相关问题;
  • 输出具体改造方案。

在调研 HTTPS 协议与部署相关问题之后,各端均输出了一份具体的改造方案,如下:

  • 服务端:动态适配请求协议头,消灭硬编码,域名升级;
  • 前端:页面静态路径去掉协议头;
  • 客户端:升级 Native 网络库支持 HTTPS 及 WebView 升级(此仅 iOS 端);
  • 测试:HTTPS 测试方法与测试点、上线流程。

接下来,笔者将主要对上述改造方案中的 Android 客户端实践及其涉及原理进行详细介绍,对于 HTTPS 协议与 HTTP2 协议原理分析感兴趣的读者,可以阅览《HTTPS 与 HTTP2 协议分析》了解更多。

改造 Android 端 HTTPS 实践

改造后的项目架构如图 1 所示,相对于 58 同城 App 原有架构,添加了 OkHttp 网络库进行网络层收敛,而 API 请求、图片请求、H5 页面资源请求最终均会在 OkHttp 创建的连接上进行数据传输。

图 1 项目架构设计

需要说明的是,这里之所以引入了 OkHttp 网络库,主要是因为 HTTP2 协议的支持。

因为当考虑进行 HTTPS 改造时,我们首先想到的一个问题便是 HTTPS 性能低下。相对 HTTP 协议来说,HTTPS 协议建立数据通道更耗时,若直接部署到 App 中,势必会降低数据传递的效率,间接影响用户体验。

HTTP2 协议本是为了解决 HTTP/1.X 协议的低效率而诞生的,不过在实际应用中,只会在 HTTPS 协议握手阶段进行协议协商,所以 HTTP2 目前直接改善的其实是 HTTPS 的低效率。为此,HTTP2 主要提出了两大改进点:

  • 多路复用。同一域名下的请求,可通过同一条链路进行传输,不必单独建立链路,有效节省开销;
  • 压缩头信息。将头部字段缓存为索引,客户端与服务端维护索引表,通信过程中尽可能采用索引进行通信,收到索引后查询索引表,才能解析出真正的头部信息。

因此,我们在 Android 端的具体改造方案主要在于 OkHttp 库与调用库之间的交互与包装,其中:

  • Volley 底层连接替换 OkHttp,只需要创建 OkHttpStack 类实现 HTTPStack 接口并替换 HurlStack 即可,网上成型方案较多,这里不再赘述。
  • Fresco 底层连接替换 OkHttp 更加简单,官方已经提供了 OkHttpNetworkFetcher 类,直接通过 ImagePipelineConfig 设置 NetworkFetcher 即可完成替换。在后面的具体实践部分,还会讲到对 Fresco 官方提供的 OkHttpNetworkFetcher 在取消加载部分的优化。

部署实施

对 App 进行 HTTPS 改造需要服务端、前端、客户端一同配合开发,QA 进行质量把控。同时,由于 58 同城 App 涵盖了众多业务线与第三方,每个业务乃至接口的部署都可能会对其他业务造成影响。所以,各业务开发与部署的时序、整体进度的把控是我们面临的最大难题。

部署实施步骤

经过与各业务线的充分讨论,我们最终确立了如下实施步骤:

  • 以业务线为单位进行服务梳理,确定并理清各业务线的依赖关系。
  • 业务线基于依赖关系进行改造排期预估,并着手开发。

58 同城 App 平台方及时主动地跟进各业务线,解决改造期间的技术问题与协调业务线间联调配合等。同时,开发必要的风险规避策略(譬如降级策略),以降低后续灰度上线风险。

业务线完成改造并通过测试后,58 同城 App 平台方修改业务线入口跳转协议,提供 HTTPS 入口进行灰度测试,若效果符合预期,则逐步提高灰度测试比例直至全量。

实施注意事项

通过以上步骤,基本保证了业务线间能够高效并行开发,但在实施过程中,有几点需要特别注意:

  • 业务线间由于历史问题,有些业务存在严重的交叉依赖情况,需要及时协调业务线进行暂时的依赖解除。

    何为“暂时的依赖解除”?多个业务线由于并行进行 HTTPS 改造,服务的相互依赖导致单个业务线无法测试。此时进度较快的业务线可以将依赖的服务使用 HTTP 协议代替访问,或通过 host 配置相关服务的测试机,待其他业务线完成部署后再改回 HTTPS 协议。

  • 虽然以业务线为单位进行并行开发可以将开发、测试等流程分发到业务线内部完成,但 HTTPS 改造涉及到的服务众多,改造成本很高,可能会与业务线的业务需求开发产生冲突。因此,平台方需要及时跟进业务线的进度,及时妥善地处理阻塞因素。

HTTPS 实践问题汇总

鉴于 HTTPS 用户体验更好,以及可以解决 HTTPS 性能问题的切实方案,58 同城 App 便开展了全站 HTTPS 化的改造。当然,在改造过程中,我们也遇到了一些问题,主要有以下几类:

  • HTTPS 调试问题
  • 性能问题
  • 环境问题
  • OkHttp 接入问题

下面将对以上问题进行依次分析。

HTTPS 调试问题

进行 HTTPS 改造遇到的第一个问题就是 HTTPS 不好调试。当我们绑定了 PC 作为代理,通过 Charles 或 Fiddler 抓取请求时,它们即成为我们的代理服务器。若不安装 Charles 或 Fiddler 的证书到设备上,便无法完成对代理服务器的身份认证,后续的应用数据传输也就无从谈起,直接的表现即为 HTTPS 请求失败。

面对这种问题,最简单的方式是给设备安装证书,之后便可以调试 HTTPS 请求了。但每台 PC 的代理证书各异,若需要像 HTTP 请求一样方便地调试,须对每台手机安装每台 PC 的代理证书。这点对于仅需要验证请求数据的测试同学来说比较痛苦,只是为了看下数据,为什么要这么麻烦?

在此给出两点可行的建议:

  • 客户端将 HTTPS 请求结果作为日志输出,开发与测试同学可以针对日志分析接口问题;
  • 采用类似 Chuck 项目(https://github.com/jgilfelt/chuck)的思路,为 OkHttp 添加 interceptor 以收集请求结果,并将其以 UI 形式直观地展示出来。

通过以上两种方式,可以有效地简化请求结果的验证与查看。若是需要修改请求的结果进行调试开发,是否是 HTTPS 协议已无关紧要,此时借助 Charles 与 Fiddler 调试 HTTP 接口也非常简单。

性能问题

HTTPS 协议性能较 HTTP 协议稍差,也由此造成了弱网情况下的连接超时问题。

  • 多路复用特性提升 HTTPS 性能

HTTPS 协议通信效率较 HTTP 协议通信效率低是众所周知的事实,当 App 全面升级为 HTTPS 时,通信效率的降低会直接影响用户体验。我们经过线上数据对比发现,通过 HTTPS 协议访问,其耗时是 HTTP 协议访问耗时的 1.3-2.1 倍。

那么,HTTPS 该如何提高通信效率呢?

在建立安全通道部分,由于涉及到身份认证与算法、密钥协商,两次网络往返是很难优化的。但在建立了安全通道后,若能复用此通道,则后续请求便可避免两次网络往返。所以,基于这种思路,58 同城 App 主要借助 HTTP2(或 SPDY)协议的多路复用特性,提高通道使用率,进而提高通信效率。

由于多路复用特性是域名级复用,所以最重要的一点便是收敛域名。收敛效果越好,通道的复用率越高。因此,我们对 API 接口、图片等资源接口进行了域名收敛,尽可能地收敛多级域名至二级域名、收敛零散域名至统一域名。

综上,借助 HTTP2(或 SPDY)协议的多路复用特性,以及对现有业务的域名收敛进行优化,通过线上数据对比得出,其访问耗时是 HTTP 协议访问耗时的 1.2 倍左右。

  • 提高列表页 HTTPS 图片加载速度

58 同城 App 使用的图片库是 Fresco,在 OkHttp 接入后,我们也顺势将 Fresco 的 Fetcher 替换为 OkHttp 实现,以提高 HTTPS 图片的加载速度。但官方提供的 OkHttpNetworkFetcher 却仍有优化空间。比如,OkHttpNetworkFetcher 的加载任务取消操作是通过调用 Call.cancel()来实现的。具体代码如下:

//OkHttpNetworkFetcher 对 Call 进行取消
fetchState.getContext().addCallbacks(
    new BaseProducerContextCallbacks(){
        @Override
        public void onCancellationRequested(){
        if(Looper.myLooper()! = Looper.getMainLooper()){
            call.cancel();
        }else{                           mCancellationExecutor.execute(new Runnable(){
        @Override public void run() {
           call.cancel();
          }
        });
      }
   }
});

对 Call.cancel()执行加载取消操作后,加载仍然会被线程池调用执行,直到 RetryAndFollowInterceptor 执行时 cancel 操作才会起作用。

因此,我们对 OkHttpNetworkFetcher 进行了改写。在 Fresco 取消加载的回调中,对图片加载任务对应的 future 进行 cancel 操作,便可以减少 RetryAndFollowInterceptor 之前的逻辑处理(主要是自定义 Interceptor 部分)。

具体代码实现与 HttpUrlConnectionNetworkFetcher 的取消回调实现类似。

HTTPS 环境问题

客户端证书验证问题

HTTPS 改造过程中,常见的一个问题便是客户端证书验证出错,究其原因,往往是因为:

  • 证书管理混乱,导致下发证书的域名与请求域名不符,无法通过验证。
  • 证书过期。
  • 证书签发 CA 未被内置于客户端。
  • 证书链不完整,无法验证。

在此,我们具体剖析一下“CA 未被内置于客户端”与“证书链不完整”的问题。

证书签发 CA 未被内置于客户端——由于 CA 数量众多,质量也参差不齐,面对同样众多的手机厂商与自定义 ROM,无法保证 CA 能够内置到客户端证书列表中,所以 CA 存在不被客户端认可的可能性(Google 也会基于 CA 认可度进行证书列表的更新)。相对来说,顶级 CA 的设备兼容性较好,若遇到根证书路径找不到的异常,可以考虑更换 CA,签发证书。

证书链不完整,无法验证——在握手协议中,服务端会下发 Certificate 消息给客户端,消息中携带了由 CA 签发的证书与 CA 证书构成的证书链。客户端通过证书链信息,逐级向上寻找根证书,找到后再通过根证书的公钥逐级向下验证证书链,若证书链验证通过,则身份验证阶段完成。

倘若服务端只下发了自己的证书或下发的证书链不足以寻找到根证书,导致验证流程断裂,则无法通过身份验证。这是服务端证书配置部署问题,如果证书认证失败,很有可能是这个原因造成的。

DNS 劫持问题

虽然我们采用 HTTPS 提高了通信安全,但 DNS 劫持的情况仍然无法解决。当我们希望获取 IP 地址时,需要通过 DNS 服务器进行查询,若访问的服务器被污染,返回给我们错误的 IP 地址,此时便产生了 DNS 劫持问题。

相对于 HTTP 协议,HTTPS 协议 DNS 劫持的后果更为严重。于 HTTP 协议而言,DNS 劫持后会产生监听数据或插入数据的风险,而功能可能不受影响。但对于 HTTPS 协议来说,DNS 劫持后,服务器下发证书无法通过客户端认证,或是服务器根本没有开启 443 端口,均无法建立 HTTPS 连接。

面对 DNS 劫持情况,这里提供两种解决方案:

  • 下发(或内置)IP 列表,通过 DNS 接口由客户端进行 DNS 解析或 IP 地址比对;
  • 运维监控或第三方监控,对出现 DNS 劫持的区域向运营商投诉解决。

降级策略

考虑到 HTTPS 存在证书验证、DNS 劫持及代理 443 未开启等诸多问题,在实践过程中,我们也添加了降级策略。启动 App 时,通过服务接口下发域名降级字典,譬如:

{"key" : [{"HTTPS://app.58.com" : "http://app.58.com"}]}

在请求前,会将 URL 与降级字典匹配处理,经过匹配的 URL 再发起请求,避免 HTTPS 改造影响用户功能。

OkHttp 接入问题

OkHttp 是目前使用最广泛的支持 HTTP2 的 Android 端开源网络库,下面分享下 58 同城 App 在接入 OkHttp 过程中遇到的问题。

OkHttp 头部数据非法字符抛出异常

OkHttp 构造头部数据主要通过 Request.Builder 对象的 add(name,value)与 set(name,value)两种方法,而它们内部均会调用 checkNameAndValue(name,value),这个方法会对 name 与 value 分别进行字符检测,若字符不在\u001f 至\u007f 之间,则会抛出 IllegalArgumentException。

private void checkNameAndValue(String name, String value) {
  if(name == null) throw new NullPointerException("name == null");
  if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
  for(int i = 0, length = name.length(); i < length; i++) {
     char c = name.charAt(i);
     if (c <= '\u001f' || c >= '\u007f') {
     throw new IllegalArgumentException(Util.format(
          "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
     }
}
   if (value == null) throw new NullPointerException("value == null");
   for (int i = 0, length = value.length(); i < length; i++) {
     char c = value.charAt(i);
     if (c <= '\u001f' || c >= '\u007f') {
     throw new IllegalArgumentException(Util.format(
         "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
     }
   }
}

从 OkHttp 底层代码可以发现,OkHttp 对字符串是通过 UTF-8 编码的,这里强制进行字符检测,猜想可能是基于对编码规范的考虑。

为了避免字符检测失败导致的异常,我们可以通过其他 API 绕过这个限制:

  • Header 添加通过 Headers.of 方法生成 Headers,并通过 Builder.headers 方法配置进去。
  • 通过 Internal.instance.addLenient 方法直接设置 name 与 value,add 与 set 方法底层也是调用这个方法。不过这个方法需要保证 Internal.instance 已初始化,即 OkHttpClient 已创建,否则会抛出空指针异常。

OkHttp 中 post 请求抛出异常

当我们使用 Request.Builder 类通过 post(RequestBody)方法进行 post 请求构造时,若不对 RequestBody 做判空操作,则有可能会抛出 IllegalArgumentException。

post(RequestBody)方法最终会调用 method()方法,它会对请求类型与 body 做校验,若 post 请求对应的 body 为 null,则抛出异常。代码如下:

public Builder method(String method, RequestBody body) {
  if (method == null) throw new NullPointerException("method == null");
  if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
  if (body != null && !HttpMethod.permitsRequestBody(method)) {
  throw new IllegalArgumentException("method " + method + " must not have a request body.");
  }
  //这里便是 body 为空时,异常抛出点
  if (body == null && HttpMethod.requiresRequestBody(method)) {
  throw new IllegalArgumentException("method " + method + " must have a request body.");
  }
  this.method = method;
  this.body = body;
  return this;
}

所以在构造 post 请求时,需要对 RequestBody 进行非空判断,若 RequestBody 为空,则需要构造一个无内容的 RequestBody 对象。

OkHttp 在 HTTP2 协议下 Response 监听线程崩溃

在接入 OkHttp 并使用 HTTP2 协议进行通信后,第三方应用崩溃检测工具(如 Bugly)会收集到线上版本关于 OkHttp 的两种崩溃,分别为 EOFException、ArrayIndexOutOfBoundsException。这两种崩溃的堆栈信息显示,均是在 FramedConnection 内部类 Reader 的 execute( )方法中抛出的。

@Override protected void execute() {
 ......
 try {
 ......
} catch (IOException e) {
    ......
    } finally {
    try {
    //抛出点 1
    close(connectionErrorCode, streamErrorCode);
    } catch (IOException ignored) {
   }
    //抛出点 2
    Util.closeQuietly(frameReader);
    }
}

究其原因,Reader 的 execute 方法被独立线程调用进行 Response 的监听,在连接断开或异常中断的情况下,会进入代码中 finally 代码块,而 finally 代码块只 Catch 住了 OkHttp 可能抛出的异常,并没有关注 Okio 抛出的异常。

这个问题当前还无法稳定复现,可能出现在两端,也可能出现在国内复杂的网络环境下,排查较为复杂。而对连接断开与异常中断等场景,如果我们 Catch 住所有的异常,也不会对用户有任何影响。
目前我们的处理方式便是对 OkHttp 进行重打包,对整个 execute()方法进行捕获,以解决 Response 监听线程崩溃的问题。

在 HTTP2 协议下头信息小写问题

HTTP2 为了解决 HTTP1.X 中头信息过大导致效率低下的问题,提出了通过 HPACK 压缩算法压缩头部信息的解决方案。

正因为 HPACK 以索引代替头部字段,所以相同头部字段若因为大小写的问题导致存在多个索引,是一种很大的浪费。举个例子,“accept-encoding”、“Accept-Encoding”与“ACCEPT-ENCODING”表达的是一个意思,所以 HTTP2 规定,头部信息统一用小写。

OkHttp 的实现,也是统一采用小写:

private static final Header[] STATIC_HEADER_TABLE = new Header[] {
   ......
   new Header("accept-charset", ""),
   new Header("accept-encoding", "gzip, deflate"),
   new Header("accept-language", ""),
   new Header("accept-ranges", ""),
   new Header("accept", ""),
    ......
};

所以当我们需要针对某些头信息进行逻辑处理时,首先要对字段进行小写的格式化操作,以避免监听不到头部字段或添加的大写头部字段被小写头部字段覆盖。

总结

站在技术的角度解决用户痛点是每个开发者的愿景,HTTPS 改造虽然只是一次普通的技术改造,但对用户隐私保护与用户体验优化却有着深远的影响。通过 HTTPS 改造项目,58 同城 App 完成了接口的 HTTPS 化,数据监听与内容篡改已成往事,用户体验也得到了保障,但由于 58 同城 App 涉及到众多业务,HTTPS 性能方向上仍然有很大空间亟待我们后续优化。

作者:赵岘,58 同城 Android 高级工程师,主要负责性能优化与质量提高方向,主导了 58 同城 App 端 HTTPS 改造项目。
责编:唐门教主
声明:本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》。

作者:Byeweiyang 发表于2017/8/1 9:56:32 原文链接
阅读:0 评论:0 查看评论

Linux进程间通信之POSIX信号量

$
0
0

当多个进程同时访问系统上的某个资源的时候,就需要考虑进程的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问。POSIX信号量不仅可以用于进程之间的同步,也可以用于线程之间的同步。


概述

SUSv3规定了两种类型的POSIX信号量。

  • 命名信号量:这种信号量拥有一个名字。通过使用相同的名字调用sem_open(),不相关的进程能够访问同一个信号量。
  • 未命名信号量:这种信号量没有名字,相反,它位于内存中一个预先商定的位置处。未命名信号量可以在进程之间或一组线程之间共享。当在进程之间共享时,信号量必须位于一个共享内存区域中。当在线程之间共享时,信号量可以位于被这些线程共享的一块内存区域中(如在堆上或在一个全局变量中)。
  • POSIX信号量是一个整数,其值不能小于0。


命名信号量

要使用命名信号量必须要使用下列函数。

  • sem_open()函数打开或创建一个信号量并返回一个句柄以供后续调用使用,如果这个调用会创建信号量的话还会对所创建的信号量进行初始化。
  • sem_post()和sem_wait()函数分别递增和递减一个信号量值。
  • sem_getvalue()函数获取一个信号量的当前值。
  • sem_close()函数删除调用进程与它之前打开的一个信号量之间的关联关系。
  • sem_unlink()函数删除一个信号量名字并将其标记为在所有进程关闭该信号量时删除该信号量。

打开一个命名信号量

sem_open()函数创建和打开一个新的命名信号量或打开一个既有信号量。

#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>

sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);

name标识出了信号量。
oflag参数是一个位掩码,它确定了是打开一个既有信号量还是创建并打开一个新信号量。如果oflag为0,那么将访问一个既有信号量。如果在oflag中指定了O_CREAT,并且与给定的name对应的信号量不存在,那么就创建一个新的信号量。如果在oflag中同时指定了O_CREAT和O_EXCL,并且与给定的name对应的信号量已经存在,那么sem_open()就会失败。
如果sem_open()被用来打开一个既有信号量,那么调用就只需要两个参数。但如果在flags中指定了O_CREAT,那么就还需要另外两个参数:mode和value。

  • mode参数是一个位掩码,它指定了施加于新信号之上的权限。这个参数能取的位值与文件上的位值是一样的并且与open()一样,mode参数中的值会根据进程的umask来取掩码。
  • value参数是一个无符号整数,它指定了新信号量的初始值。

不管是创建一个新信号量还是打开一个既有信号量,sem_open()都会返回一个指向一个sem_t值的指针,而在后续的调用中则可以通过这个指针来操作这个信号量。
SUSv3声称当在sem_open()的返回值指向的sem_t变量的副本上执行操作时结果是未定义的。换句话说,像下面这种使用sem2的做法是不允许的。

sem_t *sp,sem2;
sp=sem_open(...);
sem2=*sp;
sem_wait(&sem2);

通过fork()创建的子进程会继承其父进程打开的所有命名信号量的引用。在fork()之后,父进程和子进程就能够使用这些信号量来同步它们的动作了。

关闭一个信号量

当一个进程打开一个命名信号量时,系统会记录进程与信号量之间的关联关系。sem_close()函数会终止这种关联关系(即关闭信号量),释放系统为该进程关联到该信号量之上的所有资源,并递减引用该信号量的进程数。

#include<semaphore.h>

int sem_close(sem_t *sem);

打开的命名信号量在进程终止或进程执行了一个exec()时会自动被关闭。
关闭一个信号量并不会删除这个信号量,而要删除信号量则需要使用sem_unlink()。

删除一个命名信号量

sem_unlink()函数删除通过name标识的信号量并将信号量标记成一旦所有进程都使用完这个信号量时就销毁该信号量。

#include<semaphore.h>

int sem_unlink(const char *name);

等待一个信号量

sem_wait()函数会递减(减小1)sem引用的信号量的值。

#include<semaphore.h>

int sem_wait(sem_t *sem);

如果信号量的当前值大于0,那么sem_wait()会立即返回。如果信号量的当前值等于0,那么sem_wait()会阻塞直到信号量的值大于0为止,当信号量大于0时该信号量值就被递减并且sem_wait()会返回。
如果一个阻塞的sem_wait()调用被一个信号处理器中断了,那么它就会失败并返回EINTR错误。

sem_trywait()函数是sem_wait()的一个非阻塞版本。

#include<semaphore.h>

int sem_trywait(sem_t *sem);

如果递减操作无法立即被执行,那么sem_trywait()就会失败并返回EAGAIN错误。

发布一个信号量

sem_post()函数递增(增加1)sem引用的信号量的值。

#include<semaphore.h>

int sem_post(sem_t *sem)l

如果在sem_post()调用之前信号量的值为0,并且其他某个进程(或线程)正在因等待递减这个信号量而阻塞,那么该进程会被唤醒,它的sem_wait()调用会继续往前执行来递减这个信号量。如果多个进程(或线程)在sem_wait()中阻塞了,并且这些进程的调度采用的是默认的循环时间分享策略,那么哪个进程会被唤醒并允许递减这个信号量是不确定的。
POSIX信号量仅仅是一种同步机制,而不是一种排队机制。

获取信号量的当前值

sem_getvalue()函数将sem引用的信号量的当前值通过sval指向的int变量返回。

#include<semaphore.h>

int sem_getvalue(sem_t *sem,int *sval);

如果一个或多个进程(或线程)当前正在阻塞以等待递减信号量的值,那么sval中的返回值将取决于实现。在Linux下,返回0。
注意在sem_getvalue()返回时,sval中的返回值可能已经过时了。


未命名信号量

未命名信号量(也被称为基于内存的信号量)是类型为sem_t并存储在应用程序分配的内存中的变量。通过将这个信号量放在由几个进程或线程共性的内存区域中就能够使这个信号量对这些进程或线程可用。
操作未命名信号量所使用的函数与操作命名信号量使用的函数是一样的。此外,还需要两个函数;

  • sem_init()函数对一个信号量进行初始化并通知系统该信号量会在进程间共享还是在单个进程中的线程间共享。
  • sem_destory()函数销毁一个信号量。

这些函数不应该被应用到命名信号量上。

未命名与命名信号量对比

  • 在线程间共享的信号量不需要名字。将一个未命名信号量作为一个共享(全局或堆是上的)变量自动会使之对所有线程可访问。
  • 在相关进程间共享的信号量不需要名字。如果一个父进程在一块共享内存区域中分配了一个未命名信号量,那么作为fork()操作的一部分,子进程会自动继承这个映射,从而继承这个信号量。
  • 如果正在构建的是一个动态数据结构(如二叉树),并且其中的每一项都需要一个关联的信号量,那么最简单的做法是在每一项中都分配一个未命名的信号量。为每一项打开一个命名信号量需要为如何生成每一项中的信号名字和管理这些名字设计一个规则。

初始化一个未命名信号量

sem_init()函数使用value中指定的值来对sem指向的未命名信号量进行初始化。

#include<semaphore.h>

int sem_init(sem_t *sem,int pshared,unsigned int value);

pshared参数表明这个信号量是在线程间共享还是在进程间共享。

  • 如果pshared等于0,那么信号量将会在调用进程中的线程间进行共享。在这种情况下,sem通常被指定成一个全局变量的地址或分配在堆上的一个变量的地址。线程共享的信号量具备进程持久性,它在进程终止时会被销毁。
  • 如果pshared不等于0,那么信号量将会在进程间共享。在这种情况下,sem必须是共享内存区域中的某个地址的位置。信号量的持久性与它所处的共享内存的持久性是一样的。由于通过fork()创建的子进程会继承其父进程的内存映射,因此进程共享的信号量会被通过fork()创建的子进程继承,这样父进程和子进程也就能够使用这些信号量来同步它们的动作了。

未命名信号量不存在相关的权限设置。对一个未命名信号量的访问将由进程在底层共享内存区域上的权限来控制。
SUSv3规定对一个已初始化过的未命名信号量进行初始化操作将会导致未定义的行为。换句话说,必须要将应用程序设计成只有一个进程或线程来调用sem_init()以初始化一个信号量。
与命名信号量一样,在sem_t变量的副本上执行操作的结果是未定义的。

销毁一个未命名信号量

sem_destroy()函数将销毁信号量sem,其中sem必须是一个之前使用sem_init()进行初始化的未命名信号量。只有在不存在进程或线程在等待一个信号量时才能够安全销毁这个信号量。

#include<semapthore.h>

int sem_destroy(sem_t *sem);

当使用sem_destroy()销毁了一个未命名信号量之后就能够使用sem_init()来重新初始化这个信号量了。
一个未命名信号量应该在其底层的内存被释放之前被销毁。

代码示例

使用一个未命名线程共享的信号量来保护对全局变量的访问。

#include <semaphore.h>
#include <pthread.h>
#include "tlpi_hdr.h"

static int glob = 0;
static sem_t sem;

static void *                   /* Loop 'arg' times incrementing 'glob' */
threadFunc(void *arg)
{
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
        if (sem_wait(&sem) == -1)
            errExit("sem_wait");

        loc = glob;
        loc++;
        glob = loc;

        if (sem_post(&sem) == -1)
            errExit("sem_post");
    }

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t1, t2;
    int loops, s;

    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;

    /* Initialize a semaphore with the value 1 */

    if (sem_init(&sem, 0, 1) == -1)
        errExit("sem_init");

    /* Create two threads that increment 'glob' */

    s = pthread_create(&t1, NULL, threadFunc, &loops);
    if (s != 0)
        errExitEN(s, "pthread_create");
    s = pthread_create(&t2, NULL, threadFunc, &loops);
    if (s != 0)
        errExitEN(s, "pthread_create");

    /* Wait for threads to terminate */

    s = pthread_join(t1, NULL);
    if (s != 0)
        errExitEN(s, "pthread_join");
    s = pthread_join(t2, NULL);
    if (s != 0)
        errExitEN(s, "pthread_join");

    printf("glob = %d\n", glob);
    exit(EXIT_SUCCESS);
}
作者:fengxinlinux 发表于2017/8/1 10:33:37 原文链接
阅读:3 评论:0 查看评论

Android面试题基础集锦《一》

$
0
0

深圳华谷网络技术有限公司

10道题值得思考

第一道题

1、Android 数据存储方式分为哪几种?

  1. SharedPreferences存储数据
    2. CotentProvider内容提供者
    3. 文件存储
    4. 数据库存储(Sqlite)
    5.网络存储

2. NDK是什么? 1. 一系列工具类的集合 2. Arm指令集 3. NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。 NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。 NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

3.Android线程间的网络通信有哪些?

共享内存(变量);文件,数据库;Handler;Java 里的 wait(),notify(),notifyAll()

4、Android横竖屏生命周期切换?

横竖屏切换生命周期

1、启动程序进入Activity界面

这里写图片描述

onCreate(初始化)–>OnStart(启动)–>OnResume(恢复)

2、旋转切换屏幕

这里写图片描述

onPause(暂停)–>OnSaveInstanceState(保存当前状态)–>OnStop(停止)–>OnDestory(销毁)–>onCreate(初始化)–>OnStart(启动)–>OnRestoreInstanceState(再次保存状态)–>OnResume(恢复)
3.Android横竖屏切换在手机开发中比较常见,很多软件在开发过程中为了避免横竖屏切换时引发不必要的麻烦,通常禁止掉横竖屏的切换。
一、在AndroidManifest.xml中设置activity中的android:screenOrientation属性值来实现。
(1)竖屏:android:screenOrientation=”portrait”
(2)横屏:android:screenOrientation=”landscape”
二、在Java代码中通过类似如下代码来设置 (不推荐这种方法,在大的app不同方向启动时会慢)
(1)竖屏: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
(2)横屏:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
三、如果要彻底禁止翻转,忽略重力感应带来的切换,(模拟器上不管用,在真机上是正确的)
(1)忽略重力:android:screenOrientation=”nosensor”
横竖屏辨识
一、在onConfigurationChanged里判断,为了onConfigurationChanged在监听屏幕方向变化有效需要以下条件
(1)AndroidManifest.xml增加权限:<uses-permission android:name="android.permission.CHANGE_CONFIGURATION"></uses-permission>
(2)AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13
(3)在AndroidManifest.xml的Activity里增加:android:configChanges="keyboard|screenSize|orientation|layoutDirection"
(4)在onConfigurationChanged(Configuration newConfig)进行判断

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(newConfig.orientation == 1)//竖屏 
if(newConfig.orientation == 2)// 横屏
}

二、因为当屏幕变为横屏的时候,系统会重调用Activity的onCreate方法可以在onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。

if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
Log.i("info","landscape"); // 横屏
} else if(this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT) {
Log.i("info","portrait"); // 竖屏
}

注意:该方法是在AndroidManifest.xml中不设置onConfigurationChanged才能重走生命周期

5、页面上现有ProgressBar控件progressBar,请用书写线程以10秒的的时间完成其进度显示工作。

          //得到progeressBar的最大长度  
                int progressBarMax = progressBar.getMax();  
                try {  
                    //progressBar当前的长度没有达到他的最长度,让循环一直进行  
                    while (progressBarMax != progressBar.getProgress()) {  
                        //拿到一个每次前进的进度值,因为是要10s完成,所以分为10份  
                        int stepProgress = progressBarMax / 10;  
                        //progressBar当前的进度值  
                        int currentProgress = progressBar.getProgress();  
                        //让progressBar进度为每次前进最大值的十分之一  
                        progressBar.setProgress(currentProgress + stepProgress);  
                        //前进一次,睡眠一秒  
                        Thread.sleep(1000);  
                    }  

                } catch (Exception e) {  
                    e.printStackTrace();  
                }  

            }  

6、Android如何避免OOM?

1)使用更加轻量的数据结构

例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,下图演示了HashMap的简要工作原理,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。

2)避免在Android里面使用Enum

3)减小Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:

inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

decode format:解码格式,选择ARGB_8888/RGB_565/ARGB_4444/ALPHA_8,存在很大差异。

4)使用更小的图片

在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。

在Android上面最常用的一个缓存算法是LRU(Least Recently Use)

5)复用系统自带的资源

Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

6)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用

7)Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。

利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率上的提升(3.0以及4.4以后存在一些使用限制上的差异)。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。

8)避免在onDraw方法里面执行对象的创建

类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

9)StringBuilder

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

避免对象的内存泄露

内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,gc就会更容易被触发,容易出现内存抖动,从而引起性能问题。

10)注意Activity的泄漏

通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:

内部类引用导致Activity的泄漏

最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。

内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。

11)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。

12)注意临时Bitmap对象的及时回收

虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。

需要特别留意的是Bitmap类里面提供的createBitmap()方法:

这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。

13)注意WebView的泄漏

Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

14)资源文件需要选择合适的文件夹进行存放

我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。

15)谨慎使用static对象

因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象。

16)特别留意单例对象中不合理的持有

虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。

17)珍惜Services资源

18)优化布局层次,减少内存消耗

越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。

19)谨慎使用“抽象”编程

很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。

20)谨慎使用多进程

使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术。

7、Android UI如何刷新View?

第一,利用子线程发消息刷新UI。
第二,利用异步任务更新UI
第三,利用配置文件+activity的生命周期方法刷新UI。

8、面试题有四棵树如何让相邻的书两个之间的距离保持相等(腾讯面试题)

正三菱锥
扔一个坑里
平面解决不了的问题换种思维空间?

9、动画的分类?

动画分类

View动画、帧动画、属性动画

View动画包括:平移、旋转、缩放、透明度,View动画是一种渐近式动画
帧动画:图片切换动画
属性动画:通过动态改变对象的属性达到动画效果
View动画

继承自Animation,四个动画效果实现类:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation

10、自定义View?

这里写图片描述

1 2 3表示随机颜色值 90%表示进度条 上面是个五边形 如何实现一直连续的改变颜色值并不断更新进度?

作者:qq_15950325 发表于2017/7/31 22:59:58 原文链接
阅读:205 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>