首页 文章 精选 留言 我的

精选列表

搜索[网站开发],共10000篇文章
优秀的个人博客,低调大师

Android开发实践:自定义带消息循环(Looper)的工作线程

1. 首先,我们完成一个简单的线程框架。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class LooperThread{ private volatile boolean mIsLooperQuit= false ; private ThreadmThread; public void start(){ if (mThread!= null ){ return ; } mIsLooperQuit= false ; mThread= new Thread(mLooperRunnable); mThread.start(); } public void stop(){ if (mThread== null ){ return ; } mIsLooperQuit= true ; mThread= null ; } protected RunnablemLooperRunnable= new Runnable(){ @Override public void run(){ while (!mIsLooperQuit){ } } }; } 如上述代码所示,mLooperRunnable.run()循环执行线程任务,mIsLooperQuit则是线程退出循环的条件。下面,我们将添加消息的发送和处理代码。 2. 添加线程循环的消息发送和处理代码 (1) 定义消息结构体,创建消息队列 1 2 3 4 5 6 7 8 public class LooperThread{ private Queue<Message>mMessageQueue= new LinkedList<Message>(); public static class Message{ int what; } } (2) 创建互斥锁和条件变量 1 2 3 4 5 public class LooperThread{ private LockmLock= new ReentrantLock(); private ConditionmCondition=mLock.newCondition(); } (3) 创建发送消息的函数 1 2 3 4 5 6 7 8 9 10 //发送消息,由外部其他模块调用,发送消息给线程 public void sendMessage(Messagemessage){ if (mThread== null ){ return ; } mLock.lock(); mMessageQueue.add(message); //添加消息到消息队列 mCondition.signal(); //通知线程循环,有消息来了,请立即处理 mLock.unlock(); } (4) 创建处理消息的函数 1 2 3 4 //处理消息,由线程内部调用 public void handleMessage(Messagemessage){ //这里可以通过一个Callback来回调监听者 } (5) 在mLooperRunnable.run()循环中解析消息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 protected RunnablemLooperRunnable= new Runnable(){ @Override public void run(){ while (!mIsLooperQuit){ mLock.lock(); Messagemessage= null ; try { while (!mIsLooperQuit&&mMessageQueue.isEmpty()){ mCondition.await(); //没有消息到来则休眠 } message=mMessageQueue.poll(); } catch (InterruptedExceptione){ e.printStackTrace(); } finally { mLock.unlock(); } handleMessage(message); } }; } (6) 修改线程的Stop()函数,唤醒休眠的消息循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void stop(){ if (mThread== null ){ return ; } mIsLooperQuit= true ; mLock.lock(); mCondition.signal(); mLock.unlock(); mMessageQueue.clear(); mThread= null ; } 到这里,一个基本的带有消息循环的线程类封装就完成了,相信大家应该从编写这段代码的过程中,理解了系统是如何实现消息循环的。 本文转自 Jhuster 51CTO博客,原文链接:http://blog.51cto.com/ticktick/1565272,如需转载请自行联系原作者

优秀的个人博客,低调大师

开发一个完整iOS直播app——GPUImage渲染底层实现-opengl

