Android面试知识点

xiaoxiao2021-02-28  10

自我介绍: 您好,我是xxx,从事android开发也有好几年了,总共呆过两家公司,第一家是xx,干了1年,在这家公司做过3个项目,刚开始的时候主要写一些需求文档,用xmind整理项目功能,做一些简单的界面,大家都知道搞it的技术很重要,所以我在完成自己的工作之余就经常看其他模块的实现代码,尽快提升自己的技术,也积极参与同事的讨论,提出一些自己的想法,很快业务也熟悉了;我记得在第二个项目的时候就能独立完成一些模块的功能,(做完第三个项目的时候/xxxx年xx月的时候/一年后)后来为了有更好的发展,就来到了第二家公司;这家公司对我的提升很多,项目很多,做了5、6个吧,包括电商类的,金融类的,教育类的,医疗类的都有,其中大部分是团队开发,也有独立开发的,比如哪个哪个,在团队开发中,我主要担任的也是核心开发人员,负责框架的搭建,需求的整理和分析,项目任务的安排和进度的把控,及项目规范性和性能的把控;这家公司成长很多,挺感谢公司的,不过现在做的项目和之前的业务都差不多,公司组织架构也比较稳定了,觉得发展到了一个瓶颈,加上现在移动互联发展这么快,新技术天天在更新,智能家居,虚拟现实,vr,直播都是比较火的方向,我觉得也是以后的发展方向,it行业不跟着时代跟着技术进步,迟早会被淘汰,最近考虑了一下,还是决定离职,换个环境,周围的同事朋友也有劝我不要这么做的,我是一个不喜欢安逸的人,老这样也没意思,也为了以后的发展还是要大胆的尝试探索,投了一下简历,也算是和咱公司有缘分,就到这来面试了,说了这么多,口也渴了,停顿一秒。。。。面试官该说话了(当然前面随时会打断),应对问题,面试官一般问一些公司情况,个人情况,就会让介绍项目,那就开始项目介绍节奏。。。,如果没有让介绍项目,谈的不多,有空档就主动介绍项目(不要冷场,挺尴尬的), 项目介绍:这个项目是6月份开始做的,9月份第一版上线, 1.准备好录音笔,模拟面试的时候就使用(真正去面试的时候,第一次都会忘了录,或录音失败),面试录音诊断是找到面试问题的唯一利器 2.学历证(本科)和公司章提前买好,不要到了入职还没有,影响得之不易的offer,加急制作,费用高 3.改简历,投简历,开始每天150份,智联,51job,拉钩,boss,猎聘,100offer等,开始面试电话可能不多,耐心等待,如果投完900家,还是没有什么电话,可以重新注册一个账号再投 4.招聘信息共享,待会面试题,一个没成,后续的补上,有备而去,很重要 5.到了面试地点,去早了直接进去不要等,不能按时到,提前和人事说一下(如果能联系上) 6.见到了人事,寒暄几句(天冷天热,过来快慢,是否堵车,公司情况(位置好坏,好找不好找,办公区环境(大,宽敞,干净))),恰当的夸夸她(根据情况,不要生硬),询问技术面试官的职位和姓,见到时好打招呼,一定要起身,男的主动握手(女的就不要了,除非她先伸手) 7.如果两个或以上,确定哪个是头,主要和他交流,其他适当照顾到 8.自我介绍,介绍项目时,一定要自然,在回顾过去,眼睛可以平时或向上一点,不能左右顾盼 技术升级:算法,底层,项目技术要精(项目也许不在多),框架洗讲,直播,sdk开发,自定义view,RxJava,reactnative,插件化 1.下拉刷新时头部导航条的透明度会随着scrollview的滑动距离改变而改变,滑动到一定距离时,界面右下方会出现一个小火箭,点击它,界面会有一个置顶功能。首先我会自定义一个组件,继承LinearLayout,添加一个header view,保证是第一个添加到linearlayout的最上端,设置topMargin的值为负的header View高度,即将其隐藏在最上方,之后需要添加footer view,由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏。下来刷新和上拉加载都要进行手指滑动距离的判断,当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态,footer 准备刷新,手指移动过程,还没有释放移动footer view高度同样和移动header view高度是一样,都是通过修改header view的topmargin的值来达到。为了实现偏移控制,一般自定义View/ViewGroup都需要重载computeScroll()方法,我会判断scrollview的滑动距离动态去给导航条的背景设置透明度,滑动渐变的效果,当滑到一定距离时,我会把小火箭显示出来,点击是会调用scrollview的scrollTo()方法,实现置顶功能。 2.图片的缓存处理 在加载图片的时候,如果图片比较大时而界面上只需要显示一小块,那么会造成内存浪费,继续加载更多也可能会出现内存溢出,这时使用缓存技术是必备的,这里我用的是IamgeLoader的LruCache类,首先将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。首先自定义一个继承自ScrollView的类,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载图片。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。另外,为了保证图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。 断点续传: 1:当用户点击下载按钮是就要把文件路径的信息传给后台由service的onstartcom的方法接受穿过来的参数,由于service和activity都是主线程不能做耗时操作所以要开启一个子线程去做耗时操作网络下载和把数据写到本地文件并且把进度信息本地文件保存到数据库并把进度传给activity通过广播发送网络文件 网络下载的关键点: 1:获取网络文件的长度 2:在本地创建一个文件,设置其长度 3:从数据库中获取上次下载的进度 4:从上次下载的位置下载数据,同时保存进度到数据库 5将下载进度回传activity 6:下载完成后删除下载信息 3.流式布局的实现 自定义ViewGroup,重点重写下面两个方法 1)onMeasure:测量子view的宽高,设置自己的宽和高 2)onLayout:设置子view的位置,onMeasure:根据子view的布局文件中属性,来为子view设置测量模式和测量值 当这个流式布局在被加载如内存并显示在屏幕上这一过程中,首先会调用view.measure(w,h)这个方法,表示测量view的宽度与高度,其中参数w与h分别表示这个控件的父控件的宽高。在view.measure()方法的调用过程中又会调用view本身的一个回调方法,onMeasure(),这个是view自身的一个回调方法,用于让开发者在自定义View的时候重新计算自身的大小。一般会在这个方法中循环遍历,计算出这个控件的全部子孙控件的宽高。在View的宽高计算完成以后,考虑将这个控件显示到屏幕的指定位置上,此时view的onLayout()方法会被调用。 一般同时会在这个方法中计算出全部子孙控件在这个控件中的位置。在onMeasure()中首先通过循环,遍历这个控件的所有子控件,同时调用子控件的measure()方法,这时measure方法的两个参数是控件能给这个子控件的最大宽高这里getChildMeasureSpec()方法的作用是用来计算一个合适子视图的尺寸大小(宽度或者高度),结合我们从子视图的LayoutParams所给出的MeasureSpec信息来获取最合适的结果。比如,如果这个View知道自己的大小尺寸,并且子视图的大小恰好跟父窗口一样大,父窗口必须用给定的大小去layout子视图,通过循环遍历,控制每个item子控件的显示位置,如果当前行还能放得下一个item,就放到当前行,如果放不下就放到下一行的最左边。 4.支付功能: 微信支付: 1)从微信开放平台上下载微信支付的SDK,使用其libammsdk.jar包。 2)在微信开放平台注册我们的应用(应用需要填写包名,应用的签名等)可以获取到APPID和APPSecret,然后申请开通支付功能,提交企业以及银行账户资料等待审核,审核通过了在线签订合同,之后就可以进行微信支付了,同时商户会收到邮件获得财付通商户账户,支付秘钥key,财付通权限秘钥,财付通商户标示id等开发关键数据。 3)预付订单:当用户确定购买商品之后,首先请求微信服务器获取access_tocken,提交预付订单package,添加签名,提交预付订单后会获取微信服务器返回的prepayid,接着把请求参数返回给手机端。 4)发起支付:调用api.sendReq(req);发起支付,发起支付的时候需要传入支付相关信息,相关信息以KEY-VALUE键值对方式用&拼接组成。例如:订单信息(订单号,交易付款时间,购买数量等),商品信息(商品名称,商品单价,商品描述等),支付信息(支付金额等)和notify_url(这个是通知地址,也就是微信的服务器把支付后成功与否等结果信息同步给我们公司服务器时候使用的地址)等。还需要拼接一个重要的签名参数sign,sign的值是上述这些交易信息MD5加密签名后的结果。 5)支付信息提交后会提交到微信的服务器,微信服务器完成整个支付功能后会给用户(客户端和服务器分别提示)成功或者失败的提示。 支付宝支付: 1)首先从支付宝平台下载SDK,使用其中的alipay.jar包。 2)当用户确定购买商品之后,首先生成订单,我们订单生成的规则就是用两个2-9的随机数+当前时间的毫秒值作为订单号。 3)调用支付宝的接口:调用支付宝的支付方法(方法Alipay.pay(String info)),支付方法需要传入支付相关信息,相关信息以KEY-VALUE键值对方式用&拼接组成。订单信息(订单号,交易付款时间,购买数量等),商品信息(商品名称,商品单价,商品描述等),支付信息(买方,卖方支付宝账号信息,支付金额等)和notify_url(这个是通知地址,也就是支付宝的服务器把支付后成功与否等结果信息同步给我们公司服务器时候使用的地址)等。还需要拼接一个重要的签名参数sign,sign的值是上述这些交易信息RSA签名后的结果。 4)支付信息提交后会提交到支付宝的服务器,支付宝的服务器完成整个支付功能后会给用户成功或者失败的提示界面。这个过程都是支付宝的功能。支付宝支付完成后,也会把信息同步给我们的服务器(也就是通过前面的notify_url这个地址)。 5)我们的服务器处理完毕之后,调用pay方法的时候其实也会接到返回值。返回值是json格式里面有很多信息,我们主要从中取结果状态码(resultStatus),根据状态码的数值判断支付是否成功,除了支付宝本身的提示界面以外,我们也会给用户一个我们自己的提示。 5.xmpp实现聊天功能: 我项目中即时通讯主要使用XMPP实现的,它有一个开源项目androidpn,使用的时候客户端要用到asmack.jar包,这个jar包提供了通讯的功能,主要使用jar包中的XMPPConnection类。主要实现如下: 1)使用XMPPConnection类与服务器openfire建立持久连接 2)在持久连接基础上进行用户注册(用户注册之后服务器就会记录该用户的信息,下次就能够识别) 3)在连接基础上进行用户登录认证(登录之后服务端会知道当前用户的状态)服务端只要记录了该用户的信息,就可以往用户手机上发送推送的信息了。 实现通讯功能主要是使用jar包中的 Chat 类完成信息的发送,发送信息的时候要注意的是用户的格式是用户名称@域/命名空间(例如:要给loupengfei聊天的话地址为:loupengfei@127.0.0.1/AndroidpnClient),这样服务端就能够把信息转给响应名称的人。 接收信息: 1)在用户建立连接的时候就定义了一个指定命名空间的解析器provider(负责解析服务端发过来的xml格式的消息) 2)同时也注册一个包监听器PacketListener,当有信息发送过来的时候我们定义的解析器会收到信息,在解析中使用PULL解析方式把xml中信息解析出来然后封装成我们自定义的一个IQ的子类(就是消息Bean),然后带着消息进入到包监听类中,在包监听类中结合handler把聊天内容等更新到UI上。 6.自定义圆形头像: 1)首先通过setImageBitmap()方法设置图片Bitmap 2)进入构造函数CircleImageView()获取自定义参数,以及调用setup()函数 3)进入setup()函数(非常关键),进行图片画笔边界画笔(Paint)一些重绘参数初始化:构建渲染器BitmapShader用Bitmap来填充绘制区域,设置样式和内外圆半径计算等,以及调用updateShaderMatrix()函数和 invalidate()函数 4)进入updateShaderMatrix()函数,计算缩放比例和平移,设置BitmapShader的Matrix参数等 5)触发ondraw()函数完成最终的绘制,使用配置好的Paint先画出绘制内圆形来以后再画边界圆形。 7.瀑布流照片墙的实现 瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙。 8.屏幕适配 1)比重适配,控件在其父布局中的显示权重,一般用于线性布局中,如果控件过多,且显示比例也不相同的时候,控制起来就比较麻烦了,毕竟反比不是那么好确定的。 2)多个布局文件适配,就是对不同大小的屏幕提供不同的layout。比如横竖屏的切换,可以在res目录下建立layout-port和layout-land两个目录,里面分别放置竖屏和横屏两种布局文件,以适应对横屏竖屏自动切换。 3)多个drawable适配(图片的适配),就是对不同密度的屏幕提供不同的图片。在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面。应尽量使用.9格式的图片,如需对密度为low的屏幕提供合适的图片,需新建文件夹drawable-ldpi/,并放入合适大小的图片。相应的,medium对应drawable-mdpi/,high对应drawable-hdpi/,extra high对应drawable-xhdpi/。 4)多个values适配,为了优质的用户体验,需要去针对不同的dpi设置,编写多套数值文件,要把生成的所有values文件夹放到res目录下,当设计师把UI高清设计图给你之后,你就可以根据设计图上的尺寸,以某一个分辨率的机型为基础,找到对应像素数的单位,然后设置给控件。 9.内存优化 1)静态变量引起内存泄露 静态变量是类相关的变量,它的生命周期是从这个类被声明,到这个类彻底被垃圾回收器回收才会被销毁。所以,一般情况下,静态变量从所在的类被使用开始就要一直占用着内存空间,直到程序退出。如果不注意,静态变量引用了占用大量内存的资源,造成垃圾回收器无法对内存进行回收,就可能造成内存的浪费。 2)使用Application的Context 在Android中,Application Context的生命周期和应用的生命周期一样长,而不是取决于某个Activity的生命周期。如果想保持一个长期生命的对象,并且这个对象需要一个 Context,就可以使用Application对象。可以通过调用Context.getApplicationContext()方法或者 Activity.getApplication()方法来获得Application对象。 3)及时关闭资源 Cursor是Android查询数据后得到的一个管理数据集合的类。正常情况下,如 果我们没有关闭它,系统会在回收它时进行关闭,但是这样的效率特别低。如果查询得到的数据量较小时还好,如果Cursor的数据量非常大,特别是如果里面 有Blob信息时,就可能出现内存问题。所以一定要及时关闭Cursor。 4)避免使用浮点数 通常的经验是,在Android设备中,浮点数会比整型慢两倍。 5)使用实体类比接口好 按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不能确定,先避免使用Map,剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API常常会牺牲一些性能) 6)避免使用枚举 枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。 使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。 7)使用Bitmap及时调用recycle() Bitmap是内存消耗大户,绝大多数的OOM崩溃都是在操作Bitmap时产生的,我们需要根据需求去加载图片的大小,例如在列表中仅用于预览时加载缩略图,只有当用户点击具体条目想看详细信息的时候,这时另启动一个fragment/activity/对话框等等,去显示整个图片。直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃,使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。 8)对Adapter进行优化 如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存 空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队 列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。 如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引 用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱 引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联 的引用队列中。 弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。 listview如何优化? 1)convertView复用,对convetView进行判空,当convertView不为空时重复使用,为空则初始化,从而减少了很多不必要的View的创建 2)定义一个ViewHolder,封装Listview Item条目中所有的组件,将convetView的tag设置为ViewHolder,不为空时通过ViewHolder的属性获取对应组件即可 3)当ListView加载数据量较大时可以采用分页加载和图片异步加载 handler的原理? Handler主要用于线程间的通信。 一个Handler允许发送和处理Message和Runable对象,UI主线程会自动分配一个Looper(消息轮询器),每个Looper中封装着MessageQueue(消息队列),遵循先进先出原则。Looper负责不断的从自己的消息队列里取出队头的任务或消息执行。一般是在子线程执行完耗时操作之后,通过Handler的sendMessage或post方法将Message和Runable对象传递给MessageQueue,而且在这些对象离开MessageQueue时,Handler负责执行他们(用到handleMessage方法,主要执行刷新UI的代码)。 其中Message类就是定义了一个信息,这个信息中包含一个描述符和任意的数据对象,这个信息被用来传递给Handler.Message对象提供额外的两个int域和一个Object域。 Tcp和Udp的区别和联系? TCP---传输控制协议,提供的是面向连接、可靠的字节流服务,传输数据前经过“三次握手”建立连接,保证数据传输的可靠性,但效率比较低。一般用于对于数据传输安全性较高的场合。 UDP---用户数据报协议,是一个简单的面向数据报的运输层协议,面向无连接。UDP不提供可靠性,数据传输可能发生错序,丢包,但效率较高。一般用于对于实时性要求较高的场合。 方法的重写和重载? 重写是子类的方法覆盖父类的方法,要求方法名和参数都相同 。 重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数。 ArrayList和LinkedList区别? 存数据,ArrayList数组存储数据,索引值以下标来搜索,查询比较方,删除增加比较麻烦,但是linkedList以链表式存储数据,对于增删比较方便。 Final ,finally,finalized,区别? final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 finally是异常处理语句结构的一部分,表示总是执行。 finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。 加载大图、多图如何防止内存溢出? 大图:解析每张图片的时候都先检查一下图片的大小,确保图片不会超出内存大小,如果只需要加载一张比较小的图片展示出来,显然加载大图的原图是不值得的,可以通过设置BitmapFactory.Options中inSampleSize的值实现图片压缩。 多图:屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM,可以使用LruCache类,主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。 如何人解决anr?forceclose(强制关闭) (1) AsyncTask,其中doInBackground()和onPostExecute(Result)两个方法非常重要 doInBackground() 这个方法运行在后台线程中,主要负责执行那些很耗时的操作,如访问网络。该方法必须重写。 onPostExecute(Result) 这个方法运行于UI主线程,在doInBackground(Params…)方法执行后调用,该方法用于接收后台任务执行后返回的结果,刷新UI显示。 (2.)子线程 + handler 在子线程中执行完耗时操作需要刷新UI时,通过handler.sendMessage()发消息给主线程, 然后在主线程Handler中的handleMessage()方法中执行刷新UI操作 android中内存如何优化? 1)谨慎使用静态变量 2)使用Application的Context 3)及时关闭资源(io流、cursor对象) 4)使用Bitmap及时调用recycle() 5)对Adapter进行优化 冒泡排序: for (int i = 0; i < score.length-1; i++){ //最多做n-1趟排序 for(int j = 0 ;j < score.length - i - 1; j++){ if(score[j] < score[j + 1]){ //把小的值交换到后面 int temp = score[j]; score[j] = score[j + 1]; score[j + 1] = temp; } } } 微信支付: 1)从微信开放平台上下载微信支付的SDK,使用其libammsdk.jar包。 2)在微信开放平台注册我们的应用(应用需要填写包名,应用的签名等)可以获取到APPID和APPSecret,然后申请开通支付功能,提交企业以及银行账户资料等待审核,审核通过了在线签订合同,之后就可以进行微信支付了,同时商户会收到邮件获得财付通商户账户,支付秘钥key,财付通权限秘钥,财付通商户标示id等开发关键数据。 3)预付订单:当用户确定购买商品之后,首先请求微信服务器获取access_tocken,提交预付订单package,添加签名,提交预付订单后会获取微信服务器返回的prepayid,接着把请求参数返回给手机端。 4)发起支付:调用api.sendReq(req);发起支付,发起支付的时候需要传入支付相关信息,相关信息以KEY-VALUE键值对方式用&拼接组成。例如:订单信息(订单号,交易付款时间,购买数量等),商品信息(商品名称,商品单价,商品描述等),支付信息(支付金额等)和notify_url(这个是通知地址,也就是微信的服务器把支付后成功与否等结果信息同步给我们公司服务器时候使用的地址)等。还需要拼接一个重要的签名参数sign,sign的值是上述这些交易信息MD5加密签名后的结果。 5)支付信息提交后会提交到微信的服务器,微信服务器完成整个支付功能后会给用户(客户端和服务器分别提示)成功或者失败的提示。 支付宝支付: 1)首先从支付宝平台下载SDK,使用其中的alipay.jar包。 2)当用户确定购买商品之后,首先生成订单,我们订单生成的规则就是用两个2-9的随机数+当前时间的毫秒值作为订单号。 3)调用支付宝的接口:调用支付宝的支付方法(方法Alipay.pay(String info)),支付方法需要传入支付相关信息,相关信息以KEY-VALUE键值对方式用&拼接组成。订单信息(订单号,交易付款时间,购买数量等),商品信息(商品名称,商品单价,商品描述等),支付信息(买方,卖方支付宝账号信息,支付金额等)和notify_url(这个是通知地址,也就是支付宝的服务器把支付后成功与否等结果信息同步给我们公司服务器时候使用的地址)等。还需要拼接一个重要的签名参数sign,sign的值是上述这些交易信息RSA签名后的结果。(加密就是数字签名) 4)支付信息提交后会提交到支付宝的服务器,支付宝的服务器完成整个支付功能后会给用户成功或者失败的提示界面。这个过程都是支付宝的功能。支付宝支付完成后,也会把信息同步给我们的服务器(也就是通过前面的notify_url这个地址)。 5)我们的服务器处理完毕之后,调用pay方法的时候其实也会接到返回值。返回值是json格式里面有很多信息,我们主要从中取结果状态码(resultStatus),根据状态码的数值判断支付是否成功,除了支付宝本身的提示界面以外,我们也会给用户一个我们自己的提示。 xmpp实现聊天功能: 我项目中即时通讯主要使用XMPP实现的,它有一个开源项目androidpn,使用的时候客户端要用到asmack.jar包,这个jar包提供了通讯的功能,主要使用jar包中的XMPPConnection类。主要实现如下: 1)使用XMPPConnection类与服务器建立持久连接 2)在持久连接基础上进行用户注册(用户注册之后服务器就会记录该用户的信息,下次就能够识别) 3)在连接基础上进行用户登录认证(登录之后服务端会知道当前用户的状态)服务端只要记录了该用户的信息,就可以往用户手机上发送推送的信息了。 实现通讯功能主要是使用jar包中的Chat类完成信息的发送,发送信息的时候要注意的是用户的格式是用户名称@域/命名空间(例如:要给loupengfei聊天的话地址为:loupengfei@127.0.0.1/AndroidpnClient),这样服务端就能够把信息转给响应名称的人。 接收信息: 1)在用户建立连接的时候就定义了一个指定命名空间的解析器provider(负责解析服务端发过来的xml格式的消息) 2)同时也注册一个包监听器PacketListener,当有信息发送过来的时候我们定义的解析器会收到信息,在解析中使用PULL解析方式把xml中信息解析出来然后封装成我们自定义的一个IQ的子类(就是消息Bean),然后带着消息进入到包监听类中,在包监听类中结合handler把聊天内容等更新到UI上。 四大组件详细介绍: Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。 BroadcastReceive广播接收器: Service 是一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。 android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。这些数据可以存储在文件系统中、在一个SQLite数据库、或以任何其他合理的方式, 其他应用可以通过ContentResolver类(见ContentProviderAccessApp例子)从该内容提供者中获取或存入数据.(相当于在应用外包了一层壳), 只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中 它的好处:统一数据访问方式。 java中异常处理: Activity的启动模式: standard:默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。 singleTop:可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。 singleTask:只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。 如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个Activity,singleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中。 singleInstance:只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。 线程:被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元 webview的辅助类: 作用: 通过url地址加载互联网或本地网页 WebSettings 设置WebView的一些属性、状态等,例如允许使用javascript,允许使用缓存,允许使用内置的缩放组件 WebViewClient 主要帮助WebView处理各种通知、请求事件(例如,点击链接时候如何显示界面,页面开始加载,加载完毕之后有何动作等) WebChromeClient 辅助WebView处理Javascript的对话框、网站图标、网站Title、加载进度等 java的反射机制原理以及应用场合: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有 属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态 获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 OSI七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层 常用的存储方式有哪些? SQLite: SQLite是一个轻量级的数据库,支持基本SQL语法,是常被采用的一种数据存储方式。Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的API。 SharedPreference: 除SQLite数据库外,另一种常用的数据存储方式,其本质就是一个xml文件,常用于存储较简单的参数设置。 File: 即常说的文件(I/O)存储方法,常用语存储大数量的数据,但是缺点是更新数据将是一件困难的事情。 ContentProvider: Android 系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用间的是互相私密的,所以此存储方式较少使用,但是其又是必不可少的一种存储方式。 例如音频,视频,图片和通讯录,一般都可以采用此种方式进行存储。每个ContentProvider都会对外提供一个公共的URI(包装成Uri对 象),如果应用程序有数据需要共享时,就需要使用ContentProvider为这些数据定义一个URI,然后其他的应用程序就通过 Content Provider传入这个URI来对数据进行操作。 网络存储: 从网络读取数据和写入数据。 Android提供了通过网络来实现数据的存储和获取的方法。 我们可以调用WebService返回的数据或是解析HTTP协议实现网络数据交互。 package org.rsa.util; import javax.crypto.Cipher; import java.security.*; import java.security.spec.RSAPublicKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.io.*; import java.math.BigInteger; /** * RSA 工具类。提供加密,解密,生成密钥对等方法。 * 需要到http://www.bouncycastle.org下载bcprov-jdk14-123.jar。 * RSA加密原理概述 * RSA的安全性依赖于大数的分解,公钥和私钥都是两个大素数(大于100的十进制位)的函数。 * 据猜测,从一个密钥和密文推断出明文的难度等同于分解两个大素数的积 * =================================================================== * (该算法的安全性未得到理论的证明) * =================================================================== * 密钥的产生: * 1.选择两个大素数 p,q ,计算 n=p*q; * 2.随机选择加密密钥 e ,要求 e 和 (p-1)*(q-1)互质 (最大公因数为1的两个数叫互质数) * 3.利用 Euclid 算法计算解密密钥 d , 使其满足 e*d = 1(mod(p-1)*(q-1)) (其中 n,d 也要互质) * 4:至此得出公钥为 (n,e) 私钥为 (n,d) * =================================================================== * 加解密方法: * 1.首先将要加密的信息 m(二进制表示) 分成等长的数据块 m1,m2,...,mi 块长 s(尽可能大) ,其中 2^s<n * 2:对应的密文是: ci = mi^e(mod n) * 3:解密时作如下计算: mi = ci^d(mod n) * =================================================================== * RSA速度 * 由于进行的都是大数计算,使得RSA最快的情况也比DES慢上100倍,无论 是软件还是硬件实现。 * DES全称为Data Encryption Standard,即数据加密标准 * 速度一直是RSA的缺陷。一般来说只用于少量数据 加密。 *文件名:RSAUtil.java<br> *@author 董利伟<br> *版本:<br> *描述:<br> *创建时间:2008-9-23 下午09:58:16<br> *文件描述:<br> *修改者:<br> *修改日期:<br> *修改描述:<br> */ public class RSAUtil { //密钥对 private KeyPair keyPair = null; /** * 初始化密钥对 */ public RSAUtil(){ try { this.keyPair = this.generateKeyPair(); } catch (Exception e) { e.printStackTrace(); } } /** * 生成密钥对 * @return KeyPair * @throws Exception */ private KeyPair generateKeyPair() throws Exception { try { //KeyPairGenerator 类用于生成公钥和私钥对。密钥对生成器是使用 getInstance 工厂方法 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider()); //这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低 final int KEY_SIZE = 1024; //每个提供者都必须提供(并记录)默认的初始化,以防客户端没有显式初始化 keyPairGen.initialize(KEY_SIZE, new SecureRandom());//随机源 KeyPair keyPair = keyPairGen.genKeyPair(); return keyPair; } catch (Exception e) { throw new Exception(e.getMessage()); } } /** * 生成公钥 * @param modulus * @param publicExponent * @return RSAPublicKey * @throws Exception */ private RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws Exception { KeyFactory keyFac = null; try { keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); } catch (NoSuchAlgorithmException ex) { throw new Exception(ex.getMessage()); } RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent)); try { return (RSAPublicKey) keyFac.generatePublic(pubKeySpec); } catch (InvalidKeySpecException ex) { throw new Exception(ex.getMessage()); } } /** * 生成私钥 * @param modulus * @param privateExponent * @return RSAPrivateKey * @throws Exception */ private RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception { KeyFactory keyFac = null; try { keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); } catch (NoSuchAlgorithmException ex) { throw new Exception(ex.getMessage()); } RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent)); try { return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec); } catch (InvalidKeySpecException ex) { throw new Exception(ex.getMessage()); } } /** * 加密 * @param key 加密的密钥 * @param data 待加密的明文数据 * @return 加密后的数据 * @throws Exception * * 用于将Cipher初始化为解密模式的常量 public final static int DECRYPT_MODE 用于将Cipher初始化为加密模式的常量 public final static int ENCRYPT_MODE */ public byte[] encrypt(Key key, byte[] data) throws Exception { try { Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); //完成用于加密或是解密操作的初始化 cipher.init(Cipher.ENCRYPT_MODE, key); //获得加密块大小,如:加密前数据为128个byte,而key_size=1024 加密块大小为127 byte,加密后为128个byte; //因此共有2个加密块,第一个127 byte第二个为1个byte int blockSize = cipher.getBlockSize();//获得加密块加密后块大小 int outputSize = cipher.getOutputSize(data.length);//获得输出缓冲区字节长度: int leavedSize = data.length % blockSize; int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize; byte[] raw = new byte[outputSize * blocksSize]; int i = 0; while (data.length - i * blockSize > 0) { if (data.length - i * blockSize > blockSize) // 按单部分操作加密或解密数据,或者结束一个多部分操作。 // 调用doFinal方法将会重置Cipher对象到使用init进行初始化时的状态,就是说,Cipher对象被重置,使得可以进行更多数据的加密或解密, cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize); else cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize); //这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中 //,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。 i++; } return raw; } catch (Exception e) { throw new Exception(e.getMessage()); } } /** * 解密 * @param key 解密的密钥 * @param raw 已经加密的数据 * @return 解密后的明文 * @throws Exception */ public byte[] decrypt(Key key, byte[] raw) throws Exception { try { Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); cipher.init(cipher.DECRYPT_MODE, key); int blockSize = cipher.getBlockSize(); //字节数组流: ByteArrayOutputStream bout = new ByteArrayOutputStream(64); int j = 0; while (raw.length - j * blockSize > 0) { bout.write(cipher.doFinal(raw, j * blockSize, blockSize)); j++; } return bout.toByteArray(); } catch (Exception e) { throw new Exception(e.getMessage()); } } /** * 返回公钥 * @return * @throws Exception */ public RSAPublicKey getRSAPublicKey() throws Exception{ //获取公钥 RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic(); //获取公钥系数(字节数组形式) byte[] pubModBytes = pubKey.getModulus().toByteArray(); //返回公钥公用指数(字节数组形式) byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray(); //生成公钥 RSAPublicKey recoveryPubKey = this.generateRSAPublicKey(pubModBytes,pubPubExpBytes); return recoveryPubKey; } /** * 获取私钥 * @return * @throws Exception */ public RSAPrivateKey getRSAPrivateKey() throws Exception{ //获取私钥 RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate(); //返回私钥系数(字节数组形式) byte[] priModBytes = priKey.getModulus().toByteArray(); //返回私钥专用指数(字节数组形式) byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray(); //生成私钥 RSAPrivateKey recoveryPriKey = this.generateRSAPrivateKey(priModBytes,priPriExpBytes); return recoveryPriKey; } } 测试 package org.yanzi.websafe; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.md5.util.MD5Util; import org.rsa.util.RSAUtil; public class TestCode { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub /****************************RSA加密解密测试********************************/ try { RSAUtil rsa = new RSAUtil(); String str = "yanzi1225627"; RSAPublicKey pubKey = rsa.getRSAPublicKey();//返回公钥 RSAPrivateKey priKey = rsa.getRSAPrivateKey();//获取私钥 //加密的时候使用公钥,和需要加密文字的byte[] byte[] enRsaBytes = rsa.encrypt(pubKey,str.getBytes()); String enRsaStr = new String(enRsaBytes, "UTF-8"); System.out.println("加密后==" + enRsaStr); System.out.println("解密后==" + new String(rsa.decrypt(priKey, rsa.encrypt(pubKey,str.getBytes())))); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } /************************************MD5加密测试*****************************/ // String srcString = "yanzi1225627"; // System.out.println("MD5加密后 = " + MD5Util.getMD5String(srcString)); } } package org.md5.util; import java.security.MessageDigest; public class MD5Util { public final static String getMD5String(String s) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; try { byte[] btInput = s.getBytes(); //获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); //使用指定的字节更新摘要 mdInst.update(btInput); //获得密文 byte[] md = mdInst.digest(); // 这个方法是加密后返回的byte数组 //把密文转换成十六进制的字符串形式 int j = md.length;// 记录md的长度 char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // hexDigits 数组中对应的十六进制数放入str中 str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { e.printStackTrace(); return null; } } } 所谓流媒体是指采用流式传输的方式在Internet播放的媒体格式。 流媒体又叫流式媒体,它是指商家用一个视频传送服务器把节目当成数据包发出,传送到网络上。用户通过解压设备对这些数据进行解压后,节目就会像发送前那样显示出来。 流媒体(Streaming Media)的出现极大地方便了人们的工作和生活。在地球的另一端,某大学的课堂上,某个教授正在兴致盎然地传授一门你喜欢的课程,想听?太远!放弃?可惜!没关系,网络时代能满足你的愿望。在网络上找到该在线课程,课程很长,但没关系,只管点击播放,教授的身影很快出现在屏幕上,课程一边播放一边下载,虽然远在天涯,却如亲临现场!除了远程教育,流媒体在视频点播、网络电台、网络视频等方面也有着广泛的应用。 流媒体,又叫流式媒体,是边传边播的媒体,是多媒体的一种。边传边播是指媒体提供商在网络上传输媒体的"同时",用户一边不断地接收并观看或收听被传输的媒体。"流"媒体的"流"指的是这种媒体的传输方式(流的方式),而并不是指媒体本身。 基本信息 中文名称 流媒体 外文名称 Streaming media 目录 1媒体技术 2基本介绍 3技术应用 4技术问题 5常用格式 6传输协议 7播放方式 折叠编辑本段媒体技术 折叠流式传输的基础 在网络上传输音/视频等多媒体信息,目前主要有下载和流式传输两种方案。A/V文件一般都较大,所以需要的存储容量也较大;同时由于网络带宽的限制,下载常常要花数分钟甚至数小时,所以这种处理方法延迟也很大。流式传输时,声音、影像或动画等时基媒体由音视频服务器向用户计算机的连续、实时传送,用户不必等到整个文件全部下载完毕,而只需经过几秒或十数秒的启动延时即可进行观看。当声音等时基媒体在客户机上播放时,文件的剩余部分将在后台从服务器内继续下载。流式不仅使启动延时成十倍、百倍地缩短,而且不需要太大的缓存容量。流式传输避免了用户必须等待整个文件全部从Internet上下载才能观看的缺点。 流媒体指在Internet/Intranet中使用流式传输技术的连续时基媒体,如:音频、视频或多媒体文件。流式媒体在播放前并不下载整个文件,只将开始部分内容存入内存,流式媒体的数据流随时传送随时播放,只是在开始时有一些延迟。流媒体实现的关键技术就是流式传输。 流式传输定义很广泛,现在主要指通过网络传送媒体(如视频、音频)的技术总称。其特定含义为通过Internet 将影视节目传送到PC机。实现流式传输有两种方法:实时流式传输(Realtime streaming)和顺序流式传输(progressive streaming)。一般说来,如视频为实时广播,或使用流式传输媒体服务器,或应用如RTSP的实时协议,即为实时流式传输。如使用HTTP服务器,文件即通过顺序流发送。采用哪种传输方法依赖你的需求。当然,流式文件也支持在播放前完全下载到硬盘。 折叠顺序流式传输 顺序流式传输是顺序下载,在下载文件的同时用户可观看在线媒体,在给定时刻,用户只能观看已下载的那部分,而不能跳到还未下载的前头部分,顺序流式传输不象实时流式传输在传输期间根据用户连接的速度做调整。由于标准的HTTP服务器可发送这种形式的文件,也不需要其他特殊协议,它经常被称作HTTP流式传输。顺序流式传输比较适合高质量的短片段,如片头、片尾和广告,由于该文件在播放前观看的部分是无损下载的,这种方法保证电影播放的最终质量。这意味着用户在观看前,必须经历延迟,对较慢的连接尤其如此。对通过调制解调器发布短片段,顺序流式传输显得很实用,它允许用比调制解调器更高的数据速率创建视频片段。尽管有延迟,毕竟可让你发布较高质量的视频片段。顺序流式文件是放在标准HTTP或 FTP服务器上,易于管理,基本上与防火墙无关。顺序流式传输不适合长片段和有随机访问要求的视频,如:讲座、演说与演示。它也不支持现场广播,严格说来,它是一种点播技术。 折叠实时流式传输 实时流式传输指保证媒体信号带宽与网络连接配匹,使媒体可被实时观看到。实时流与HTTP流式传输不同,他需要专用的流媒体服务器与传输协议。实时流式传输总是实时传送,特别适合现场事件,也支持随机访问,用户可快进或后退以观看前面或后面的内容。理论上,实时流一经播放就可不停止,但实际上,可能发生周期暂停。实时流式传输必须配匹连接带宽,这意味着在以调制解调器速度连接时图象质量较差。而且,由于出错丢失的信息被忽略掉,网络拥挤或出现问题时,视频质量很差。如欲保证视频质量,顺序流式传输也许更好。实时流式传输需要特定服务器,如:QuickTime Streaming Server、RealServer与Windows Media Server。这些服务器允许你对媒体发送进行更多级别的控制,因而系统设置、管理比标准HTTP服务器更复杂。实时流式传输还需要特殊网络协议,如:RTSP (Realtime Streaming Protocol)或MMS (Microsoft Media Server)。这些协议在有防火墙时有时会出现问题,导致用户不能看到一些地点的实时内容。 折叠流媒体技术原理 流式传输的实现需要缓存。因为Internet以包传输为基础进行断续的异步传输,对一个实时A/V源或存储的A/V文件,在传输中它们要被分解为许多包,由于网络是动态变化的,各个包选择的路由可能不尽相同,故到达客户端的时间延迟也就不等,甚至先发的数据包还有可能后到。为此,使用缓存系统来弥补延迟和抖动的影响,并保证数据包的顺序正确,从而使媒体数据能连续输出,而不会因为网络暂时拥塞使播放出现停顿。通常高速缓存所需容量并不大,因为高速缓存使用环形链表结构来存储数据:通过丢弃已经播放的内容,流可以重新利用空出的高速缓存空间来缓存后续尚未播放的内容。——流式传输的实现需要合适的传输协议。由于TCP需要较多的开销,故不太适合传输实时数据。在流式传输的实现方案中,一般采用HTTP/TCP来传输控制信息,而用RTP/UDP来传输实时声音数据。流式传输的过程一般是这样的:用户选择某一流媒体服务后,Web浏览器与Web服务器之间使用HTTP/TCP交换控制信息,以便把需要传输的实时数据从原始信息中检索出来;然后客户机上的Web浏览器启动A/VHelper程序,使用HTTP从Web服务器检索相关参数对Helper程序初始化。这些参数可能包括目录信息、A/V数据的编码类型或与A/V检索相关的服务器地址。 A/VHelper程序及A/V服务器运行实时流控制协议(RTSP),以交换A/V传输所需的控制信息。与CD播放机或VCRs所提供的功能相似,RTSP提供了操纵播放、快进、快倒、暂停及录制等命令的方法。A/V服务器使用RTP/UDP协议将A/V数据传输给A/V客户程序(一般可认为客户程序等同于Helper程序),一旦A/V数据抵达客户端,A/V客户程序即可播放输出。 需要说明的是,在流式传输中,使用RTP/UDP和RTSP/TCP两种不同的通信协议与A/V服务器建立联系,是为了能够把服务器的输出重定向到一个不同于运行A/VHelper程序所在客户机的目的地址。实现流式传输一般都需要专用服务器和播放器,其基本原理如图所示。 折叠智能流技术(SureStream) 今天,28.8Kbps调制解调器是Internet连接的基本速率,cable modem、 ADSL、DSS、ISDN等发展快,内容提供商不得不要么限制发布媒体质量,要么限制连接人数。根据RealNetwork站点统计,对28.8Kbps调制解调器,实际流量为10bps到26Kbps,呈钟形分布,高峰在20Kbps。这意味着若内容提供商选择20Kbps固定速率,将有大量用户得不到好质量信号,并可能停止媒体流而引起客户端再次缓冲,直到接收足够数据。一种解决方法是服务器减少发送给客户端的数据而阻止再缓冲,在RealSystem 5.0中,这种方法称为“视频流瘦化”。这种方法的限制是RealVideo文件为一种数据速率设计,结果可通过抽取内部帧扩展到更低速率,导致质量较低。离原始数据速率越远,质量越差。另一种解决方法是根据不同连接速率创建多个文件,根据用户连接,服务器发送相应文件,这种方法带来制作和管理上的困难,而且,用户连接是动态变化的,服务器也无法实时协调。 智能流技术通过两种途径克服带宽协调和流瘦化。首先,确立一个编码框架,允许不同速率的多个流同时编码,合并到同一个文件中;第二,采用一种复杂客户/服务器机制探测带宽变化。 针对软件、设备和数据传输速度上的差别,用户以不同带宽浏览音视频内容。为满足客户要求,Progressive networks公司编码、记录不同速率下媒体数据,并保存在单一文件中,此文件称为智能流文件,即创建可扩展流式文件。当客户端发出请求,它将其带宽容量传给服务器,媒体服务器根据客户带宽将智能流文件相应部分传送给用户。以此方式,用户可看到最可能的优质传输,制作人员只需要压缩一次,管理员也只需要维护单一文件,而媒体服务器根据所得带宽自动切换。智能流通过描述I现实世界Internet上变化的带宽特点来发送高质量媒体并保证可靠性,并对混合连接环境的内容授权提供了解决方法。流媒体实现方式如下: * 对所有连接速率环境创建一个文件 * 在混合环境下以不同速率传送媒体 * 根据网络变化,无缝切换到其它速率 * 关键帧优先,音频比部分帧数据重要 * 向后兼容老版本RealPlayer 折叠智能流 在RealSystem G2中是对所谓自适应流管理(ASM)API的实现,ASM描述流式数据的类型,辅助智能决策,确定发送那种类型数据包。文件格式和广播插件定义了ASM规则。用最简单的形式分配预定义属性和平均带宽给数据包组。对高级形式,ASM规则允许插件根据网络条件变化改变数据包发送。每个ASM规则可有一定义条件的演示式,如演示式定义客户带宽是5,000到15,000Kbps,包损失小于2.5%。如此条件描述了客户当前网络连接,客户就订阅此规则。定义在规则中的属性有助于RealServer有效传送数据包,如网络条件变化,客户就订阅一个不同规则。 折叠编辑本段基本介绍 流媒体是指以流的方式在网络中传输音频、视频和多媒体文件的形式。 流媒体文件格式是支持采用流式传输及播放的媒体格式。流式传输方式是将视频和音频等多媒体文件经过特殊的压缩方式分成一个个压缩包,由服务器向用户计算机连续、实时传送。在采用流式传输方式的系统中,用户不必像非流式播放那样等到整个文件全部下载完毕后才能看到当中的内容,而是只需要经过几秒钟或几十秒的启动延时即可在用户计算机上利用相应的播放器对压缩的视频或音频等流式媒体文件进行播放,剩余的部分将继续进行下载,直至播放完毕。 这个过程的一系列相关的包称为“流”。流媒体实际指的是一种新的媒体传送方式,而非一种新的媒体。流媒体技术全面应用后,人们在网上聊天可直接语音输入;如果想彼此看见对方的容貌、表情,只要双方各有一个摄像头就可以了;在网上看到感兴趣的商品,点击以后,讲解员和商品的影像就会跳出来;更有真实感的影像新闻也会出现。 流媒体技术发端于美国。在美国目前流媒体的应用已很普遍,比如惠普公司的产品发布和销售人员培训都用网络视频进行。 流式传输方式则是将整个A/V及3D等多媒体文件经过特殊的压缩方式分成一个个压缩包,由视频服务器向用户计算机连续、实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备(硬件或软件)对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。 折叠编辑本段技术应用 互联网的迅猛发展和普及为流媒体业务发展提供了强大市场动力,流媒体业务正变得日益流行。 流媒体技术广泛用于多媒体新闻发布、在线直播、网络广告、电子商务、视频点播、远程教育、远程医疗、网络电台、 实时视频会议等互联网信息服务的方方面面。流媒体技术的应用将为网络信息交流带来革命性的变化,对人们的工作和生活将产生深远的影响。一个完整的流媒体解决方案应是相关软硬件的完美集成,它大致包括下面几个方面的内容: 内容采集、 视音频捕获和压缩编码、内容编辑、内容存储和播放、应用服务器内容管理发布及用户管理等。 流媒体技术和声音信息经过压缩处理后放上网站服务器,让用户一边下载一边观看、收听,而不要等整个压缩文件下载到自己的计算机上才可以观看的网络传输技术。该技术先在使用者端的计算机上创建一个缓冲区,在播放前预先下一段数据作为缓冲,在网路实际连线速度小于播放所耗的速度时,播放程序就会取用一小段缓冲区内的数据,这样可以避免播放的中断,也使得播放品质得以保证。 传输流程 在流式传输的实现方案中,一般采用HTTP/TCP来传输控制信息,而用RTP/UDP来传输实时声音数据。具体的传输流程如下: (1)Web浏览器与Web服务器之间使用HTTP/TCP交换控制信息,以便把需要传输的实时数据从原始信息中检索出来。 (2)用HTTP从Web服务器检索相关数据,由A/V播放器进行初始化。 (3)从Web服务器检索出来的相关服务器的地址定位A/V服务器。 (4)A/V播放器与A/V服务器之间交换A/V传输所需要的实时控制协议。 (5)一旦A/V数据抵达客户端,A/V播放器就可播放。 技术方式 目前主流的流媒体技术有三种,分别是RealNetworks公司的RealMedia、Microsoft公司的WindowsMediaTechnology和Apple公司的QuickTime。这三家的技术都有自己的专利算法、专利文件格式甚至专利传输控制协议。 1.Apple公司的QuickTime QuickTime是一个非常老牌的媒体技术集成,是数字媒体领域事实上的工业标准。之所以说集成这个词是因为QuickTime实际上是一个开放式的架构,包含了各种各样的流式或者非流式的媒体技术。QuickTime是最早的视频工业标准,1999年发布的QuickTime4.0版本开始支持真正的流式播放。由于QuickTime本身也存在着平台的便利(MacOS),因此也拥有不少的用户。QuickTime在视频压缩上采用的是SorensonVideo技术,音频部分则采用QDesignMusic技术。QuickTime最大的特点是其本身所具有的包容性,使得它是一个完整的多媒体平台,因此基于QuickTime可以使用多种媒体技术来共同制作媒体内容。同时,它在交互性方面是三者之中最好的。例如,在一个QuickTime文件中可同时包含midi、动画gif、flash和smil等格式的文件,配合QuickTime的WiredSprites互动格式,可设计出各种互动界面和动画。QuickTime流媒体技术实现基础是需要3个软件的支持,QuickTime播放器、QuickTime编辑制作、QuickTimeStreaming服务器。 2.RealNetworks公司的RealMedia RealMedia发展的时间比较长,因此具有很多先进的设计,例如,ScalableVideoTechnology可伸缩视频技术可以根据用户电脑速度和连接质量而自动调整媒体的播放质素。Two—passEncoding两次编码技术可通过对媒体内容进行预扫描,再根据扫描的结果来编码从而提高编码质量。特别是SureStream自适应流技术,可通过一个编码流提供自动适合不同带宽用户的流播放。RealMedia音频部分采用的是RealAudio,该编码在低带宽环境下的传输性能非常突出。RealMedia通过基于smil并结合自己的RealPix和RealText技术来达到一定的交互能力和媒体控制能力。Real流媒体技术需要3个软件的支持,RealPlayer播放器、RealProducer编辑制作、RealServer服务器。 3.Microsoft公司的WindowsMedia WindowsMedia是三家之中最后进入这个市场的,但凭借其操作系统的便利很快便取得了较大的市场份额。WindowsMediaVideo采用的是mpeg-4视频压缩技术,音频方面采用的是WindowsMediaAudio技术。WindowsMedia的关键核心是MMS协议和ASF数据格式,MMS用于网络传输控制,ASF则用于媒体内容和编码方案的打包。目前WindowsMedia在交互能力方面是三者之中最弱的,自己的ASF格式交互能力不强,除了通过IE支持smil之外就没有什么其他的交互能力了。WindowsMedia流媒体技术的实现需要3个软件的支持,WindowsMedia播放器、WindowsMedia工具和WindowsMedia服务器。总的来说,如果使用Windows服务器平台,WindowsMedia的费用最少。虽然在现阶段其功能并不是最好,用户也不是最多。 折叠编辑本段技术问题 流媒体技术不是一种单一的技术,它是网络技术及视/音频技术的有机结合。在网络上实现流媒体技术,需要解决流媒体的制作、发布、传输及播放等方面的问题,而这些问题则需要利用视音频技术及网络技术来解决,具体如下: (1)流媒体制作技术方面解决的问题 在网上进行流媒体传输,所传输的文件必须制作成适合流媒体传输的流媒体格式文件。因这通常格式存储的多媒体文件容量十分大,若要在现有的窄带网络上传输则需要花费十分长的时间,若遇网络繁忙,还将造成传输中断。另外,通常格式的流媒体也不能按流媒体传输协议进行传输。因此,对需要进行流媒体格式传输的文件应进行预处理,将文件压缩生成流媒体格式文件。这里应注意两点:一是选用适当的压缩算法进行压缩,这样生成的文件容量较小。二是需要向文件中添加流式信息。 (2)流媒体传输方面需解决的问题 流媒体的传输需要合适的传输协议,目前在internet上的文件传输大部分都是建立在tcp协议的基础上,也有一些是以ftp传输协议的方式进行传输,但采用这些传输协议都不能实现实时方式的传输。随着流媒体技术的深入研究,目前比较成熟的流媒体传输一般都是采用建立在udp协议上的rtp/rtsp实时传输协议。 为何要在udp协议而不在tcp协议上进行实时数据的传输呢?这是因为udp和tcp协议在实现数据传输时的可靠性有很大的区别。tcp协议中包含了专门的数据传送校验机制,当数据接受方收到数据后,将自动向发送方发出确认信息,发送方在接收到确认信息后才继续传送数据,否则将一直处于等待状态。而udp协议则不同,udp协议本身并不能做任何校验。由此可以看出,tcp协议注重传输质量,而udp协议则注重传输速度.因此,对于对传输质量要求不是很高,而对传输速度则有很高的要求的视音频流媒体文件来说,采用udp协议则更合适. (3)流媒体的传输过程中需要缓存的支持 因为interent是以包为单位进行异步传输的,因此多媒体数据在传输中要被分解成许多包,由于网络传输的不稳定性,各个包选择的路由不同,所以到达客户端的时间次序可能发生改变,甚至产生丢包的现象.为此,必须采用缓存技术来纠正由于数据到达次序发生改变而产生的混乱状况,利用缓存对到达的数据包进行正确排序,从而使视音频数据能连续正确地播放.缓存 中存储的是某一段时间内的数据,数据在缓存中存放的时间是暂时的,缓存中的数据也是动态的,不断更新的.流媒体在播放时不断读取缓存中的数据进行播放,播放完后该数据便被立即清除,新的数据将存入到缓存中.因此,在播放流媒体文件时并不需占用太大的缓存空间. (4)流媒体播放方面需解决的问题 流媒体播放需要浏览器的支持.通常情况下,浏览器是采用mime来识别各种不同的简单文件格式,所有的web浏览器都是基于http协议,而http协议都内建有mime.所以web浏览器能够通过http协议中内建的mime来标记web上众多的多媒体文件格式,包括各种流媒体格式. 折叠编辑本段常用格式 声音流、视频流、文本流、图像流、动画流 RA:实时声音 RM:实时视频或音频的实时媒体 RT:实时文本 RP:实时图像 SMIL:同步的多重数据类型综合设计文件 SWF:micromedia的real flash 和shockwave flash动画文件 RPM:HTML文件的插件 RAM:流媒体的元文件,是包含RA、RM、SMIL文件地址(URL地址)的文本文件 CSF:一种类似媒体容器的文件格式,可以将非常多的媒体格式包含在其中,而不仅仅限于音、视频。 它可以把ppt和教师讲课的视频完美结合, 很多大学和大型企业使用这套软件进行教学录像和远程教育 1、realnetwork公司:三种 2、apple公司:quicktime\mov 3、microsoft公司 (1)asf\wmv\wma (2)avi (3)mpeg\mpg\dat 4、micromedia公司 (1)flash的swf格式 (2)metastream的mts格式 (3)aam多媒体教学课件格式,可将authorware生成的文件压缩为aam和aas流式文件播放 折叠编辑本段传输协议 1、RSVP:资源预留协议 2、RTP:实时传输协议 3、RTCP:实时传输控制协议 4、MMS:微软流媒体服务协议 5、RTSP:实时流传输协议 6、MIME:多目因特网电子邮件扩展协议 7、RTMP(RTMPE/RTMPS/RTMPT):Adobe实时消息协议簇 8、RTMFP:Adobe实施消息流协议(P2P协议) 折叠编辑本段播放方式 折叠单播 在客户端与媒体服务器之间需要建立一个单独的数据通道,从一台服务器送出的每个数据包只能传送给一个客户机,这种传送方式称为单播。每个用户必须分别对媒体服务器发送单独的查询,而媒体服务器必须向每个用户发送所申请的数据包拷贝。这种巨大冗余首先造成服务器沉重的负担,响应需要很长时间,甚至停止播放;管理人员也被迫购买硬件和带宽来保证一定的服务质量。 折叠组播 IP组播技术构建一种具有组播能力的网络,允许路由器一次将数据包复制到多个通道上。采用组播方式,单台服务器能够对几十万台客户机同时发送连续数据流而无延时。媒体服务器只需要发送一个信息包,而不是多个;所有发出请求的客户端共享同一信息包。信息可以发送到任意地址的客户机,减少网络上传输的信息包的总量。网络利用效率大大提高,成本大为下降。 折叠点播与广播 点播连接是客户端与服务器之间的主动的连接。在点播连接中,用户通过选择内容项目来初始化客户端连接。用户可以开始、停止、后退、快进或暂停流。点播连接提供了对流的最大控制,但这种方式由于每个客户端各自连接服务器,却会迅速用完网络带宽。 广播指的是用户被动接收流。在广播过程中,客户端接收流,但不能控制流。例如,用户不能暂停、快进或后退该流。广播方式中数据包的单独一个拷贝将发送给网络上的所有用户。 使用单播发送时,需要将数据包复制多个拷贝,以多个点对点的方式分别发送到需要它的那些用户,而使用广播方式发送,数据包的单独一个拷贝将发送给网络上的所有用户,而不管用户是否需要,上述两种传输方式会非常浪费网络带宽。组播吸收了上述两种发送方式的长处,克服了上述两种发送方式的弱点,将数据包的单独一个拷贝发送给需要的那些客户。组播不会复制数据包的多个拷贝传输到网络上,也不会将数据包发送给不需要它的那些客户,保证了网络上多媒体应用占用网络的最小带宽。 支付 http://ju.outofmemory.cn/entry/138098 线程池http://www.trinea.cn/android/java-android-thread-pool/ 缓存 http://blog.csdn.net/wwj_748/article/details/42737607 http://blog.csdn.net/hewence1/article/details/39004231 上 http://blog.csdn.net/hewence1/article/details/39004301 中 http://blog.csdn.net/weiyidemaomao/article/details/21237833 http://blog.csdn.net/it_yuan/article/details/8489125 http://baike.baidu.com/link?url=eM5lPhaRLUmUg4It6SeAVm1JQKAHPmZxUhdwMVjzvHnnjQu7a8MatG6_IoPinthZuMkpIOCwGPqNtgvssN-8pK http://blog.csdn.net/luohai859/article/details/38660563 http://blog.csdn.net/luohai859/article/details/38660257 透明 http://www.bkjia.com/Androidjc/967394.html 1、为什么要用缓存 缓存是存取数据的临时地,因为取原始数据代价太大了,加了缓存,可以取得快些。缓存可以认为是原始数据的子集,它是从原始数据里复制出来的,并且为了能被取回,被加上了标志。 在android开发中,经常要访问网络数据比如大量网络图片,如果每次需要同一张图片都去网络获取,这代价显然太大了。可以考虑设置本地文件缓存和内存缓存,存储从网络取得的数据;本地文件缓存空间并非是无限大的,容量越大读取效率越低,可设置一个折中缓存容量比如10M,如果缓存已满,我们需要采用合适的替换策略换掉一个已有的数据对象,并替之已一个新的数据对象;内存缓存作为最先被读取的数据,应该存储那些经常使用的数据对象,且内存容量有限,内存缓存的容量也应该限定。依照这样的做法,取得一个图片(总图片数为N)的流程应该是这样的: a.先在内存缓存取(设存储K个),若取到则返回(命中率为K/N,时间为tA),否则进行b; b.在本地文件缓存(设能存储M个)中取,若取到则返回并更新内存缓存(命中率为(M-K)/N,时间为tB),否则进行c; c.通过网络下载图片,并更新本地文件缓存和内存缓存(命中率为(N-M)/N,时间为tC); 取一张图片的时间期望为:W = tA * (K/N) + tB * (M-K)/N + tC * (N-M)/N ,其中tA < tB < tC ,为使W代价小,即尽可能快的取得数据,我们应该提高内存缓存的命中率和本地文件缓存的命中率,但两者的容量都是有限制的,所以必须使用适合替换算法来更新两者所存储的对象。选择合适的替换算法是缓存的难点所在。 2、常见缓存交换算法介绍 最理想的替换算法是每次调换出的数据对象是所有缓存中最迟将被使用的,这可以最大限度的推迟数据对象调换。可惜的是,我们无法预知用户的访问,所以这种算法是无法实现的。 实际中常见的缓存算法有以下几种(参考文档a): Least Frequently Used(LFU) 对每个缓存对象计算他们被使用的频率。把最不常用的缓存对象换走。 Least Recently User(LRU) 把最近最少使用的缓存对象给换走。总是需要去了解在什么时候,用了哪个缓存对象。如果有人想要了解为什么总能把最近最少使用的对象踢掉,是非常困难的。浏览器就是使用了LRU作为缓存算法。新的对象会被放在缓存的顶部,当缓存达到了容量极限,我会把底部的对象踢走,而技巧就是:我会把最新被访问的缓存对象,放到缓存池的顶部。 所以,经常被读取的缓存对象就会一直呆在缓存池中。可以用数据或者链表实现。其改进算法有LRU2 和 2Q。 Least Recently Used 2(LRU2) 把被两次访问过的对象放入缓存池,当缓存池满了之后,我会把有两次最少使用的缓存对象踢走。因为需要跟踪对象2次,访问负载就会随着缓存池的增加而增加。如果用在大容量的缓存池中,就会有问题。另外,还需跟踪那么不在缓存的对象,因为他们还没有被第二次读取。这比LRU好。 Two Queues(2Q) 把被访问的数据放到LRU的缓存中,如果该对象再一次被访问,就把他转移到第二个更大的LRU缓存。替换掉缓存对象是为了保持第一个缓存池是第二个缓存池的1/3。当缓存的访问负载是固定的时候,把 LRU 换成 LRU2,就比增加缓存的容量更好。这种机制使得该算法比 LRU2 更好。 Adaptive Replacement Cache(ARC) 这种算法介于 LRU 和 LFU 之间,由2个 LRU 组成,第一个,也就是 L1,包含的条目是最近只被使用过一次的,而第二个 LRU,也就是L2,包含的是最近被使用过两次的条目。因此,L1 放的是新的对象,而 L2 放的是常用的对象。该算法是是性能最好的缓存算法之一,能够自调,并且是低负载的。保存着历史对象,这样,就可以记住那些被移除的对象,同时,也可以看到被替换掉的对象是否可以留下,取而代之的是替换别的对象。该算法记忆力很差,但是很快,适用性也强。 Most Recently Used(MRU) 该算法与 LRU是对应的。它替换掉最近最多被使用的对象,你一定会问为什么。原因是,当一次访问过来的时候,有些事情是无法预测的,并且在缓存系统中找出最少最近使用的对象是一项时间复杂度非常高的运算。该算法在数据库内存缓存中很见!每当一次缓存记录的使用,会把它放到栈的顶端。当栈满了的时候,会把栈顶的对象给换成新进来的对象! First in First out(FIFO) 这是一个低负载的算法,并且对缓存对象的管理要求不高。通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去。很快,但是不适用。 Second Chance 改进的FIFO算法,比 FIFO 好的地方是改善了 FIFO 的成本。一样是在观察队列的前端,但是很FIFO的立刻替换不同,它会检查即将要被踢出的对象有没有之前被使用过的标志(1一个bit表示),如果没有被使用过,就把他换出;否则,把这个标志位清除,然后把这个缓存对象当做新增缓存对象加入队列。你可以想象就这就像一个环队列。当再一次在队头碰到这个对象时,由于它已经没有标志位,可以立刻就它换出。在速度上比FIFO快。 CLock 这是一个更好的FIFO,也比 second chance更好。因为它不会像second chance那样把有标志的缓存对象放到队列的尾部,但是也可以达到second chance的效果。它持有一个装有缓存对象的环形列表,头指针指向列表中最老的缓存对象。当缓存miss发生并且没有新的缓存空间时,它会根据指针指向的缓存对象的标志位去决定应该怎么做。如果标志是0,直接用新的缓存对象替代这个缓存对象;如果标志位是1,把头指针递增,然后重复这个过程,直到新的缓存对象能够被放入。 Simple time-based 通过绝对的时间周期去失效那些缓存对象。对于新增的对象,保存特定的时间。很快,但不适用。 Extended time-based expiration 通过相对时间去失效缓存对象的;对于新增的缓存对象,保存特定的时间,比如是每5分钟,每天的12点。 Sliding time-based expiration 被管理的缓存对象的生命起点是在这个缓存的最后被访问时间算起。很快,不太适用。 缓存算法主要考虑到了下面几点: 成本。如果缓存对象有不同的成本,应该把那些难以获得的对象保存下来。 容量。如果缓存对象有不同的大小,应该把那些大的缓存对象清除,这样就可以让更多的小缓存对象进来了。 时间。一些缓存还保存着缓存的过期时间。电脑会失效他们,因为他们已经过期了。 3、LRU算法实现 具体的实现可以参考文档d。本文将考虑使用软引用(参考e)结合LRU算法实现如上所述的android二级缓存,稍后贴出代码。 在您的UI中显示单个图片是非常简单的,如果您需要一次显示很多图片就有点复杂了。在很多情况下 (例如使用 ListView, GridView 或者 ViewPager控件), 显示在屏幕上的图片以及即将显示在屏幕上的图片数量是非常大的(例如在图库中浏览大量图片)。 在这些控件中,当一个子控件不显示的时候,系统会重用该控件来循环显示 以便减少对内存的消耗。同时垃圾回收机制还会 释放那些已经载入内存中的Bitmap资源(假设您没有强引用这些Bitmap)。一般来说这样都是不错的,但是在用户来回滑动屏幕的时候,为了保证UI 的流畅性和载入图片的效率,您需要避免重复的处理这些需要显示的图片。 使用内存缓存和磁盘缓存可以解决这个问题,使用缓存可以让控件快速的加载已经处理过的图片。 这节内容介绍如何使用缓存来提高UI的载入输入和滑动的流畅性。 使用内存缓存 内存缓存提高了访问图片的速度,但是要占用不少内存。 LruCache 类(在API 4之前可以使用Support Library 中的类 )特别适合缓存Bitmap, 把最近使用到的 Bitmap对象用强引用保存起来(保存到LinkedHashMap中),当缓存数量达到预定的值的时候,把 不经常使用的对象删除。 注意: 过去,实现内存缓存的常用做法是使用 SoftReference 或者 WeakReference bitmap 缓存, 但是不推荐使用这种方式。从Android 2.3 (API Level 9) 开始,垃圾回收开始强制的回收掉 soft/weak 引用 从而导致这些缓存没有任何效率的提升。 另外,在 Android 3.0 (API Level 11)之前,这些缓存的Bitmap数据保存在底层内存(native memory)中,并且达到预定条件后也不会释放这些对象,从而可能导致 程序超过内存限制并崩溃。 在使用 LruCache 的时候,需要考虑如下一些因素来选择一个合适的缓存数量参数: 程序中还有多少内存可用 同时在屏幕上显示多少图片?要先缓存多少图片用来显示到即将看到的屏幕上? 设备的屏幕尺寸和屏幕密度是多少?超高的屏幕密度(xhdpi 例如 Galaxy Nexus) 设备显示同样的图片要比低屏幕密度(hdpi 例如 Nexus S)设备需要更多的内存。 图片的尺寸和格式决定了每个图片需要占用多少内存 图片访问的频率如何?一些图片的访问频率要比其他图片高很多?如果是这样的话,您可能需要把这些经常访问的图片放到内存中。 在质量和数量上如何平衡?有些情况下保存大量的低质量的图片是非常有用的,当需要的情况下使用后台线程来加入一个高质量版本的图片。 这里没有万能配方可以适合所有的程序,您需要分析您的使用情况并在指定自己的缓存策略。使用太小的缓存并不能起到应有的效果,而使用太大的缓存会消耗更多 的内存从而有可能导致 java.lang.OutOfMemory 异常或者留下很少的内存供您的程序其他功能使用。 下面是一个使用 LruCache 缓存的示例: [java] view plaincopy private LruCache<string, bitmap=""> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int memClass = ((ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache. final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache<string, bitmap="">(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return bitmap.getByteCount(); } }; ... } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } 注意: 在这个示例中,该程序的1/8内存都用来做缓存用了。在一个normal/hdpi设备中,这至少有4MB(32/8)内存。 在一个分辨率为 800×480的设备中,满屏的GridView全部填充上图片将会使用差不多1.5MB(800*480*4 bytes) 的内存,所以这样差不多在内存中缓存了2.5页的图片。 当在 ImageView 中显示图片的时候, 先检查LruCache 中是否存在。如果存在就使用缓存后的图片,如果不存在就启动后台线程去载入图片并缓存: [java] view plaincopy public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } } BitmapWorkerTask 需要把新的图片添加到缓存中: [java] view plaincopy class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } ... } 使用磁盘缓存 在访问最近使用过的图片中,内存缓存速度很快,但是您无法确定图片是否在缓存中存在。像 GridView 这种控件可能具有很多图片需要显示,很快图片数据就填满了缓存容量。 同时您的程序还可能被其他任务打断,比如打进的电话 — 当您的程序位于后台的时候,系统可能会清楚到这些图片缓存。一旦用户恢复使用您的程序,您还需要重新处理这些图片。 在这种情况下,可以使用磁盘缓存来保存这些已经处理过的图片,当这些图片在内存缓存中不可用的时候,可以从磁盘缓存中加载从而省略了图片处理过程。 当然, 从磁盘载入图片要比从内存读取慢很多,并且应该在非UI线程中载入磁盘图片。 注意: 如果缓存的图片经常被使用的话,可以考虑使用 ContentProvider ,例如在图库程序中就是这样干滴。 在示例代码中有个简单的 DiskLruCache 实现。然后,在Android 4.0中包含了一个更加可靠和推荐使用的DiskLruCache(libcore/luni/src/main/java/libcore/io/DiskLruCache.java) 。您可以很容易的把这个实现移植到4.0之前的版本中使用(来 href="http://www.google.com/search?q=disklrucache">Google一下 看看其他人是否已经这样干了!)。 这里是一个更新版本的 DiskLruCache : [java] view plaincopy private DiskLruCache mDiskCache; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override protected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); ... } class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache // Process as normal final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); } // Add final bitmap to caches addBitmapToCache(String.valueOf(imageKey, bitmap); return bitmap; } ... } public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } // Also add to disk cache if (!mDiskCache.containsKey(key)) { mDiskCache.put(key, bitmap); } } public Bitmap getBitmapFromDiskCache(String key) { return mDiskCache.get(key); } // Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getCacheDir(Context context, String uniqueName) { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ? context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); } 在UI线程中检测内存缓存,在后台线程中检测磁盘缓存。磁盘操作从来不应该在UI线程中实现。当图片处理完毕后,最终的结果会同时添加到 内存缓存和磁盘缓存中以便将来使用。 处理配置改变事件 运行时的配置变更 — 例如 屏幕方向改变 — 导致Android摧毁正在运行的Activity,然后使用 新的配置从新启动该Activity (详情,参考这里 Handling Runtime Changes)。 您需要注意避免在配置改变的时候导致重新处理所有的图片,从而提高用户体验。 幸运的是,您在 使用内存缓存 部分已经有一个很好的图片缓存了。该缓存可以通过 Fragment (Fragment会通过setRetainInstance(true)函数保存起来)来传递给新的Activity 当Activity重新启动 后,Fragment 被重新附加到Activity中,您可以通过该Fragment来获取缓存对象。 下面是一个在 Fragment中保存缓存的示例: [java] view plaincopy private LruCache<string, bitmap=""> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache<string, bitmap="">(cacheSize) { ... // Initialize cache here as usual } mRetainFragment.mRetainedCache = mMemoryCache; } ... } class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache<string, bitmap=""> mRetainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); <strong>setRetainInstance(true);</strong> } } 您可以尝试分别使用和不使用Fragment来旋转设备的屏幕方向来查看具体的图片载入情况。 操作系统的任务主要是合理地调配系统的各种资源,为各种程序的运行提供环境,它可以看作是硬件和应用软件之间的一个媒介。其中对内存的管理是系统的最主要的职责,怎么样使有限的内存用在刀刃上,怎么要保证系统本身所需的内存(以防止死机,在win2000和winxp里这一点已经做的非常好了 缓存是CPU的一部分,它存在于CPU中 CPU存取数据的速度非常的快,一秒钟能够存取、处理十亿条指令和数据(术语:CPU主频1G),而内存就慢很多,快的内存能够达到几十兆就不错了,可见两者的速度差异是多么的大 缓存是为了解决CPU速度和内存速度的速度差异问题 内存中被CPU访问最频繁的数据和指令被复制入CPU中的缓存,这样CPU就可以不经常到象“蜗牛”一样慢的内存中去取数据了,CPU只要到缓存中去取就行了,而缓存的速度要比内存快很多 什么是缓存区容量? 在combo中缓存区容量指的是什么?是不是数字越大,价格便越高,并且性能越高,刻坏的几率越小 这里要特别指出的是: 1.因为缓存只是内存中少部分数据的复制品,所以CPU到缓存中寻找数据时,也会出现找不到的情况(因为这些数据没有从内存复制到缓存中去),这时CPU还是会到内存中去找数据,这样系统的速度就慢下来了,不过CPU会把这些数据复制到缓存中去,以便下一次不要再到内存中去取。 2.因为随着时间的变化,被访问得最频繁的数据不是一成不变的,也就是说,刚才还不 频繁的数据,此时已经需要被频繁的访问,刚才还是最频繁的数据,现在又不频繁了, 所以说缓存中的数据要经常按照一定的算法来更换,这样才能保证缓存中的数据是被访 问最频繁的 3.关于一级缓存和二级缓存 为了分清这两个概念,我们先了解一下RAM ram和ROM相对的,RAM是掉电以后,其中才信息就消失那一种,ROM在掉电以后信息也不会消失那一种 RAM又分两种, 一种是静态RAM,SRAM;一种是动态RAM,DRAM。前者的存储速度要比后者快得多,我们 现在使用的内存一般都是动态RAM。 有的菜鸟就说了,为了增加系统的速度,把缓存扩大不就行了吗,扩大的越大,缓存的 数据越多,系统不就越快了吗 缓存通常都是静态RAM,速度是非常的快, 但是静态RAM集成度低(存储相同的数据,静态RAM的体积是动态RAM的6倍), 价格高(同容量的静态RAM是动态RAM的四倍), 由此可见,扩大静态RAM作为缓存是一个非常愚蠢的行为, 但是为了提高系统的性能和速度,我们必须要扩大缓存, 这样就有了一个折中的方法,不扩大原来的静态RAM缓存,而是增加一些高速动态RAM做 为缓存, 这些高速动态RAM速度要比常规动态RAM快,但比原来的静态RAM缓存慢, 我们把原来的静态ram缓存叫一级缓存,而把后来增加的动态RAM叫二级缓存。 一级缓存和二级缓存中的内容都是内存中访问频率高的数据的复制品(映射),它们的 存在都是为了减少高速CPU对慢速内存的访问。 通常CPU找数据或指令的顺序是:先到一级缓存中找,找不到再到二级缓存中找,如果还找不到就只有到内存中找了 2.赛扬处理器与奔腾处理器的区别再哪里? 赛扬处理器与奔腾处理器在运算内核上完全相同,不同的地方是二级缓存的大小不同。现有的台式机处理器P4的二级缓存大小是512KB,而P4赛扬的二级缓存大小是128KB。在笔记本上用的奔腾-M处理器的二级缓存大小是1MB,新出的赛扬M处理器的二级缓存大小是512KB,跟P4的一样。奔腾-M和赛扬M处理器除了二级缓存大小不同外,其余地方一样。 什么是二级缓存? 它是干什么用的? 二级缓存又叫L2 CACHE,它是处理器内部的一些缓冲存储器,其作用跟内存一样。 它是怎么出现的呢? 要上溯到上个世纪80年代,由于处理器的运行速度越来越快,慢慢地,处理器需要从内存中读取数据的速度需求就越来越高了。然而内存的速度提升速度却很缓慢,而能高速读写数据的内存价格又非常高昂,不能大量采用。从性能价格比的角度出发,英特尔等处理器设计生产公司想到一个办法,就是用少量的高速内存和大量的低速内存结合使用,共同为处理器提供数据。这样就兼顾了性能和使用成本的最优。而那些高速的内存因为是处于CPU和内存之间的位置,又是临时存放数据的地方,所以就叫做缓冲存储器了,简称“缓存”。它的作用就像仓库中临时堆放货物的地方一样,货物从运输车辆上放下时临时堆放在缓存区中,然后再搬到内部存储区中长时间存放。货物在这段区域中存放的时间很短,就是一个临时货场。最初缓存只有一级,后来处理器速度又提升了,一级缓存不够用了,于是就添加了二级缓存。二级缓存是比一级缓存速度更慢,容量更大的内存,主要就是做一级缓存和内存之间数据临时交换的地方用。现在,为了适应速度更快的处理器P4EE,已经出现了三级缓存了,它的容量更大,速度相对二级缓存也要慢一些,但是比内存可快多了。 缓存的出现使得CPU处理器的运行效率得到了大幅度的提升,这个区域中存放的都是CPU频繁要使用的数据,所以缓存越大处理器效率就越高,同时由于缓存的物理结构比内存复杂很多,所以其成本也很高。 什么叫虚拟内存,它与缓存有什么异同呢 虚拟内存是WINDOWS操作系统用来管理内存的一种方法 虚拟内存说穿了就是把内存中的信息分出去一部分存在硬盘上 这样从表面上看就扩大了内存容量(难怪内存中存了这么多东西,原来有一部分被转到了硬盘上) 我们知道内存中存有很多数据和指令,但是这些数据和指令被访问的频率是不同的,有些要被经常访问,有些却被CPU冷落WINDOWS 是个落井下石的坏东西,它大声的说:既然你们被CPU冷落了,干脆把你们打入冷宫吧。于是这些被访问频率低或者不被访问的数据从内存中被赶到了硬盘上。但WINDOWS并非铁石心肠,它还说:如果CPU需要访问你们时,我就把你们送入内存,如果你们更加热门的时候,就可能被CPU带入缓存,如果你们被重新冷落时,我就会无情的把你们再打入冷宫。 而那个冷宫就是硬盘上的虚拟内存。如果你刚才还不知道什么是虚拟内存是什么东西时,那么现在呢? 虚拟内存和缓存有什么区别呢 它们的原理都是局部时间内被访问的内存地址具有局部性。 现在我们来看看它们的区别。 1.缓存是一种硬件,是CPU的一部分;而虚拟内存并不是一种硬件,而是存在于硬盘上的一个文件,是根据WINDOWS的要求生成的(二者中的信息都是随时变化的) 2.缓存的目的是加快系统的速度,而虚拟内存技术是为了使更多的程序能够在有限的内存中运行。
转载请注明原文地址: https://www.6miu.com/read-1600165.html

最新回复(0)