Android 视图框架系列2/3——SurfaceView视图框架
2016-01-06 11:51
447 查看
本篇我们来说说 SurfaceView 视图框架。
![](http://img.blog.csdn.net/20160115160429347?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](http://img.blog.csdn.net/20160115160615059?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](http://img.blog.csdn.net/20160115160630791?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在前一篇 视图框架系列1/3——View视图框架 中我们已经知道,利用 View 可以完成动画效果的实现,现在我们来实现这样一个需求——我们绘制一个不断做跑步动作的人,但是位置不变(在 MyView 类中通过子线程不断通知重复重绘右边两个不同状态的小兵即可),动画效果是实现了。但是如果我要求响应事件,点击屏幕让他喊
“军情、军情”,按方向键让他向相应的方向跑。这么做?按照原理来说是:给 MyView 重写 onTouchEvent() 响应触屏事件、重写 onKeyDown() 方法响应方向键。但是别忘了——View框架的重绘是不断重新执行 onDraw() 方法,而 onDraw() 是在 UI 线程中执行的,此时 UI 线程正在不断执行 onDraw() 方法,很容易导致 UI 线程阻塞而不能响应触屏和按键。虽说当线程间断休眠时间够长即相邻两个
onDraw() 之间间断时间足够长时可以响应事件,但是我们怎么可以让我们的程序处于一个不可控的状态呢?现在用 View 便不再能够满足我们的需求。
有比较才能显出区别:看完 视图框架系列1/3——View视图框架 ,在结合上边的情景,不难总结出这样的结论:View 可以实现动画效果,但是仅仅局限于两种:
★(1)“被动更新”的动画,“被动”是指这个动画(“视图”更为准确)不主动更新,它的更新依赖于 onTouchEvent、onKeyDown 等事件来触发 onDraw 的重绘;
★(2)只显示一个动画而不接受事件响应,很好理解,比如我在页面上显示一个闪动的星星,这个星星只是展示作用,不接受任何事件。
★另外:View 视图绘制的效率比较低,因为每一次重绘实际上是重新执行以便 onDraw 方法,onDraw 方法是重新绘制整个画布,而在 View 中,canvas 就是整个视图。
而此时的窘境,正是 SurfaceView 视图大显身手的时候!SurfaceView 继承于 View ,所以同样拥有触屏监听、按钮监听等方法,但是请注意,SurfaceView 看名字就和 Surface 脱不了干系,Surface 是 Android 中一个很重要的类,有必要了解一下。每个 View 在和屏幕绑定时都会关联一个对应的 Surface,你可以把
Surface 理解成一块屏幕缓存。但从源码可以看出 SurfaceView 还有一个 Surface 类型的成员变量,所以 SurfaceView 就拥有了两个内存区。 这里就该说 SurfaceView 的双缓冲机制了。
SurfaceView就是一个典型的双缓冲机制,其内嵌的 Surface 专门处理待绘制的内容,包括各式、尺寸等,在真正绘制时 SurfaceView 控制其绘制位置进行真正的绘制显示。所以 Surface 用来做游戏视图处理再合适不过了。
以上算是一个五脏六腑俱全的 Surfaceiew 的使用了。仔细看代码你会发现,我们在使用 SurfaceView 时,并没有直接和 SurfaceView “交手”,而是通过 getHolder() 获取到 SurfaceHolder 类的实例进行操作,通过 holder.lockCanvas()方法获取到 Canvas 对象,就可以进行自己的绘制了,最后用
holder.unLockCanvasAndPost(canvas) 进行提交。*要知道* lockCanvas() 方法不仅仅是获取 Canvas,同时还对即将获取到的 Canvas 添加同步锁,这个机制主要是为了防止 SurfaceView 在修改的过程中被修改、摧毁等情况发生;unLockCanvasAndPost(canvas)
方法是解除同步锁并把之前所做的画布绘制工作进行提交。
*附加* SurfaceHolder 类除了提供lockCanvas() 方法用于获取当前 Canvas (默认与手机屏幕大小一致)外,还提供了其重载的 lockCanvas(Rect mRect) 方法用于获取自定义举行大小的画布,若有问题可以参看
Canvas有关问题整理。
需要重申和留意的是:SurfaceView 是通过 SurfaceHolder 来操作的,所以使用 SurfaceView 时不再使用 onDraw(Canvas canvas) 来绘图,而是通过 holder.lockCanvas() 获取Canvas 来绘图,即使重写了 onDraw() 方法, SurfaceView
在启动时也不会执行到。
首先改造 myDraw() 方法:
![](http://img.blog.csdn.net/20160118144847756?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
看这个结果图,说明触屏动作及时响应了,否则也不会出现触点位置的字符,但是并没有抹掉旧状态,就是因为我们没有“刷屏”。接下来我们在 canvas.drawText() 之前加上:
擦!!用白色
![](http://img.blog.csdn.net/20160118145704870?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
有坑,都看不到边界,用字给框个边界吧,我真是太聪明了!
这就是“刷屏”(娘的!别再瞎想你的朋友圈刷屏了)。这里就是区别:View类本身提供的两种重绘方法 invalidate()、postInvalidate() 内部已经封装了刷屏的操作(具体是什么操作,最好看源码,我没看,不敢妄言),所以每次重绘之后都看不到之前的历史记录;但是用 SurfaceView 时我们用lockCanvas () 获取到的是同一个画布,绘图用的是自定义绘图方法 myDraw(),系统没有帮我们刷新画布,也,没有提供给我们一张新画布,所以在进行下一次绘画时必定能看到上一次的痕迹,这时候我们就要自己动手“刷屏":
"刷屏"就是盖调上次绘画的痕迹,所以这里有两种方法:
(1)每次绘图之前,绘制一个屏幕大小的图形(或是图片,如游戏背景)覆盖在原来的画布上;
(2)每次绘画之前,给画布填充一种颜色(上边我们用到的),如 drawRGB()、drawColor() 。
中国添加线程会遇到一些问题,下面我们来一一探讨一下。
这是一个比较绕的问题,请集中注意力细细品味:
结果不用看,就是一个跑动的字符串,重点看代码的设计思路。
这是一个完成视图“主动更新”的部分,不包含用户动作响应,但是“主动”这一模块是完整的。下边对代码中涉及到的问题进行说明:
(1)便于线程的消亡,资源释放
一个线程一旦启动就开始执行 run() 函数,run() 函数执行完毕后退出,该线程随之消亡,这是一般的线程。二般的需求是,拿游戏背景举例,要在线程中 While 循环,不断更新背景的显示,在游戏暂停或退出时便需要有个 flag 判断 run() 完成。
(2)防止线程重复创建及线程启动异常
大家都知道,Android 退出当前界面有两种方式:Back 键、Home 键。对于一个 SurfaceView ,通过这两种方式退出,再返回程序中,有什么区别呢?
Back键:surfaceDestroyed ——> 构造函数 ——> surfaceCreated ——> surfaceChanged;
Home键:surfaceDestroyed ——> surfaceCreated ——> surfaceChanged;
很明显,Back 键返回再进入额外执行了一次构造函数,也就是说,Back 返回再进入时 SurfaceView 会重新加载一次。也正是这个原因,线程的初始化和线程启动的位置就很有讲究了:
![](http://img.blog.csdn.net/20160118172230240?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
有 3 种方式(总不能 thread 先启动再初始化吧):
蓝色线:线程初始化在构造函数(以下称为 struct)中,线程启动在视图创建函数(以下称为 surfaceCreated)中,在这种情况下会有线程启动异常情况发生:点 Home 键返回再进入时,会尝试启动线程,此时报错如下:
![](http://img.blog.csdn.net/20160118173625527?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
原因是 Home 情况下未执行 struct 便没有新创建 thread,尝试启动是线程仍是之前已经启动过的线程,系统报错线程已经启动。(附加:Back 情况正确运行);
黑色线:线程初始化和启动都在 struct 中,这种情况更明显:Home 情况下,再次进入后线程不会再运行,视图不动了;
最好的办法就是红色线:线程初始化和启动都在 surfaceCreated 中,并且在 surfaceDestroy 时将 flag 置为 false 使线程停止运行而销毁。这样既避免了线程的重复创建(flag 的作用),又避免了“线程已启动”的异常。
放在 finally 语句中。
并且提交之前也要判断 canvas 不为 null,保证提交的是有效的画布。
0,因为在这之前试图还没有创建,宽高当然是 0。
> 刷新周期”时,当然不要耽搁,尽快开始下一个周期。
好了,SurfaceView 主要内容就差不多了,可是累死了!如感觉对你有帮助,那就一起来继续下一篇 “Android
视图框架系列3/3——View和SurfaceView之间的抉择” 吧!
一、提出问题
SurfaceView 继承自 View ,但其实和 View 已经有了很大的区别,可以说这一层的继承,跨的有点大!来看这样一个情景:在前一篇 视图框架系列1/3——View视图框架 中我们已经知道,利用 View 可以完成动画效果的实现,现在我们来实现这样一个需求——我们绘制一个不断做跑步动作的人,但是位置不变(在 MyView 类中通过子线程不断通知重复重绘右边两个不同状态的小兵即可),动画效果是实现了。但是如果我要求响应事件,点击屏幕让他喊
“军情、军情”,按方向键让他向相应的方向跑。这么做?按照原理来说是:给 MyView 重写 onTouchEvent() 响应触屏事件、重写 onKeyDown() 方法响应方向键。但是别忘了——View框架的重绘是不断重新执行 onDraw() 方法,而 onDraw() 是在 UI 线程中执行的,此时 UI 线程正在不断执行 onDraw() 方法,很容易导致 UI 线程阻塞而不能响应触屏和按键。虽说当线程间断休眠时间够长即相邻两个
onDraw() 之间间断时间足够长时可以响应事件,但是我们怎么可以让我们的程序处于一个不可控的状态呢?现在用 View 便不再能够满足我们的需求。
有比较才能显出区别:看完 视图框架系列1/3——View视图框架 ,在结合上边的情景,不难总结出这样的结论:View 可以实现动画效果,但是仅仅局限于两种:
★(1)“被动更新”的动画,“被动”是指这个动画(“视图”更为准确)不主动更新,它的更新依赖于 onTouchEvent、onKeyDown 等事件来触发 onDraw 的重绘;
★(2)只显示一个动画而不接受事件响应,很好理解,比如我在页面上显示一个闪动的星星,这个星星只是展示作用,不接受任何事件。
★另外:View 视图绘制的效率比较低,因为每一次重绘实际上是重新执行以便 onDraw 方法,onDraw 方法是重新绘制整个画布,而在 View 中,canvas 就是整个视图。
而此时的窘境,正是 SurfaceView 视图大显身手的时候!SurfaceView 继承于 View ,所以同样拥有触屏监听、按钮监听等方法,但是请注意,SurfaceView 看名字就和 Surface 脱不了干系,Surface 是 Android 中一个很重要的类,有必要了解一下。每个 View 在和屏幕绑定时都会关联一个对应的 Surface,你可以把
Surface 理解成一块屏幕缓存。但从源码可以看出 SurfaceView 还有一个 Surface 类型的成员变量,所以 SurfaceView 就拥有了两个内存区。 这里就该说 SurfaceView 的双缓冲机制了。
二、双缓冲技术
双缓冲技术是游戏开发中一个重要技术,主要原理——当一个动画争先显示时,程序又在改变它,前面的还没有显示完,程序又要求重绘,这样屏幕就会产生闪烁,为了避免这种闪烁,可以将要处理的图片先在内存中处理好之后,再将其显示到屏幕上,这样显示出来的总是完整的,便不会产生闪烁。SurfaceView就是一个典型的双缓冲机制,其内嵌的 Surface 专门处理待绘制的内容,包括各式、尺寸等,在真正绘制时 SurfaceView 控制其绘制位置进行真正的绘制显示。所以 Surface 用来做游戏视图处理再合适不过了。
三、SurfaceView的使用
在继承 SurfaceView 开发时,自定义类要继承自 SurfaceView ,而且要实现 SurfceHolder.Callback 接口,那这个接口是干嘛的, SurfaceHolder 类又是什么呢?我们先看一段简单代码:public class MySurfaceView extends SurfaceView implements Callback { private SurfaceHolder holder; private Paint paint; public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); //给Holder声明Callback回调 paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setTextSize(50); } /* Callback方法之一,SurfaceView创建时回调 */ @Override public void surfaceCreated(SurfaceHolder arg0) { myDraw(); } /* Callback方法之一,SurfaceView改变时回调 */ @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { } /* Callback方法之一,SurfaceView销毁时回调 */ @Override public void surfaceDestroyed(SurfaceHolder arg0) { } /* 我自己的绘制方法 */ private void myDraw() { String text = "Holle-World-!"; Canvas canvas = holder.lockCanvas(); //获取画布 canvas.drawColor(Color.WHITE); //刷屏 canvas.drawText(text, 0, 50, paint); //绘制内容 holder.unlockCanvasAndPost(canvas); //提交对画布所做的操作 } }
以上算是一个五脏六腑俱全的 Surfaceiew 的使用了。仔细看代码你会发现,我们在使用 SurfaceView 时,并没有直接和 SurfaceView “交手”,而是通过 getHolder() 获取到 SurfaceHolder 类的实例进行操作,通过 holder.lockCanvas()方法获取到 Canvas 对象,就可以进行自己的绘制了,最后用
holder.unLockCanvasAndPost(canvas) 进行提交。*要知道* lockCanvas() 方法不仅仅是获取 Canvas,同时还对即将获取到的 Canvas 添加同步锁,这个机制主要是为了防止 SurfaceView 在修改的过程中被修改、摧毁等情况发生;unLockCanvasAndPost(canvas)
方法是解除同步锁并把之前所做的画布绘制工作进行提交。
*附加* SurfaceHolder 类除了提供lockCanvas() 方法用于获取当前 Canvas (默认与手机屏幕大小一致)外,还提供了其重载的 lockCanvas(Rect mRect) 方法用于获取自定义举行大小的画布,若有问题可以参看
Canvas有关问题整理。
需要重申和留意的是:SurfaceView 是通过 SurfaceHolder 来操作的,所以使用 SurfaceView 时不再使用 onDraw(Canvas canvas) 来绘图,而是通过 holder.lockCanvas() 获取Canvas 来绘图,即使重写了 onDraw() 方法, SurfaceView
在启动时也不会执行到。
四、刷屏
在上边的代码的 myDraw() 方法中,有一行 “ canvas.drawColor(Color.WHITE) ; //刷屏 ”的语句,从字面意思看就是绘制了一种颜色,什么是刷屏呢?我先看问题再解释(我们让上边代码中绘制的“Holle-World-!”随手指触摸屏幕的位置变化):首先改造 myDraw() 方法:
String text = "Holle-World-!"; private int positionX = 0, positionY = 0; //初始化X,Y为0 private void myDraw() { Canvas canvas = holder.lockCanvas(); //获取画布 //这里先不画颜色(刷屏) canvas.drawText(text, positionX, positionY + 50, paint); //Y轴方向+50是因为绘制Text时指定的是左下角位置,+50用来校正位置 holder.unlockCanvasAndPost(canvas); //提交对画布所做的操作 }添加触屏响应:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: positionX = (int)event.getX(); positionY = (int)event.getY(); myDraw(); break; default: break; } return super.onTouchEvent(event); }运行一下,多点几下就会出现这个样子:
看这个结果图,说明触屏动作及时响应了,否则也不会出现触点位置的字符,但是并没有抹掉旧状态,就是因为我们没有“刷屏”。接下来我们在 canvas.drawText() 之前加上:
canvas.drawColor(Color.WHITE); //刷屏再运行:
擦!!用白色
有坑,都看不到边界,用字给框个边界吧,我真是太聪明了!
这就是“刷屏”(娘的!别再瞎想你的朋友圈刷屏了)。这里就是区别:View类本身提供的两种重绘方法 invalidate()、postInvalidate() 内部已经封装了刷屏的操作(具体是什么操作,最好看源码,我没看,不敢妄言),所以每次重绘之后都看不到之前的历史记录;但是用 SurfaceView 时我们用lockCanvas () 获取到的是同一个画布,绘图用的是自定义绘图方法 myDraw(),系统没有帮我们刷新画布,也,没有提供给我们一张新画布,所以在进行下一次绘画时必定能看到上一次的痕迹,这时候我们就要自己动手“刷屏":
"刷屏"就是盖调上次绘画的痕迹,所以这里有两种方法:
(1)每次绘图之前,绘制一个屏幕大小的图形(或是图片,如游戏背景)覆盖在原来的画布上;
(2)每次绘画之前,给画布填充一种颜色(上边我们用到的),如 drawRGB()、drawColor() 。
五、在 SurfaceView 中添加线程
从本篇开篇我们总结前一篇 View 视图框架的局限来看,我们用 SurfaceView 的目的就是要一边完成视图的“主动更新”,一边还要响应用户操作,所以在 SurfaceView 中添加线程完成视图主动更新是必不可少的,比如游戏背景的变化,用户无法操作,而且背景是根据游戏情景不断更新的。但是在 SurfaceView中国添加线程会遇到一些问题,下面我们来一一探讨一下。
这是一个比较绕的问题,请集中注意力细细品味:
public class MySurfaceView extends SurfaceView implements Runnable, Callback { private SurfaceHolder holder; private Paint paint; private String text = "Holle!"; private Canvas canvas; private boolean flag; private Thread thread; private int positionX = 0, positionY = 0; private int screenW, screenH; private int speedX = 10, speedY = 13; public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setTextSize(50); } @Override public void surfaceCreated(SurfaceHolder arg0) { screenW = getWidth(); screenH = getHeight(); //看——>说明4 flag = true; thread = new Thread(this); thread.start(); } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { } @Override public void surfaceDestroyed(SurfaceHolder arg0) { flag = false; } private void myDraw() { try { //看——>说明2 canvas = holder.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.WHITE); canvas.drawText(text, positionX, positionY + 50, paint); } } catch (Exception e) { // TODO: handle exception } finally { holder.unlockCanvasAndPost(canvas); //看——>说明3 } } /** * 模拟一个逻辑 */ private void myLogic() { if (positionX < 0 || positionX > screenW - 100) { speedX = -speedX; } if (positionY < 0 || positionY > screenH) { speedY = -speedY; } positionX += speedX; positionY += speedY; } @Override public void run() { while (flag) { //看——>说明1 long start = System.currentTimeMillis(); //看——>说明5 myDraw(); myLogic(); long end = System.currentTimeMillis(); try { if (end - start < 100) { Thread.sleep(100 - (end - start)); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果不用看,就是一个跑动的字符串,重点看代码的设计思路。
这是一个完成视图“主动更新”的部分,不包含用户动作响应,但是“主动”这一模块是完整的。下边对代码中涉及到的问题进行说明:
(一)说明1:线程标识 flag 的作用
线程标志 flag 主要有两个作用:(1)便于线程的消亡,资源释放
一个线程一旦启动就开始执行 run() 函数,run() 函数执行完毕后退出,该线程随之消亡,这是一般的线程。二般的需求是,拿游戏背景举例,要在线程中 While 循环,不断更新背景的显示,在游戏暂停或退出时便需要有个 flag 判断 run() 完成。
(2)防止线程重复创建及线程启动异常
大家都知道,Android 退出当前界面有两种方式:Back 键、Home 键。对于一个 SurfaceView ,通过这两种方式退出,再返回程序中,有什么区别呢?
Back键:surfaceDestroyed ——> 构造函数 ——> surfaceCreated ——> surfaceChanged;
Home键:surfaceDestroyed ——> surfaceCreated ——> surfaceChanged;
很明显,Back 键返回再进入额外执行了一次构造函数,也就是说,Back 返回再进入时 SurfaceView 会重新加载一次。也正是这个原因,线程的初始化和线程启动的位置就很有讲究了:
有 3 种方式(总不能 thread 先启动再初始化吧):
蓝色线:线程初始化在构造函数(以下称为 struct)中,线程启动在视图创建函数(以下称为 surfaceCreated)中,在这种情况下会有线程启动异常情况发生:点 Home 键返回再进入时,会尝试启动线程,此时报错如下:
原因是 Home 情况下未执行 struct 便没有新创建 thread,尝试启动是线程仍是之前已经启动过的线程,系统报错线程已经启动。(附加:Back 情况正确运行);
黑色线:线程初始化和启动都在 struct 中,这种情况更明显:Home 情况下,再次进入后线程不会再运行,视图不动了;
最好的办法就是红色线:线程初始化和启动都在 surfaceCreated 中,并且在 surfaceDestroy 时将 flag 置为 false 使线程停止运行而销毁。这样既避免了线程的重复创建(flag 的作用),又避免了“线程已启动”的异常。
(二)说明2:视图绘制时的异常 try
因为 SurfaceView 在未创建或者不可编辑时,调用 lockCanvas() 时将返回 null,Canvas 在绘图时也将出现不可预知的错误,所以用 try...catch...进行处理,并且为避免 lockCanvas() 为 null 时的问题,在绘制之前判断 canvas 是否为空进行处理。(三)说明3:画布提交的时机
绘图的时候可能出现不可预知的 Bug,虽然用 try...catch...包裹起来保证程序不会崩溃;但是如果在画布提交之前出现异常,本次将跳过提交,在下次获取画布时就会抛出异常,原因就是上次画布就没有解锁也没有提交。所以要将 unlockCanvasAndPost()放在 finally 语句中。
并且提交之前也要判断 canvas 不为 null,保证提交的是有效的画布。
(四)说明4:视图尺寸的获取时机
SurfaceView 的 getWidth()、getHeight() 要在视图创建之后即 surfaceCreated 调用之后执行,否则得到的结果永远为0,因为在这之前试图还没有创建,宽高当然是 0。
(五)说明5:刷新频率一致性的保证
不管是动画还是变形,都要保证变动的效果要保持流畅,所以很有必要保证线程每运行完一次的时间相同,即刷新频率的一致。就像代码中做的那样,设定一个刷新周期,并记录下线程开始时的时间戳,和绘图、逻辑运行完后的时间戳,比较运行时间和刷新周期,如果“运行时间 < 刷新周期”,就让线程把刷新周期中剩余的时间“睡”过去,如果“运行时间> 刷新周期”时,当然不要耽搁,尽快开始下一个周期。
好了,SurfaceView 主要内容就差不多了,可是累死了!如感觉对你有帮助,那就一起来继续下一篇 “Android
视图框架系列3/3——View和SurfaceView之间的抉择” 吧!
相关文章推荐
- 1.0 Android学习路线简要介绍
- Android SQLite服务--创建、增删改查
- 获取APK的签名MD5值
- 开发基于高德SDK的Android车载导航应用
- Android:PopWindow
- AndroidManifest.xml文件解析
- 第1部分 Android环境搭建入门
- Android应用程序的debug属性
- android 读取assets下文件或者 java读取本地文件
- AndroidStudio 成长之路之Theme Editor
- Android系统之路(初识MTK) ------ 更改设备盘符名称/型号/名称/品牌/Version/Devices/Product......
- Android Studio更新升级方法
- ADB 常见问题
- Android Sensor——传感器
- Android实现短信加密功能(发送加密短信、解密本地短信)
- Android --- bitmapfactory 使用getResources()小结
- Android 视图框架系列1/3——View视图框架
- android 快速开发工具
- android studio运行时报错
- Android:自定义窗口标题