一、前言 本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示 由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广大同学再次爬坑,本篇讲解了不少OpenGL的知识,并且还讲解了花了大量时间解决bug的注意点,曾经因为对glDrawArrays这个方法不熟悉,遇上Bug,晚上熬到凌晨四点都没解决,还是第二天中午解决的。 如果喜欢我的文章,可以关注我微博:袁峥Seemygo,也可以来小码哥,了解下我们的iOS培训课程。后续还会更新更多内容,有任何问题,欢迎关注微,信号“小码哥订阅号”。 二、GPUImageVideoCamera 可以捕获采集的视频数据 关键是捕获到一帧一帧视频数据如何展示? 通过这个方法可以获取采集的视频数据 采集视频注意点:要设置采集竖屏,否则获取的数据是横屏 通过AVCaptureConnection就可以设置 三、自定义OpenGLView渲染视频 暴露一个接口,获取采集到的帧数据,然后把帧数据传递给渲染View,展示出来 四、利用OpenGL渲染帧数据并显示 导入头文件#import <GLKit/GLKit.h>,GLKit.h底层使用了OpenGLES,导入它,相当于自动导入了OpenGLES 步骤 01-自定义图层类型 02-初始化CAEAGLLayer图层属性 03-创建EAGLContext 04-创建渲染缓冲区 05-创建帧缓冲区 06-创建着色器 07-创建着色器程序 08-创建纹理对象 09-YUV转RGB绘制纹理 10-渲染缓冲区到屏幕 11-清理内存 01-自定义图层类型 为什么要自定义图层类型CAEAGLLayer? CAEAGLLayer是OpenGL专门用来渲染的图层,使用OpenGL必须使用这个图层 02-初始化CAEAGLLayer图层属性 1.不透明度(opaque)=YES,CALayer默认是透明的,透明性能不好,最好设置为不透明. 2.设置绘图属性 kEAGLDrawablePropertyRetainedBacking :NO (告诉CoreAnimation不要试图保留任何以前绘制的图像留作以后重用) kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告诉CoreAnimation用8位来保存RGBA的值) 其实设置不设置都无所谓,默认也是这个值,只不过GPUImage设置了 03-创建EAGLContext 需要将它设置为当前context,所有的OpenGL ES渲染默认渲染到当前上下文 EAGLContext管理所有使用OpenGL ES进行描绘的状态,命令以及资源信息,要绘制东西,必须要有上下文,跟图形上下文类似。 当你创建一个EAGLContext,你要声明你要用哪个version的API。这里,我们选择OpenGL ES 2.0 04-创建渲染缓冲区 有了上下文,openGL还需要在一块buffer进行描绘,这块buffer就是RenderBuffer OpenGLES 总共有三大不同用途的color buffer,depth buffer 和 stencil buffer. 最基本的是color buffer,创建它就好了 函数glGenRenderbuffers 它是为renderbuffer(渲染缓存)申请一个id(名字),创建渲染缓存 参数n表示申请生成renderbuffer的个数 参数renderbuffers返回分配给renderbuffer(渲染缓存)的id 。 注意:返回的id不会为0,id 0 是OpenGL ES保留的,我们也不能使用id 为0的renderbuffer(渲染缓存)。 函数glBindRenderbuffer 告诉OpenGL:我在后面引用GL_RENDERBUFFER的地方,其实是引用_colorRenderBuffer 参数target必须为GL_RENDERBUFFER 参数renderbuffer就是使用glGenRenderbuffers生成的id 。 当指定id的renderbuffer第一次被设置为当前renderbuffer时,会初始化该 renderbuffer对象,其初始值为: 函数renderbufferStorage 把渲染缓存(renderbuffer)绑定到渲染图层(CAEAGLLayer)上,并为它分配一个共享内存。 参数target,为哪个renderbuffer分配存储空间 参数drawable,绑定在哪个渲染图层,会根据渲染图层里的绘图属性生成共享内存。 实战代码 05-创建帧缓冲区 它相当于buffer(color, depth, stencil)的管理者,三大buffer可以附加到一个framebuffer上 本质是把framebuffer内容渲染到屏幕 函数glFramebufferRenderbuffer 该函数是将相关buffer()三大buffer之一)attach到framebuffer上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕 参数target,哪个帧缓存 参数attachment是指定renderbuffer被装配到那个装配点上,其值是GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一个,分别对应 color,depth和 stencil三大buffer。 renderbuffertarget:哪个渲染缓存 renderbuffer渲染缓存id 06-创建着色器 着色器 什么是着色器? 通常用来处理纹理对象,并且把处理好的纹理对象渲染到帧缓存上,从而显示到屏幕上。 提取纹理信息,可以处理顶点坐标空间转换,纹理色彩度调整(滤镜效果)等操作。 着色器分为顶点着色器,片段着色器 顶点着色器用来确定图形形状 片段着色器用来确定图形渲染颜色 步骤: 1.编辑着色器代码 2.创建着色器 3.编译着色器 只要创建一次,可以在一开始的时候创建 着色器代码 实战代码 07-创建着色器程序 步骤: 1.创建程序 2.贴上顶点和片段着色器 3.绑定attribute属性 4.连接程序 5.绑定uniform属性 6.运行程序 注意点:第3步和第5步,绑定属性,必须有顺序,否则绑定不成功,造成黑屏 08-创建纹理对象 纹理 采集的是一张一张的图片,可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中 什么是纹理?一个纹理其实就是一幅图像。 纹理映射,我们可以把这幅图像的整体或部分贴到我们先前用顶点勾画出的物体上去. 比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。 纹理映射是一个相当复杂的过程,基本步骤如下: 1)激活纹理单元、2)创建纹理 、3)绑定纹理 、4)设置滤波 注意:纹理映射只能在RGBA方式下执行 函数glTexParameter 控制滤波,滤波就是去除没用的信息,保留有用的信息 一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时,纹理的单个纹素很少对应于屏幕图像上的像素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小) 固定写法 函数glPixelStorei 设置像素存储方式 pname:像素存储方式名 一种是GL_PACK_ALIGNMENT用于将像素数据打包,一般用于压缩。 另一种是GL_UNPACK_ALIGNMENT,用于将像素数据解包,一般生成纹理对象,就需要用到解包. param:用于指定存储器中每个像素行有多少个字节对齐。这个数值一般是1、2、4或8, 一般填1,一个像素对应一个字节; 函数CVOpenGLESTextureCacheCreateTextureFromImage 根据图片生成纹理 参数allocator kCFAllocatorDefault,默认分配内存 参数textureCache 纹理缓存 参数sourceImage 图片 参数textureAttributes NULL 参数target , GL_TEXTURE_2D(创建2维纹理对象) 参数internalFormat GL_LUMINANCE,亮度格式 参数width 图片宽 参数height 图片高 参数format GL_LUMINANCE 亮度格式 参数type 图片类型 GL_UNSIGNED_BYTE 参数planeIndex 0,切面角标,表示第0个切面 参数textureOut 输出的纹理对象 实战代码 09-YUV转RGB绘制纹理 纹理映射只能在RGBA方式下执行 而采集的是YUV,所以需要把YUV 转换 为 RGBA, 本质其实就是改下矩阵结构 注意点(熬夜凌晨的bug):glDrawArrays如果要绘制着色器上的点和片段,必须和着色器赋值代码放在一个代码块中,否则找不到绘制的信息,就绘制不上去,造成屏幕黑屏 之前是把glDrawArrays和YUV转RGB方法分开,就一直黑屏. 函数glUniform1i 指定着色器中亮度纹理对应哪一层纹理单元 参数location:着色器中纹理坐标 参数x:指定那一层纹理 函数glEnableVertexAttribArray 开启顶点属性数组,只有开启顶点属性,才能给顶点属性信息赋值 函数glVertexAttribPointer 设置顶点着色器属性,描述属性的基本信息 参数indx:属性ID,给哪个属性描述信息 参数size:顶点属性由几个值组成,这个值必须位1,2,3或4; 参数type:表示属性的数据类型 参数normalized:GL_FALSE表示不要将数据类型标准化 参数stride 表示数组中每个元素的长度; 参数ptr 表示数组的首地址 函数glBindAttribLocation 给属性绑定ID,通过ID获取属性,方便以后使用 参数program 程序 参数index 属性ID 参数name 属性名称 函数glDrawArrays 作用:使用当前激活的顶点着色器的顶点数据和片段着色器数据来绘制基本图形 mode:绘制方式 一般使用GL_TRIANGLE_STRIP,三角形绘制法 first:从数组中哪个顶点开始绘制,一般为0 count:数组中顶点数量,在定义顶点着色器的时候,就定义过了,比如vec4,表示4个顶点 注意点,如果要绘制着色器上的点和片段,必须和着色器赋值代码放在一个代码块中,否则找不到绘制的信息,就绘制不上去,造成屏幕黑屏。 实战代码 请点击此处输入图片描述请点击此处输入图片描述 10-渲染缓冲区到屏幕 注意点:必须设置窗口尺寸glViewport 注意点:渲染代码必须调用[EAGLContext setCurrentContext:_context] 原因:因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏. 注意点:每次创建纹理前,先把之前的纹理引用清空[self cleanUpTextures],否则卡顿 函数glViewport 设置OpenGL渲染窗口的尺寸大小,一般跟图层尺寸一样. 注意:在我们绘制之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小 方法presentRenderbuffer 是将指定renderbuffer呈现在屏幕上 实战代码 11-清理内存 注意:只要有Ref结尾的,都需要自己手动管理,清空 函数glClearColor 设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏. 函数glClear 用来指定要用清屏颜色来清除由mask指定的buffer,mask可以是 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT的自由组合。 在这里我们只使用到 color buffer,所以清除的就是 clolor buffer。 GPUImage工作原理 GPUImage最关键在于GPUImageFramebuffer这个类,这个类会保存当前处理好的图片信息。 GPUImage是通过一个链条处理图片,每个链条通过target连接,每个target处理完图片后,会生成一个GPUImageFramebuffer对象,并且把图片信息保存到GPUImageFramebuffer。 这样比如targetA处理好,要处理targetB,就会先取出targetA的图片,然后targetB在targetA的图片基础上在进行处理. 本文转自ljianbing51CTO博客,原文链接:http://blog.51cto.com/ljianbing/1921654 ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android开发笔记——常见BUG类型之内存泄露与线程安全

一、内存泄露 1、很抱歉,”XXX”已停止运行。OOM? 怎样才能让app报OOM呢?最简单的办法如下: Bitmapbt1=BitmapFactory.decodeResource(this.getResources(),R.drawable.image); Bitmapbt2=BitmapFactory.decodeResource(this.getResources(),R.drawable.image); Bitmapbtn=... 2、查看内存占用 命令行:adb shell dumpsys meminfopackageName 通过Android Studio的Memory Monitor查看内存中Dalvik Heap的实时变化 3、发生内存泄露的条件 首先,每个app有最大内存限制。 ActivityManageractivityManager=(ActivityManager)context.getSystemServiceContext.ACTIVITY_SERVICE); activityManager.getMemoryClass(); getMemoryClass()取到的是最大内存资源。Android中的堆内存分为NativeHeap和DalvikHeap。C/C++申请的内存空间在NativeHeap中,Java申请的内存空间则在DalvikHeap中。对于head堆的大小限制,可以查看/system/build.prop文件: dalvik.vm.heapstartsize=8m dalvik.vm.heapgrowthlimit=96m dalvik.vm.heapsize=256m 注意: heapsize参数表示单个进程heap可用的最大内存,但如果存在以下参数”dalvik.vm.headgrowthlimit =96m”表示单个进程heap内存被限定在96m,即程序运行过程实际只能使用96m内存。 如果申请的内存资源超过上述限制,系统就会抛出OOM错误。 4、常见避免OOM的措施 以下主要从四个方面总结常见的措施:1)减小对象的内存占用;2)内存对象的重复利用;3)避免对象的内存泄露;4)内存使用策略优化。 4.1 减小对象的内存占用 使用ArrayMap/SparseArray而不是HashMap等传统数据结构。 参考链接:Android内存优化(使用SparseArray和ArrayMap代替HashMap) 在Android中避免使用枚举。 减小Bitmap对象的内存占用。inSampleSize和decode format。 4.2 内存对象的重复利用 ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用 使用LRU机制缓存Bitmap 避免在onDraw方法里面执行对象的创建 使用StringBuilder来替代频繁的”+” 4.3 避免对象的内存泄露 4.1和4.2都是比较常规的措施,4.3需要重点关注。 1)Activity泄露 导致Activity泄露的原因较多,下面列举一些比较常见的。从原理上主要分为两类:i)静态对象;ii)this$0。 Activity被static变量引用。这段代码来自于我们的Crash上传 privatestaticMap<ComponentName,ExceptionHandler>configMap= newHashMap<ComponentName,ExceptionHandler>();publicstaticvoidsetActivity(finalActivityactivity,booleansend2Server){ Log.d(TAG,"bindexceptionhandler:"+activity.getComponentName().getClassName());//上下文初始化SDKContext.init(activity.getApplication()); init(activity.getApplication()); ExceptionHandlerexceptionHandler=newExceptionHandler( activity,send2Server,Thread.getDefaultUncaughtExceptionHandler()); configMap.put(activity.getComponentName(),exceptionHandler); Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); } 下面是通过MAT分析一个Activity泄露的截图: 内部类引用导致Activity的泄漏 最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。 可参考链接:线程通信 2)考虑使用Application Context而不是Activity Context 对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。 3)注意临时Bitmap对象的及时回收 虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。 4)内存占用监控 通过Runtime获取maxMemory,而maxMemory-totalMemory即为剩余可使用的dalvik内存。定期检查这个值,达到80%就去释放各种cache资源(bitmap的cache)。 /** *Returnsthemaximumnumberofbytestheheapcanexpandto.See{@link#totalMemory}forthe *currentnumberofbytestakenbytheheap,and{@link#freeMemory}forthecurrentnumberof *thosebytesactuallyusedbyliveobjects.*/intmaxMemory=Runtime.getRuntime().maxMemory());//应用程序最大可用内存/** *Returnsthenumberofbytestakenbytheheapatitscurrentsize.Theheapmayexpandor *contractovertime,asthenumberofliveobjectsincreasesordecreases.See *{@link#maxMemory}forthemaximumheapsize,and{@link#freeMemory}foranideaofhowmuch *theheapcouldcurrentlycontract.*/longtotalMemory=Runtime.getRuntime().totalMemory());//应用程序已获得内存/** *Returnsthenumberofbytescurrentlyavailableontheheapwithoutexpandingtheheap.See *{@link#totalMemory}fortheheap'scurrentsize.Whenthesebytesareexhausted,theheap *mayexpand.See{@link#maxMemory}forthatlimit.*/longfreeMemory=Runtime.getRuntime().freeMemory());//应用程序已获得内存中未使用内存 5)注意Cursor对象是否及时关闭 在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。 4.4 内存使用策略优化 谨慎使用large heap 综合考虑设备内存阈值与其他因素设计合适的缓存大小 onLowMemory()/onTrimMemory(int) 资源文件需要选择合适的文件夹进行存放 Try catch某些大内存分配的操作 谨慎使用static对象 优化布局层次,减少内存消耗 谨慎使用多进程 谨慎使用依赖注入框架 使用ProGuard来剔除不需要的代码 谨慎使用第三方libraries 考虑不同的实现方式来优化内存占用 二、线程安全 1、下面的方法是线程安全的吗? 怎样使上述方法线程安全? 2、Java中的线程安全 怎样保持在多线程环境下的数据一致性,Java提供了多种方法实现: synchronized java.util.concurrent.atomic java.util.concurrent.locks thread safe collection(ConcurrentHashMap) volatile 2.1 synchronized JVM保证被synchronized关键字修饰的代码段在同一时间只能被一个线程访问,内部通过对对象或类加锁来实现的。当方法被synchronized修饰时,锁加在对象上;当方法同时为static时,锁加在类上。从性能的角度来讲,一般不建议直接将锁加在类上,这样会使得类的所有对象的该方法均为synchronized的。 从之前扫描的问题来看,在编写synchronized程序时主要有两点需要注意: synchronized需要创建基于对象或者类的锁,所以不能在构造器或者变量上加锁。 synchronized造成死锁。 1) 锁加在哪里? List<ResultPoint>currentPossible=possibleResultPoints; List<ResultPoint>currentLast=lastPossibleResultPoints;intframeLeft=frame.left;intframeTop=frame.top;if(currentPossible.isEmpty()){ lastPossibleResultPoints=null; }else{ possibleResultPoints=newArrayList<>(5); lastPossibleResultPoints=currentPossible; paint.setAlpha(CURRENT_POINT_OPACITY); paint.setColor(resultPointColor);synchronized(currentPossible){for(ResultPointpoint:currentPossible){ canvas.drawCircle(frameLeft+(int)(point.getX()*scaleX),frameTop+(int)(point.getY()*scaleY),POINT_SIZE, paint); } } } 上述方法中,possibleResultPoints的创建没有采用同步措施,需要使用Collections.synchronizedXxx。 List<MyType>list=Collections.synchronizedList(newArrayList(<MyType>)); ...synchronized(list){for(MyTypem:list){ foo(m); m.doSomething(); } } 一般比较推荐创建一个虚拟的对象专门用于获取锁。 //dummyobjectvariableforsynchronizationprivateObjectmutex=newObject(); ...//usingsynchronizedblocktoread,incrementandupdatecountvaluesynchronouslysynchronized(mutex){ count++; } PS:直接在方法上加synchronized可能DoS攻击喔,举个栗子: publicclassMyObject{//Locksontheobject'smonitor publicsynchronizedvoiddoSomething(){ //...} }//黑客的代码MyObjectmyObject=newMyObject();synchronized(myObject){while(true){//IndefinitelydelaymyObjectThread.sleep(Integer.MAX_VALUE); } } 黑客的代码获取了MyObject对象的锁,导致doSomething死锁,从而引发Denial of Service。 publicclassMyObject{//locksontheclassobject'smonitor publicstaticsynchronizedvoiddoSomething(){ //...} }//黑客的代码synchronized(MyObject.class){while(true){ Thread.sleep(Integer.MAX_VALUE);//IndefinitelydelayMyObject} } 2) 死锁。 publicclassThreadDeadlock{publicstaticvoidmain(String[]args)throwsInterruptedException{ Objectobj1=newObject(); Objectobj2=newObject(); Objectobj3=newObject(); Threadt1=newThread(newSyncThread(obj1,obj2),"t1"); Threadt2=newThread(newSyncThread(obj2,obj3),"t2"); Threadt3=newThread(newSyncThread(obj3,obj1),"t3"); t1.start(); Thread.sleep(5000); t2.start(); Thread.sleep(5000); t3.start(); } }classSyncThreadimplementsRunnable{privateObjectobj1;privateObjectobj2;publicSyncThread(Objecto1,Objecto2){this.obj1=o1;this.obj2=o2; } @Overridepublicvoidrun(){ Stringname=Thread.currentThread().getName(); System.out.println(name+"acquiringlockon"+obj1);synchronized(obj1){ System.out.println(name+"acquiredlockon"+obj1); work(); System.out.println(name+"acquiringlockon"+obj2);synchronized(obj2){ System.out.println(name+"acquiredlockon"+obj2); work(); } System.out.println(name+"releasedlockon"+obj2); } System.out.println(name+"releasedlockon"+obj1); System.out.println(name+"finishedexecution."); }privatevoidwork(){try{ Thread.sleep(30000); }catch(InterruptedExceptione){ e.printStackTrace(); } } } 本文转自xsster51CTO博客,原文链接:http://blog.51cto.com/12945177/1929765 ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android GIS开发系列-- 入门季(6)GraphicsLayer添加文字与图片标签

一、GraphicsLayer添加图片 GraphicLayer添加图片Graphic,要用到PictureMarkerSymbol,也是样式的一种。添加代码如下: Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher); PictureMarkerSymbol pictureMarkerSymbol = new PictureMarkerSymbol(this, drawable); Graphic graphic = new Graphic(new Point(113,22), pictureMarkerSymbol); graphicsLayer.addGraphic(graphic); 添加图片效果如下: 二、GraphicsLayer添加文字 GraphicLayer添加文字,要利用TextSymbol。代码如下 TextSymbol textSymbol = new TextSymbol(20,"我是文字" , Color.RED); Graphic graphic = new Graphic(new Point(113,22), textSymbol ); graphicsLayer.addGraphic(graphic); 上面的代码没有设置使用哪种字体,Arcgis会默认的字体。如果 我们想添加中文,要设置textSymbol.setFontFamily("DroidSansFallback.ttf");, 为textSymbol设置字体,arcgis会到system\fonts下找到此字体。但有时我们设置此代码时中文并没有出现,可能会有乱码等,是因为系统的fon ts下面并没有此字体,解决的办法有,将手机root,将DroidSansFallback.ttf字体添加到system\fonts下。华为的手机是有此字体的,而三星5.0以 上则没有此字体,在华为手机上还有一款中文DroidSansChinese.ttf字体arcgis也是支持的。而三星5.0以上的中文字体好像不是ttf字体,而是otf字体 ,Arcgis好像不支持,本人也没有找到好的解决办法。 没有整理与归纳的知识,一文不值!高度概括与梳理的知识,才是自己真正的知识与技能。 永远不要让自己的自由、好奇、充满创造力的想法被现实的框架所束缚,让创造力自由成长吧! 多花时间,关心他(她)人,正如别人所关心你的。理想的腾飞与实现,没有别人的支持与帮助,是万万不能的。 本文转自wenglabs博客园博客,原文链接:http://www.cnblogs.com/arxive/p/7751888.html ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android Ap 开发 设计模式第一篇:迭代器模式

Iterator Pattern迭代器模式 场景猜想 经常编写代码都会碰到遍历一个数组,使用for循环得到数组下标之后去做进一步操作。例如下文代码: int []array = new int [ 5 ]; for ( int i = 0 ;i < array.length;i ++ ){ System.out.println( "" + i); } 或: int []array = new int [ 5 ]; for ( int i:array){ System.out.println( "" + i); } 当然,还有更多的作法,利用将 i 将下标推移的方式遍历数组元素。 使用设计模式来编写的时候,可以将 i 的行为,抽象化为迭代器,这种模式我们称之为迭代器模式 。迭代器模式可以用来作为遍历一个集合体。java 亦提供了一个工具类:java.util.Iterator<E> ,与其类似。 场景需求 书架上有5本技术书籍,要求将书籍名称一性遍历出来,将显示在Andriod 的 EditText 上面。以上面的需求为例,可以将具体需求具体到下面的示意图: Aggregate接口 Aggregate 接口是一个执行递增的“聚合”。实现此接口的类就变成类似数组、集合的“聚合”。表示己聚合的接口。 Iterator接口 Iterator 接口执行元素递增,具有类似循环变量的功能。表示执行递增、遍历的接口。 Book 类 Book 表示书籍,主要是为了获取书的详细。 BookShelf类 BookShelf 表示书架,此类实现Aggregate接口从而将其作为聚合处理。 BookShelfIterator类 BookShelfIterator表示扫描书架的类,实现了Iterator 的接口功能。 ----------------------------------------- IteratorPatternsActivity 类 IteratorPatternsActivity 为Android 的主界面,用来显示和测试上面编写的代码 ----------------------------------------- 代码实现: Aggregate接口 /** *声明一个Iterator方法,这是为了建立一个可对应聚合的Iterator *如欲递增、遍历或逐一检查某一个聚合时,利用iterator方法即可建立一个 *实现Iterator接口的类对象实例。 * @author terry * */ public interface Aggregate{ public abstract Iteratoriterator(); } Iterator 接口 public interface Iterator{ public abstract boolean hasNext(); // 存在一条则返回true public abstract Objectnext(); // 移到下一条记录 } next 方法是返回聚合当中的1个元素。不过,next 方法的功能并非仅仅如此。事实上它还会悄悄地先要在内部状态进入下一步,以便下次调用 next 方法时能确实返回下一个元素。而Iterator 只描述了有两个方法,具体这两个方法如何实现还要看BookShelfIterator内部的实现。 Book 类 /** *表示书籍的类 * @author terry * */ public class Book{ private Stringname = "" ; public Book(Stringname){ this .name = name; } public StringgetName(){ return this .name; } } BookShelf 类 /** *表示书架意图的类 *要实现Aggreagate接口才能把这个类当作聚合来处理。 * @author terry * */ public class BookShelf implements Aggregate{ private Book[]books; private int last = 0 ; public BookShelf( int maxSize){ this .books = new Book[maxSize]; } public BookgetBookAt( int index){ return books[index]; } public BookShelfappendBook(Bookbook){ this .books[last] = book; last ++ ; return this ; } public int getLength(){ return this .last; } @Override public Iteratoriterator(){ // TODOAuto-generatedmethodstub return new BookShelfIterator( this ); } } BookShelf实现了Aggregate接口 ,该方法产生并返回BookShelfIterator类的对象实例,即我们发一个命令说要对书架进行扫描时,就将得到的“聚合”返回出去。 BookShelfIterator 类 1 /** 2 *执行扫描书架用的“聚合”类 3 * @author Administrator 4 * 5 */ 6 public class BookShelfIterator implements Iterator{ 7 8 private BookShelfbookShelf; 9 private int index; 10 11 public BookShelfIterator(BookShelfbookShelf){ 12 this .bookShelf = bookShelf; 13 this .index = 0 ; 14 } 15 16 17 @Override 18 public boolean hasNext(){ 19 // TODOAuto-generatedmethodstub 20 if (index < bookShelf.getLength()){ 21 return true ; 22 } else { 23 24 return false ; 25 } 26 27 } 28 29 @Override 30 public Objectnext(){ 31 // TODOAuto-generatedmethodstub 32 Bookbook = bookShelf.getBookAt(index); 33 index ++ ; 34 35 return book; 36 } 37 38 } 做完上面一系列的代码编写之后,我们就可以在Android 的主界面上使用上面的Iterator 遍历出上面给出的需求。 IteratorPatternsActivity类 public class IteratorPatternsActivity extends Activity{ /** Calledwhentheactivityisfirstcreated. */ @Override public void onCreate(BundlesavedInstanceState){ super .onCreate(savedInstanceState); setContentView(R.layout.main); EditTextet = (EditText)findViewById(R.id.EditText01); BookShelfbookShelf = new BookShelf( 5 ); bookShelf.appendBook( new Book( " androiddeveloper " )) .appendBook( new Book( " WindowsPhone7developer " )) .appendBook( new Book( " IOSdeveloper " )) .appendBook( new Book( " BADAdeveloper " )) .appendBook( new Book( " Liondeveloper " )); Iteratoriterator = bookShelf.iterator(); while (iterator.hasNext()){ Bookbook = (Book)iterator.next(); et.setText(et.getText() + " \n " + book.getName()); } } } 上面执行的结果: 更多的功能扩展 上面只是讲述了两个功能,hasNext()和Next()方法,想想如果要在这个基础上面扩展将会是多么方便,例如: 由后往前的反向遍历 由前往后、由后往前的双向遍历(Next 、Previous......) 指定一个下标立即得到 等等 系列学习文章第一篇到此结束。下次学习Adapter Pattern (适配器模式) 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/603456,如需转载请自行联系原作者

优秀的个人博客,低调大师

打开键盘遮住View的问题解决方法-IOS开发

5.0 以后键盘的高度貌似不是216了,不过不要紧,我们调整调整就是了: iPhone ipad 竖屏(portrait) 216 264 横屏(landScape) 140 352 我们采取的方法就是在textField(有可能是其他控件)接收到弹出键盘事件时把self.view整体上移216px了(我们就以iPhone竖屏为例了)。 有关View的frame,origin,size之类的知识点不懂的请参看我的另一篇博文: <<有关View的几个基础知识点>> 首先我们要设置textField的代理,我们就设为当前控制器了。 textField,delegate=self; 然后我们在当前控制器实现下面三个委托方法: [java]view plaincopy print? -(void)textFieldDidBeginEditing:(UITextField*)textField {//当点触textField内部,开始编辑都会调用这个方法。textField将成为firstresponder NSTimeIntervalanimationDuration=0.30f; CGRectframe=self.view.frame; frame.origin.y-=216; frame.size.height+=216; self.view.frame=frame; [UIViewbeginAnimations:@"ResizeView"context:nil]; [UIViewsetAnimationDuration:animationDuration]; self.view.frame=frame; [UIViewcommitAnimations]; } [java]view plaincopy print? -(BOOL)textFieldShouldReturn:(UITextField*)textField {//当用户按下ruturn,把焦点从textField移开那么键盘就会消失了 NSTimeIntervalanimationDuration=0.30f; CGRectframe=self.view.frame; frame.origin.y+=216; frame.size.height-=216; self.view.frame=frame; //self.view移回原位置 [UIViewbeginAnimations:@"ResizeView"context:nil]; [UIViewsetAnimationDuration:animationDuration]; self.view.frame=frame; [UIViewcommitAnimations]; [textFieldresignFirstResponder]; } 本文转自 卓行天下 51CTO博客,原文链接:http://blog.51cto.com/9951038/1772565,如需转载请自行联系原作者

优秀的个人博客,低调大师

【移动开发】Android相机、相册获取图片显示并保存到SD卡

做过类似需求的同学都知道,在Activity中通过如下代码可以启动相机,然后在重写的onActivityResult方法中可以获取到返回的照片数据: 1 2 IntentopenCameraIntent= new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(openCameraIntent,TAKE_PICTURE); 在onActivityResult方法里通过Intent的getData方法获取的数据转换成bitmap并显示在界面上,有时候会有取不到数据,或者显示的bitmap会非常小,如果将bitmap保存到sd卡后会发现,图片的分辨率很低,并且图片大小也是经过压缩的,不管将相机的像素设置多高,最后通过这种方式返回的bitmap总是经过压缩了的。如果想获得理想的照片大小和分辨率改如何处理呢? 大家都知道,现在手机像素少则500W或800W,多则4KW(某亚),就拿常见的800W像素的相机拍出来的照片来说,分辨率大概在3200*2400左右,照片大小在2M左右。试想一下,在Android系统中bitmap占用4个字节,3200*2400*4=?,结果大家自己算算,如果为了一张图片,耗用这么大的内存,肯定是不合理的,并且,官方文档中有说明,Android系统分配给每个应用的最大内存是16M,所以,系统为了防止应用内存占用过大,对于在应用内通过相机拍摄的图片最终返回来的结果进行了压缩,压缩后的图片变得很小,通过之前说的getData的方式只能满足比如显示个头像这样的需求。 如果要显示大图,就会出现模糊的情况。那如何获取清晰的大图呢?我的解决思路如下: 1.拍照时,将拍得的照片先保存在本地,通过修改之前的代码如下: 1 UriimageUri=Uri.fromFile( new File(Environment.getExternalStorageDirectory(), "image.jpg" )); //指定照片保存路径(SD卡),image.jpg为一个临时文件,每次拍照后这个图片都会被替换 1 openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri); 如何调取相机拍照,代码如下: 1 2 3 4 5 6 7 8 9 10 11 /**拍照获取相片**/ private void doTakePhoto(){ Intentintent= new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //调用系统相机 UriimageUri=Uri.fromFile( new File(Environment.getExternalStorageDirectory(), "image.jpg" )); //指定照片保存路径(SD卡),image.jpg为一个临时文件,每次拍照后这个图片都会被替换 intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri); //直接使用,没有缩小 startActivityForResult(intent,PHOTO_WITH_CAMERA); //用户点击了从相机获取 } 2.在onActivityResult方法中再将图片取出,并经过缩小处理再显示在界面上或上传给服务器(压缩比例自定义) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override protected void onActivityResult( int requestCode, int resultCode,Intentdata){ super .onActivityResult(requestCode,resultCode,data); if (resultCode==RESULT_OK){ switch (requestCode){ case TAKE_PICTURE: //将保存在本地的图片取出并缩小后显示在界面上 Bitmapbitmap=BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+ "/image.jpg" ); BitmapnewBitmap=zoomBitmap(bitmap,bitmap.getWidth()/SCALE,bitmap.getHeight()/SCALE); //由于Bitmap内存占用较大,这里需要回收内存,否则会报outofmemory异常 bitmap.recycle(); //将处理过的图片显示在界面上,并保存到本地 iv_image.setImageBitmap(newBitmap); savePhotoToSDCard(newBitmap,Environment.getExternalStorageDirectory().getAbsolutePath(), String .valueOf(System.currentTimeMillis())); break ; default : break ; } } } 由于Android给bitmap分配的内存最大不超过8M,所以对使用完的较大的Bitmap要释放内存,调用其recycle()方法即可。然后将缩小后的bitmap显示在界面上或保存到SD卡,至于之前保存的原图,可以删掉,也可以放在那,下次拍照时,这张原图就会被下一张照片覆盖,所以SD卡上使用只有一张临时图片,占用也不是很大。 以上讲的是拍照获取图片,如果是从相册中获取图片又如何处理呢,我的方法如下: 1.打开相册选取图片: 1 2 3 IntentopenAlbumIntent= new Intent(Intent.ACTION_GET_CONTENT); openAlbumIntent.setType( "image/*" ); startActivityForResult(openAlbumIntent,CHOOSE_PICTURE); 2.在onActivity方法中处理获取到的图片,思路和之前类似 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Override protected void onActivityResult( int requestCode, int resultCode,Intentdata){ super .onActivityResult(requestCode,resultCode,data); if (resultCode==RESULT_OK){ switch (requestCode){ case CHOOSE_PICTURE: ContentResolverresolver=getContentResolver(); //照片的原始资源地址 UrioriginalUri=data.getData(); try { //使用ContentProvider通过URI获取原始图片 Bitmapphoto=MediaStore.Images.Media.getBitmap(resolver,originalUri); if (photo!= null ){ //为防止原始图片过大导致内存溢出,这里先缩小原图显示,然后释放原始Bitmap占用的内存 BitmapsmallBitmap=zoomBitmap(photo,photo.getWidth()/SCALE,photo.getHeight()/SCALE); //释放原始图片占用的内存,防止outofmemory异常发生 photo.recycle(); iv_image.setImageBitmap(smallBitmap); } } catch (FileNotFoundExceptione){ e.printStackTrace(); } catch (IOExceptione){ e.printStackTrace(); } break ; default : break ; } } } 还有一个方法 zoomBitmap(),代码如下: 1 2 3 4 5 6 7 8 9 10 11 /**缩放Bitmap图片**/ public BitmapzoomBitmap(Bitmapbitmap, int width, int height){ int w=bitmap.getWidth(); int h=bitmap.getHeight(); Matrixmatrix= new Matrix(); floatscaleWidth=((float)width/w); floatscaleHeight=((float)height/h); matrix.postScale(scaleWidth,scaleHeight); //利用矩阵进行缩放不会造成内存溢出 Bitmapnewbmp=Bitmap.createBitmap(bitmap, 0 , 0 ,w,h,matrix, true ); return newbmp; } 至此,功能已实现。 下面是本人项目中所实现的功能在这里总结一下: 1.要想对从图库选择的照片进行裁剪: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /**从相册获取图片**/ private IntentdoPickPhotoFromGallery(){ Intentintent= new Intent(); intent.setType( "image/*" ); //开启Pictures画面Type设定为image intent.setAction(Intent.ACTION_GET_CONTENT); //使用Intent.ACTION_GET_CONTENT这个Action //实现对图片的裁剪,必须要设置图片的属性和大小 intent.setType( "image/*" ); //获取任意图片类型 intent.putExtra( "crop" , "true" ); //滑动选中图片区域 intent.putExtra( "aspectX" , 1 ); //裁剪框比例1:1 intent.putExtra( "aspectY" , 1 ); intent.putExtra( "outputX" , 300 ); //输出图片大小 intent.putExtra( "outputY" , 300 ); intent.putExtra( "return-data" , true ); //有返回值 return intent; } 调用此方法处: 1 2 Intentintent2=doPickPhotoFromGallery(); startActivityForResult(intent2,PHOTO_WITH_DATA); ActivityForResult中和上面一样 2.项目中要拍多少张 就保存多少张,显示图片列表: A.将拍照的照片或者图库选择的图片,保存到本地 创建图片名,不能重复哦! 1 2 3 4 5 6 7 8 9 /**为图片创建不同的名称用于保存,避免覆盖**/ public static String createFileName(){ String fileName= "" ; Date date= new Date (System.currentTimeMillis()); //系统当前时间 SimpleDateFormatdateFormat= new SimpleDateFormat( "'IMG'_yyyyMMdd_HHmmss" ); fileName=dateFormat.format(date)+ ".jpg" ; return fileName; } 保存图片到SD卡 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /**SaveimagetotheSDcard**/ public static void savePhotoToSDCard( String path, String photoName, BitmapphotoBitmap){ if (android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)){ Filedir= new File(path); if (!dir.exists()){ dir.mkdirs(); } FilephotoFile= new File(path,photoName); //在指定路径下创建文件 FileOutputStreamfileOutputStream= null ; try { fileOutputStream= new FileOutputStream(photoFile); if (photoBitmap!= null ){ if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100 , fileOutputStream)){ fileOutputStream.flush(); } } } catch (FileNotFoundExceptione){ photoFile. delete (); e.printStackTrace(); } catch (IOExceptione){ photoFile. delete (); e.printStackTrace(); } finally { try { fileOutputStream.close(); } catch (IOExceptione){ e.printStackTrace(); } } } } B.最后就是显示图片列表,因为我们要用到listView,自然少不了Adapter了,我们将保存到SD卡上的图片名获取到集合中,在自定义的适配器中根据名字加载图片喽! 自定义图片列表适配器代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 /** *插入图片列表适配器 *@authorZHF * */ public class ImagesListAdapter extends BaseAdapter{ private Contextcontext; private List< String >imagesList; //各个图片的路径 public ImagesListAdapter(Contextcontext,List< String >imagesList){ this .context=context; this .imagesList=imagesList; } /**得到总的数量**/ @Override public int getCount(){ //TODOAuto-generatedmethodstub return imagesList.size(); } /**根据ListView位置返回View**/ @Override public Object getItem( int position){ return imagesList. get (position); //返回当前选中的item图片的路径 } /**根据ListView位置得到List中的ID**/ @Override public longgetItemId( int position){ //TODOAuto-generatedmethodstub return position; //返回当前选中项的Id } /**根据位置得到View对象**/ @Override public ViewgetView( int position,ViewconvertView,ViewGroupparent){ if (convertView== null ){ convertView=LayoutInflater.from(context).inflate(R.layout.newwrite_image_item, null ); } ImageViewimg=(ImageView)convertView.findViewById(R.id.newwrite_et_content_image); //图片列表项 if (!imagesList. get (position).equals( "" )){ //没有图片 BitmaptempBitmap=BitmapFactory.decodeFile(imagesList. get (position)); //根据路径显示对应的图片 BitmapnewBitmap= new ImageManager(context).zoomBitmap(tempBitmap,tempBitmap.getWidth(),tempBitmap.getHeight()/ 3 ); img.setImageBitmap(newBitmap); //对应的行上显示对应的图片 } return convertView; } } ok!完了,在显示图片的时候大家可能会碰到OOM(OutOfMemory)异常,在下一篇博客中我会具体解决了一下~ Demo已上传!下载附件即可! 本文转自zhf651555765 51CTO博客,原文链接:http://blog.51cto.com/smallwoniu/1248695,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册