画布 Canvus

canvas.drawARGB(255,0,0,255);

canvas.drawRGB(123,34,45);

canvas.drawColor(Color.parseColor(“#343434”));

canvas.drawCircle(400,800,200,mPaint);

public void drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, @NonNull Paint paint)

canvas.drawArc(200,200,1000,1000,-90,90,false,mPaint);

canvas.drawArc(200,200,1000,1000,-90,90,true,mPaint);

canvas.drawPoint(100,100,mPaint);
canvas.drawPoints(new float[]{100,100,300,300,400,400},mPaint);
canvas.drawLine(100,100,400,400,mPaint);

canvas.drawLines(new float[]{100,100,400,600,500,100,200,200},mPaint);
//这里需要注意的是点坐标数,必须是4的倍数

canvas.drawOval(200,400,800,800,mPaint);

canvas.drawRect(100,100,600,600,mPaint);

canvas.drawRoundRect(100,100,400,400,100,60,mPaint);

mPaint.setTextSize(100);
canvas.drawText(“Hello Android”,300,400,mPaint);

Path path = new Path();
path.moveTo(200,200);
path.addRect(200,200,400,400, Path.Direction.CW);
path.addArc(200,200,800,800,100,300);
canvas.drawPath(path,mPaint);

canvaus 画布操作:

Android 使用Layer堆栈来管理画布的,画布有多个图层,整个结构如下所示,下面就针对图层操作做将要介绍:

Canvas.save()  用来保存当前的matrix和clip到私有的栈中。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
任何matrix变换和clip操作都会在调用restore的时候还原,它有一个整形返回值可以传入到restoreToCount()方法,以返回到某个save状态之前。也可以直接调用restore返回到前一个状态。
Canvas.restore() 将save之前的所有操作和save之后的所有操作合并,它只回到上一个save调用之前的状态。
save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
restoreToCount() 回到任何一个save()方法调用之前的状态
Canvas.getSaveCount(); 返回栈中保存的状态,值等于save()调用次数-restore()调用次数

Canvas.translate() 平移画布
Canvas.rotate() 旋转画布
Canvas.saveLayer(),saveLayerAlpha() 将图层入栈
本身和save方法差不多,但是它单独分配了一个画布用于绘制图层。它定义了一个画布区域(saveLayerAlpha可设置透明度),此方法之后的所有绘制都在此区域中绘制,直到调用canvas.restore()方法。
Layer入栈时,后续的操作都发生在这个Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层.在绘制到上层或是Canvas上时,可以指定Layer的透明度

Canvas && SurfaceView

在使用上面介绍的方式在View上绘图的时候,它是在GUI线程中绘制的,在GUI线程中同时也用来处理用户的交互事件,这是不大合理的,单绘制的元素太多时候,会阻塞线程,轻者影响用户体验,重则倒置ANR。
也许大家会想到将onDraw方法移到后台,但是这种方式是可行的,从后台线程修改GUI元素是不允许的。
并且我们知道View是通过系统发出VSYNC信号进行屏幕重绘的,一般刷新时间间隔为16ms,如果在16ms内可以完成所有的操作,那么将不会带来卡顿感觉,而如果超过这个时间还不能完成绘制任务,就会导致GUI线程的阻塞。
当需要快速更新View的UI,或者当前绘制阻塞GUI线程时间过长的时候,应该使用Surfaceview而不是View。SurfaceView封装的是Surface而不是Canvas。这一切原因是由于Surface使用后台线程进行绘制,而Canvase不能。
但是大家有时候会感到奇怪,为什么我们看到自定义View很多都是继承View而不是SurfaceView,这是因为SurfaceView使用的是后台线程进行绘制的,这回增加内存的消耗。所以一般能在16ms内完成绘制的话就直接继承View

所以最好在绘制高速图像,或者绘制工作不能在规定时间完成,又或者需要绘制3D图形的情况下才需要使用SurfaceView。

下面我们就来介绍下SurfaceView的用法:
在介绍SurfaceView的用法之前我们先了解下Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback。

如上面所述SurfaceView提供了一个专门用于绘制的surface。它允许在后台线程中进行绘制,你可以通过SurfaceHolder控制这个Surface的格式和尺寸。Surfaceview控制让这个Surface显示在屏幕的正确绘制位置。
Surface是Z-ordered(Z轴排序),这表明它总在自己所在窗口的后面。surfaceview在显示窗口处为Surface提供了一个可见区域,通过这个区域,才能看到Surface里面的内容。
由于这个特性我们可以放置一些覆盖图层(overlays)在Surface上面,如Button、Textview之类的。但是,需要注意的是,如果Surface上面有全透明的控件,那么随着Surface的每一次变化,这些全透明的控件就会重新渲染,这样有可能会增加系统的负担。

可以通过SurfaceHolder这个接口去访问Surface,而执行getHolder()方法可以得到SurfaceHolder接口。
SurfaceHolder是控制surface的一个抽象接口,它是一个句柄得到了这个句柄就可以得到其中的Canvas、原生缓冲器以及其它方面的内容。你可以通过SurfaceHolder来控制surface的尺寸和格式,或者修改surface的像素,监视surface的变化等等。
与直接控制SurfaceView来修改surface不同,使用SurfaceHolder来修改surface时,需要注意lockCanvas()和Callback.surfaceCreated()这两个方法。
SurfaceView有如下重要方法

abstract void addCallback(SurfaceHolder.Callback callback) 给SurfaceHolder添加一个一个回调对象。用于监视SurfaceView的某些状态,可以在这些回调方法中对某个特定事件发生的时候进行必要的处理
SurfaceHolder.Callback将会在后面进行介绍
abstract void removeCallback(SurfaceHolder.Callback callback) 移除回调对象

abstract Canvas lockCanvas(Rect dirty)
锁定画布中的某一个区域,返回的画布对象Canvas(当更新的内容只有一个区域时,同时要追求高效,可以只更新一部分的区域,而不必更新全部画布区域)

abstract Canvas lockCanvas() 锁定画布,返回的画布对象Canvas,和上面不同的是这里锁定的是整个画布
abstract void unlockCanvasAndPost(Canvas canvas) 结束锁定画布,并提交改变。

SurfaceHolder.Callback是监听surface改变的一个接口

public abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
surface发生改变时被调用,传入的是新Surface的新尺寸

public abstract void surfaceCreated(SurfaceHolder holder)

在surface创建时被调用,一般在这个方法里面开启渲染屏幕的线程。

public abstract voidsurfaceDestroyed(SurfaceHolder holder)
销毁时被调用,一般在这个方法里将渲染的线程停止。

所有SurfaceView 和 SurfaceHolder.Callback的方法都应该在主线程(UI线程)里面调用。必须确保只有当Surface有效的时候,(也就是当Surface的生命周期在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间)才能让渲染进程访问。

下面是这几个对象的关系图

SurfaceView 绘图的模板:

public class surfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

private Context mContext = null;
private SurfaceHolder mHolder = null;
private Canvas mCanvas = null;
private boolean surfaceViewStatus = false;
private Thread mDrawThread = null;
private void init(Context context) {
mContext = context;
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}

public surfaceView(Context context) {
super(context);
init(context);
}

public surfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public surfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceViewStatus = true;
if(mDrawThread == null) {
mDrawThread = new Thread(this);
}
mDrawThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceViewStatus = false;
if(mDrawThread != null) {
try {
mDrawThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mDrawThread = null;
}
}

@Override
public void run() {
while (surfaceViewStatus) {
try {
mCanvas = mHolder.lockCanvas();
//在这里画图
}finally {
if(mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
}

画笔Paint

在现实生活中我们要画一幅画需要有画笔,画布,还有颜料,Android系统中也是一样,在接下去的章节中将对这些绘画元素进行一一介绍,而在这个部分我们就先介绍画笔对象,画笔有很多重要的属性,比如画笔的颜色,画笔的线宽,画笔的样式等

画笔的基本属性:

setARGB(int a,int r,int g,int b); 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
setAlpha(int a); 设置绘制图形的透明度,设置范围是[0..255]
setColor(int color); 设置绘制的颜色,该颜色值包括透明度和RGB颜色。
setAntiAlias(boolean aa); 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。一般在绘制棱角分明的图像时不需要抗锯齿。
setDither(boolean dither); 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
setStyle(Paint.Style style); 设置画笔的样式,Paint.Style.STROKE:描边,Paint.Style.FILL_AND_STROKE:描边并填充,Paint.Style.FILL:填充
setStrokeWidth(float width); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度,也就是线条宽度
setStrokeCap(Paint.Cap cap); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样Cap.ROUND,或方形样式Cap.SQUARE
setStokeJoin(Paint.Join join); 设置绘制时各图形的结合方式,如平滑效果等
setStrokeMiter(float miter): 设置画笔倾斜度

这个部分重点介绍下setStrokeCap和setStokeJoin。


Android StrokeCap 属性对比图


Android StokeJoin 属性对比图

文字相关属性:

setTextSize(float textSize); 设置绘制文字的字号大小
setLetterSpacing(float letterSpacing) 设置行间距,默认是0.
setFakeBoldText(boolean fakeBoldText); 模拟实现粗体文字
setTextScaleX(float scaleX); 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果,当值大于1会沿X轴水平放大文本,当值小于1会沿X轴水平缩放文本
setTextSkewX(float skewX); 设置斜体文字,skewX为倾斜弧度,值为负右倾值为正左倾
setUnderlineText(boolean underlineText); 设置带有下划线的文字效果
setStrikeThruText(boolean strikeThruText); 设置带有删除线的效果
setTextAlign(Paint.Align align); 设置绘制文字的对齐方向 可供选的方式有三种:CENTER,LEFT和RIGHT。
setTypeface(Typeface typeface); 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
画笔的特效
setFilterBitmap(boolean filter); 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置
setMaskFilter(MaskFilter maskfilter); 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等
setColorFilter(ColorFilter colorfilter); 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
setPathEffect(PathEffect effect); 设置绘制路径的效果,如点画线等
setShader(Shader shader); 设置图像效果,使用Shader可以绘制出各种渐变效果
setShadowLayer(float radius ,float dx,float dy,int color); 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
setXfermode(Xfermode xfermode); 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果

画笔这部分最重要也是最难的就是画笔特效这部分。
接下来我们将会介绍
setMaskFilter
setColorFilter
setShader
setShadowLayer
setXfermode
setPathEffect

一、setMaskFilter(MaskFilter maskfilter)

setMaskFilter(MaskFilter maskfilter)是用来给画笔添加某种效果,这里的效果主要有两类一类是模糊效果,一类是浮雕效果,下面将对这两个效果进行介绍:

  • BlurMaskFilter 主要是用来实现添加一些阴影轮廓效果,这里需要注意的是在使用这个效果的时候需要关闭硬件加速功能,这里的关闭有两种方式一种是在AndroidManifest中关闭,这种关闭是全局的在这个地方关闭将会导致整个应用都不能使用硬件加速,
    还有一种方式是使用setLayerType(LAYER_TYPE_SOFTWARE, null); 将硬件加速给关闭,这种关闭方式只是针对某个具体的View,其他View还是可以使用硬件加速功能的
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.INNER));
canvas.drawCircle(400,200,100,mPaint);
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL));
canvas.drawCircle(400,450,100,mPaint);
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));
canvas.drawCircle(400,700,100,mPaint);
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.SOLID));
canvas.drawCircle(400,950,100,mPaint);

Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test1);
Bitmap b = bitmap.extractAlpha();
canvas.drawBitmap(b,200,1150,mPaint);
canvas.drawBitmap(bitmap,200,1150,null);
}

EmbossMaskFilter 我个人几乎没有用到,所以这里先不作介绍,后续有机会使用的时候会增加该部分用法

ColorMatrix

在介绍ColorFilter之前我们先了解下色彩三元素的概念:
色彩三元素指的是色调(色相),饱和度,亮度:
下面是来自百度百科的解释:

色相

色彩是由于物体上的物理性的光反射到人眼视神经上所产生的感觉。色的不同是由光的波长的长短差别所决定的。
作为色相,指的是这些不同波长的色的情况。波长最长的是红色,最短的是紫色。
把红、橙、黄、绿、蓝、紫和处在它们各自之间的红橙、黄橙、黄绿、蓝绿、蓝紫、红紫这6种中间色——共计12种色作为色相环。
在色相环上排列的色是纯度高的色,被称为纯色。这些色在环上的位置是根据视觉和感觉的相等间隔来进行安排的。用类似这样的方法还可以再分出差别细微的多种色来。
在色相环上,与环中心对称,并在180度的位置两端的色被称为互补色。

饱和度

用数值表示色的鲜艳或鲜明的程度称之为彩度。有彩色的各种色都具有彩度值,无彩色的色的彩度值为0,对于有彩色的色的彩度(纯度)的高低,区别方法是根据这种色中含灰色的程度来计算的。
彩度由于色相的不同而不同,而且即使是相同的色相,因为明度的不同,彩度也会随之变化的。

亮度

表示色彩所具有的亮度和暗度被称为明度。计算明度的基准是灰度测试卡。黑色为0,白色为10,在0—10之间等间隔的排列为9个阶段。色彩可以分为有彩色和无彩色,但后者仍然存在着明度。作为有彩色
,每种色各自的亮度、暗度在灰度测试卡上都具有相应的位置值。彩度高的色对明度有很大的影响,不太容易辨别。在明亮的地方鉴别色的明度比较容易的,在暗的地方就难以鉴别。

在Android系统中用一个4*5的矩阵对图像进行处理。
颜色矩阵如下所示:

将M乘以如下的列矩阵:

得到如下的变换结果:

从上面可以看出:
M矩阵的第一行决定了变换后的红色,第二行决定了变换后的绿色,第三行决定了变换后的蓝色,最后一行决定了变换后的透明度

下面是一个测试ColorMatrix的实例:

但是对于矩阵的方式这种方法比较不直观,因此Android提供了一种单独设置H,S,V的方式来改变图像的色彩属性:
下面是使用的例子

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.jimmy.customviewtest.MainActivity">
<ImageView
android:id="@+id/imageview"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<LinearLayout
android:layout_marginBottom="15dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<SeekBar
android:max="180"
android:id="@+id/hvalue"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<SeekBar
android:id="@+id/svalue"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<SeekBar
android:id="@+id/vvalue"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {

private ImageView mImageView = null;
private int Hvalue;
private float Svalue;
private float Vvalue;
private SeekBar mHvalue;
private SeekBar mSvalue;
private int SMAX;
private int VMAX;
private SeekBar mVvalue;
private Bitmap mBitmap;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageview);
mHvalue = (SeekBar) findViewById(R.id.hvalue);
mSvalue = (SeekBar) findViewById(R.id.svalue);
mVvalue = (SeekBar) findViewById(R.id.vvalue);
mHvalue.setOnSeekBarChangeListener(this);
mSvalue.setOnSeekBarChangeListener(this);
SMAX = mSvalue.getMax();
VMAX = mVvalue.getMax();
mVvalue.setOnSeekBarChangeListener(this);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
}

@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

switch (seekBar.getId()) {
case R.id.hvalue:
Hvalue = progress;
break;
case R.id.svalue:
if (SMAX != 0) {
Svalue = progress*1.0F/SMAX;
}
break;
case R.id.vvalue:
if(VMAX != 0) {
Vvalue = progress*1.0F/VMAX;;
}
break;
}
setHSV();

}

private void setHSV() {

ColorMatrix hutMatrix = new ColorMatrix();
//设置RGB 的色调值 第一个参数为R,G,B,第二个参数为具体的色调
hutMatrix.setRotate(0,Hvalue);
hutMatrix.setRotate(1,Hvalue);
hutMatrix.setRotate(2,Hvalue);

ColorMatrix saturation = new ColorMatrix();
//设置饱和度
saturation.setSaturation(Svalue);

ColorMatrix lumMatrix = new ColorMatrix();
//设置亮度
lumMatrix.setScale(Vvalue,Vvalue,Vvalue,1f);

ColorMatrix colorMatrix = new ColorMatrix();
//postConcat 用于将矩阵的作用效果混合,从而产生叠加效果
colorMatrix.postConcat(hutMatrix);
colorMatrix.postConcat(saturation);
colorMatrix.postConcat(lumMatrix);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(mBitmap,0,0,paint);
mImageView.setImageBitmap(bitmap);
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}
}

着色器Shader

Android提供了四种着色器:

LinearGradient
RadialGradient
SweepGradient

BitmapShader
ComposeShader

下面将对这些着色器作简单介绍,都很简单主要用于填充画笔的

LinearGradient 线性变化

构造函数如下:

public LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions,Shader.TileMode tile)
参数:
float x0: 渐变起始点x坐标
float y0:渐变起始点y坐标
float x1:渐变结束点x坐标
float y1:渐变结束点y坐标
int[] colors:颜色 的int 数组
float[] positions: 相对位置的颜色数组,可为null, 如果为null,颜色沿渐变线均匀分布
Shader.TileMode tile: 渲染器平铺模式

public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,Shader.TileMode tile)
float x0: 渐变起始点x坐标
float y0:渐变起始点y坐标
float x1:渐变结束点x坐标
float y1:渐变结束点y坐标
int color0: 起始渐变色
int color1: 结束渐变色
Shader.TileMode tile: 渲染器平铺模式

RadialGradient 光束变化

构造函数如下:

public RadialGradient(float x, float y, float radius, int[] colors, float[] positions,Shader.TileMode tile)
float x: 圆心X坐标
float y: 圆心Y坐标
float radius: 半径
int[] colors: 渲染颜色数组
floate[] positions: 相对位置数组,可为null, 如果为null,颜色沿渐变线均匀分布
Shader.TileMode tile:渲染器平铺模式


public RadialGradient(float x, float y, float radius, int color0, int color1,Shader.TileMode tile)
float x: 圆心X坐标
float y: 圆心Y坐标
float radius: 半径
int color0: 圆心颜色
int color1: 圆边缘颜色
Shader.TileMode tile:渲染器平铺模式

SweepGradient 辐射变化
构造函数如下:

public SweepGradient(float cx, float cy, int[] colors, float[] positions)
Parameters:
cx 渲染中心点x 坐标
cy 渲染中心y 点坐标
colors 围绕中心渲染的颜色数组,至少要有两种颜色值
positions 相对位置的颜色数组,可为null, 如果为null,颜色沿渐变线均匀分布


public SweepGradient(float cx, float cy, int color0, int color1)
Parameters:
cx 渲染中心点x 坐标
cy 渲染中心点y 坐标
color0 起始渲染颜色
color1 结束渲染颜色

ComposeShader 两种shader混合
构造函数

public ComposeShader(Shader shaderA,Shader shaderB, Xfermode mode)
Parameters
shaderA 渲染器A,Shader及其子类对象
shaderB 渲染器B,Shader及其子类对象
mode 两种渲染器组合的模式,Xfermode对象

public ComposeShader(Shader shaderA,Shader shaderB, PorterDuff.Mode mode)
Parameters
shaderA 渲染器A,Shader及其子类对象
shaderB 渲染器B,Shader及其子类对象
mode .两种渲染器组合的模式,ProterDuff.Mode对象

BitmapShader 位图Shader
构造函数

public   BitmapShader(Bitmap bitmap,Shader.TileMode tileX,Shader.TileMode tileY)

bitmap 在渲染器内使用的位图
tileX The tiling mode for x to draw the bitmap in. 在位图上X方向渲染器平铺模式
tileY The tiling mode for y to draw the bitmap in. 在位图上Y方向渲染器平铺模式
TileMode:

下面代码是上述Shader的使用方法例子:
在看下面的例子的时候需要先明确下填充方式:

CLAMP  :如果渲染器超出原始边界范围,会复制范围内边缘染色。
REPEAT : 重复渲染器图片平铺。
MIRROR :重复渲染器图片,以镜像方式平铺。
public class ShaderView extends View {
private Context mContext = null;
public ShaderView(Context context) {
super(context);
mContext = context;
init();
}

public ShaderView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}

public ShaderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}

private Paint mPaint;
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);

}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(new LinearGradient(0,0,200,200, Color.BLUE,Color.YELLOW, Shader.TileMode.CLAMP));
canvas.drawRoundRect(0,0,1024,400,2F,3F,mPaint);

mPaint.setShader(new RadialGradient(400,800,500, Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP));
canvas.drawRoundRect(0,400,1024,800,2F,3F,mPaint);

mPaint.setShader(new SweepGradient(400,1200,Color.BLUE,Color.RED));
canvas.drawRoundRect(0,800,1024,1200,2F,3F,mPaint);

Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.test1);
mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
canvas.drawCircle(500,1600,400,mPaint);
}
}

使用setShadowLayer 给TextView 以及Button添加阴影:

上面介绍setMatrixFilder的时候介绍过给某个图形添加阴影,这里将要介绍的是给文本以及按钮添加阴影,这部分十分简单,只要熟悉下各个参数即可:

setShadowLayer共有4个参数:
第一个参数为模糊半径,越大越模糊。
第二个参数是阴影的横向偏移距离。
第三个参数是阴影的纵向偏移距离。
第四个参数是阴影颜色。
下面是该方法是使用例子

TextView textView = (TextView) findViewById(R.id.text1);
textView.setShadowLayer(20,30,20, Color.DKGRAY);

Button ButtomView = (Button) findViewById(R.id.text2);
ButtomView.setShadowLayer(30,50,80, Color.DKGRAY);

接下来我们介绍下setXfermode,这个主要用于图层的混合,它包含AvoidXfermode,PixelXorXfermode以及PorterDuffXfermode,前两者不常用所以在这里不做过多的介绍:
这里重点介绍下PorterDuffXfermode。
我们先看下下面这张图,总共有16种模式:

下面就使用如下的例子对这16种图层混合模式进行介绍:

public class XfermodeView extends View {

public XfermodeView(Context context) {
super(context);
init(context);
}

public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private Paint mPaint = null;
private Bitmap mSourceImage = null;
private Bitmap mDestImage = null;
private PorterDuffXfermode duff = null;
private void init(Context context) {
mPaint = new Paint();
mSourceImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.test);
mDestImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.background);
duff = new PorterDuffXfermode(PorterDuff.Mode.SCREEN);

}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
int saveCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mPaint,
Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(mDestImage,0,0,mPaint);
mPaint.setXfermode(duff);
canvas.drawBitmap(mSourceImage,100,600,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}

private int mTotalWidth;
private int mTotalHeight;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mTotalWidth = w;
mTotalHeight = h;
}
}
  1. 不使用任何效果的原图
  2. CLEAR SRC会将DST部分给清除掉所以画出来的效果只有白色背景(所绘制不会提交到画布上):
  3. SRC 只保留源图像的 alpha 和 color
  4. DST 只保留目标图像的 alpha 和 color
  5. SRC_OVER 在目标图片顶部绘制源图像,两个图都会显示
  6. DST_OVER 将目标图像绘制在上方。两个图都会显示
  7. SRC_IN 在两者相交的地方绘制源图像,
  8. DST_IN 在两者相交的地方绘制目标图像
  9. SRC_OUT 在不相交的地方绘制 源图像
  10. DST_OUT 在不相交的地方绘制 目标图像
  11. SRC_ATOP 源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像
  12. DST_ATOP 源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像
  13. XOR 在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和色值影响
  14. DARKEN 该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值
  15. LIGHTEN 该模式处理过后,会感觉效果变亮
  16. MULTIPLY 将基色与混合色复合。结果色总是较暗的颜色
  17. SCREEN 保留两个图层中较白的部分,较暗的部分被遮盖

我们知道现实生活中有两种常见的线条,一种是实线,一种是虚线,要绘制虚线我们就需要用到PathEffect
接下来的部分将会介绍使用setPathEffect()以及各种PathEffect

public class PathEffectView extends View {

public PathEffectView(Context context) {
super(context);
init();
}

public PathEffectView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public PathEffectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private Paint mPaint;
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
initPath();
}

private Path mPath;
private void initPath() {
mPath = new Path();
mPath.moveTo(10, 50);
for (int i = 0; i <= 30; i++) {
mPath.lineTo(i * 35, (float) (Math.random() * 500+300));
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint);
}
}

没有用任何效果的情况

CornerPathEffect
CornerPathEffect的作用是将路径的转角变得圆滑,CornerPathEffect的构造方法接受一个参数radius,用于表示转角处的圆滑程度。

DiscretePathEffect 会在路径上绘制很多“杂点”。它的构造方法有两个参数:
第一个指定这些突出的“杂点”的密度,值越小杂点越密集;第二个参数是“杂点”突出的大小,值越大突出的距离越大。

DashPathEffect 它用于产生点画线,它的构造函数有两个:
第一个参数是一个浮点型的数组,在定义该参数的时候只要浮点型数组中元素个数大于等于2即可,
数组中偶数索引的值代表实线的长度,而奇数索引的值表示虚线的长度,
第二个参数为偏移值,动态改变其值会让路径产生动画的效果。

PathDashPathEffect
PathDashPathEffect和DashPathEffect是类似的,不同的是PathDashPathEffect可以让我们自己定义路径虚线的样式,比如我们将其换成一个个小圆组成的虚线:

Path path = new Path();
path.addRect(0,0,20,20, Path.Direction.CCW);
PathEffect pathEffect = new PathDashPathEffect(path, 20, 1, PathDashPathEffect.Style.ROTATE);
mPaint.setPathEffect(pathEffect);

ComposePathEffect&&SumPathEffect
ComposePathEffect和SumPathEffect都可以用来组合两种路径效果,就是把两种效果二合一。唯一不同的是组合的方式:
ComposePathEffect(PathEffect outerpe, PathEffect innerpe)会先将路径变成innerpe的效果,再去复合outerpe的路径效果,即:outerpe(innerpe(Path));
SumPathEffect(PathEffect first, PathEffect second)则会把两种路径效果加起来再作用于路径。

颜色Color

开发一款Apk色彩搭配也是很重要的一个要素,今后会针对这个专题做个总结,在这里只是简单介绍下Android中颜色的使用:
我们可以按照如下方式使用Android已经提供了的颜色,这些颜色一般都是一些标准颜色:
Color.RED;
@android:color/xxxxxx

还可以通过各个颜色分量的数值设置颜色
Color.argb(123, 45, 56, 67);
Color.rgb(123,23,44);

在只知道十六进制的情况下个人比较喜欢用下面的方式,一般UE给出的颜色也是这种形式
Color.parseColor(“#343434”);

Android 开发中常用的颜色,这个是网上找的。

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#ffffff</color> <!-- 白色 -->
<color name="ivory">#fffff0</color> <!-- 象牙色 -->
<color name="lightyellow">#ffffe0</color> <!-- 亮黄色 -->
<color name="yellow">#ffff00</color> <!-- 黄色 -->
<color name="snow">#fffafa</color> <!-- 雪白色 -->
<color name="floralwhite">#fffaf0</color> <!-- 花白色 -->
<color name="lemonchiffon">#fffacd</color> <!-- 柠檬绸色 -->
<color name="cornsilk">#fff8dc</color> <!-- 米绸色 -->
<color name="seaShell">#fff5ee</color> <!-- 海贝色 -->
<color name="lavenderblush">#fff0f5</color> <!-- 淡紫红 -->
<color name="papayawhip">#ffefd5</color> <!-- 番木色 -->
<color name="blanchedalmond">#ffebcd</color> <!-- 白杏色 -->
<color name="mistyrose">#ffe4e1</color> <!-- 浅玫瑰色 -->
<color name="bisque">#ffe4c4</color> <!-- 桔黄色 -->
<color name="moccasin">#ffe4b5</color> <!-- 鹿皮色 -->
<color name="navajowhite">#ffdead</color> <!-- 纳瓦白 -->
<color name="peachpuff">#ffdab9</color> <!-- 桃色 -->
<color name="gold">#ffd700</color> <!-- 金色 -->
<color name="pink">#ffc0cb</color> <!-- 粉红色 -->
<color name="lightpink">#ffb6c1</color> <!-- 亮粉红色 -->
<color name="orange">#ffa500</color> <!-- 橙色 -->
<color name="lightsalmon">#ffa07a</color> <!-- 亮肉色 -->
<color name="darkorange">#ff8c00</color> <!-- 暗桔黄色 -->
<color name="coral">#ff7f50</color> <!-- 珊瑚色 -->
<color name="hotpink">#ff69b4</color> <!-- 热粉红色 -->
<color name="tomato">#ff6347</color> <!-- 西红柿色 -->
<color name="orangered">#ff4500</color> <!-- 红橙色 -->
<color name="deeppink">#ff1493</color> <!-- 深粉红色 -->
<color name="fuchsia">#ff00ff</color> <!-- 紫红色 -->
<color name="magenta">#ff00ff</color> <!-- 红紫色 -->
<color name="red">#ff0000</color> <!-- 红色 -->
<color name="oldlace">#fdf5e6</color> <!-- 老花色 -->
<color name="lightgoldenrodyellow">#fafad2</color> <!-- 亮金黄色 -->
<color name="linen">#faf0e6</color> <!-- 亚麻色 -->
<color name="antiquewhite">#faebd7</color> <!-- 古董白 -->
<color name="salmon">#fa8072</color> <!-- 鲜肉色 -->
<color name="ghostwhite">#f8f8ff</color> <!-- 幽灵白 -->
<color name="mintcream">#f5fffa</color> <!-- 薄荷色 -->
<color name="whitesmoke">#f5f5f5</color> <!-- 烟白色 -->
<color name="beige">#f5f5dc</color> <!-- 米色 -->
<color name="wheat">#f5deb3</color> <!-- 浅黄色 -->
<color name="sandybrown">#f4a460</color> <!-- 沙褐色 -->
<color name="azure">#f0ffff</color> <!-- 天蓝色 -->
<color name="honeydew">#f0fff0</color> <!-- 蜜色 -->
<color name="aliceblue">#f0f8ff</color> <!-- 艾利斯兰 -->
<color name="khaki">#f0e68c</color> <!-- 黄褐色 -->
<color name="lightcoral">#f08080</color> <!-- 亮珊瑚色 -->
<color name="palegoldenrod">#eee8aa</color> <!-- 苍麒麟色 -->
<color name="violet">#ee82ee</color> <!-- 紫罗兰色 -->
<color name="darksalmon">#e9967a</color> <!-- 暗肉色 -->
<color name="lavender">#e6e6fa</color> <!-- 淡紫色 -->
<color name="lightcyan">#e0ffff</color> <!-- 亮青色 -->
<color name="burlywood">#deb887</color> <!-- 实木色 -->
<color name="plum">#dda0dd</color> <!-- 洋李色 -->
<color name="gainsboro">#dcdcdc</color> <!-- 淡灰色 -->
<color name="crimson">#dc143c</color> <!-- 暗深红色 -->
<color name="palevioletred">#db7093</color> <!-- 苍紫罗兰色 -->
<color name="goldenrod">#daa520</color> <!-- 金麒麟色 -->
<color name="orchid">#da70d6</color> <!-- 淡紫色 -->
<color name="thistle">#d8bfd8</color> <!-- 蓟色 -->
<color name="lightgray">#d3d3d3</color> <!-- 亮灰色 -->
<color name="lightgrey">#d3d3d3</color> <!-- 亮灰色 -->
<color name="tan">#d2b48c</color> <!-- 茶色 -->
<color name="chocolate">#d2691e</color> <!-- 巧可力色 -->
<color name="peru">#cd853f</color> <!-- 秘鲁色 -->
<color name="indianred">#cd5c5c</color> <!-- 印第安红 -->
<color name="mediumvioletred">#c71585</color> <!-- 中紫罗兰色 -->
<color name="silver">#c0c0c0</color> <!-- 银色 -->
<color name="darkkhaki">#bdb76b</color> <!-- 暗黄褐色 -->
<color name="rosybrown">#bc8f8f</color> <!-- 褐玫瑰红 -->
<color name="mediumorchid">#ba55d3</color> <!-- 中粉紫色 -->
<color name="darkgoldenrod">#b8860b</color> <!-- 暗金黄色 -->
<color name="firebrick">#b22222</color> <!-- 火砖色 -->
<color name="powderblue">#b0e0e6</color> <!-- 粉蓝色 -->
<color name="lightsteelblue">#b0c4de</color> <!-- 亮钢兰色 -->
<color name="paleturquoise">#afeeee</color> <!-- 苍宝石绿 -->
<color name="greenyellow">#adff2f</color> <!-- 黄绿色 -->
<color name="lightblue">#add8e6</color> <!-- 亮蓝色 -->
<color name="darkgray">#a9a9a9</color> <!-- 暗灰色 -->
<color name="darkgrey">#a9a9a9</color> <!-- 暗灰色 -->
<color name="brown">#a52a2a</color> <!-- 褐色 -->
<color name="sienna">#a0522d</color> <!-- 赭色 -->
<color name="darkorchid">#9932cc</color> <!-- 暗紫色 -->
<color name="palegreen">#98fb98</color> <!-- 苍绿色 -->
<color name="darkviolet">#9400d3</color> <!-- 暗紫罗兰色 -->
<color name="mediumpurple">#9370db</color> <!-- 中紫色 -->
<color name="lightgreen">#90ee90</color> <!-- 亮绿色 -->
<color name="darkseagreen">#8fbc8f</color> <!-- 暗海兰色 -->
<color name="saddlebrown">#8b4513</color> <!-- 重褐色 -->
<color name="darkmagenta">#8b008b</color> <!-- 暗洋红 -->
<color name="darkred">#8b0000</color> <!-- 暗红色 -->
<color name="blueviolet">#8a2be2</color> <!-- 紫罗兰蓝色 -->
<color name="lightskyblue">#87cefa</color> <!-- 亮天蓝色 -->
<color name="skyblue">#87ceeb</color> <!-- 天蓝色 -->
<color name="gray">#808080</color> <!-- 灰色 -->
<color name="grey">#808080</color> <!-- 灰色 -->
<color name="olive">#808000</color> <!-- 橄榄色 -->
<color name="purple">#800080</color> <!-- 紫色 -->
<color name="maroon">#800000</color> <!-- 粟色 -->
<color name="aquamarine">#7fffd4</color> <!-- 碧绿色 -->
<color name="chartreuse">#7fff00</color> <!-- 黄绿色 -->
<color name="lawngreen">#7cfc00</color> <!-- 草绿色 -->
<color name="mediumslateblue">#7b68ee</color> <!-- 中暗蓝色 -->
<color name="lightslategray">#778899</color> <!-- 亮蓝灰 -->
<color name="lightslategrey">#778899</color> <!-- 亮蓝灰 -->
<color name="slategray">#708090</color> <!-- 灰石色 -->
<color name="slategrey">#708090</color> <!-- 灰石色 -->
<color name="olivedrab">#6b8e23</color> <!-- 深绿褐色 -->
<color name="slateblue">#6a5acd</color> <!-- 石蓝色 -->
<color name="dimgray">#696969</color> <!-- 暗灰色 -->
<color name="dimgrey">#696969</color> <!-- 暗灰色 -->
<color name="mediumaquamarine">#66cdaa</color> <!-- 中绿色 -->
<color name="cornflowerblue">#6495ed</color> <!-- 菊兰色 -->
<color name="cadetblue">#5f9ea0</color> <!-- 军兰色 -->
<color name="darkolivegreen">#556b2f</color> <!-- 暗橄榄绿 -->
<color name="indigo">#4b0082</color> <!-- 靛青色 -->
<color name="mediumturquoise">#48d1cc</color> <!-- 中绿宝石 -->
<color name="darkslateblue">#483d8b</color> <!-- 暗灰蓝色 -->
<color name="steelblue">#4682b4</color> <!-- 钢兰色 -->
<color name="royalblue">#4169e1</color> <!-- 皇家蓝 -->
<color name="turquoise">#40e0d0</color> <!-- 青绿色 -->
<color name="mediumseagreen">#3cb371</color> <!-- 中海蓝 -->
<color name="limegreen">#32cd32</color> <!-- 橙绿色 -->
<color name="darkslategray">#2f4f4f</color> <!-- 暗瓦灰色 -->
<color name="darkslategrey">#2f4f4f</color> <!-- 暗瓦灰色 -->
<color name="seagreen">#2e8b57</color> <!-- 海绿色 -->
<color name="forestgreen">#228b22</color> <!-- 森林绿 -->
<color name="lightseagreen">#20b2aa</color> <!-- 亮海蓝色 -->
<color name="dodgerblue">#1e90ff</color> <!-- 闪兰色 -->
<color name="midnightblue">#191970</color> <!-- 中灰兰色 -->
<color name="aqua">#00ffff</color> <!-- 浅绿色 -->
<color name="cyan">#00ffff</color> <!-- 青色 -->
<color name="springgreen">#00ff7f</color> <!-- 春绿色 -->
<color name="lime">#00ff00</color> <!-- 酸橙色 -->
<color name="mediumspringgreen">#00fa9a</color> <!-- 中春绿色 -->
<color name="darkturquoise">#00ced1</color> <!-- 暗宝石绿 -->
<color name="deepskyblue">#00bfff</color> <!-- 深天蓝色 -->
<color name="darkcyan">#008b8b</color> <!-- 暗青色 -->
<color name="teal">#008080</color> <!-- 水鸭色 -->
<color name="green">#008000</color> <!-- 绿色 -->
<color name="darkgreen">#006400</color> <!-- 暗绿色 -->
<color name="blue">#0000ff</color> <!-- 蓝色 -->
<color name="mediumblue">#0000cd</color> <!-- 中兰色 -->
<color name="darkblue">#00008b</color> <!-- 暗蓝色 -->
<color name="navy">#000080</color> <!-- 海军色 -->
<color name="black">#000000</color> <!-- 黑色 -->
</resources>

Android 坐标体系

/home/jimmy/Blog/tbfungeek.github.io/source/_posts/Android-进阶之Android-绘图-一-Android坐标体系及屏幕尺寸相关概念

Android 坐标体系如上图所示,竖直向下为Y轴的正方向,水平向右边为X轴的正方向。手机屏幕的左上角为坐标原点

这里我们可以通过如下方法获得关键的坐标点坐标:

  • 第一类为当前坐标所处的屏幕位置,这些坐标是相对于父View的相对位置:
    getLeft() 当前View 左边缘距离父视图最左边的距离
    getRight() 当前View 右边缘距离父视图最左边的距离
    getTop() 当前View 上边缘距离父视图最上边的距离
    getBottom() 当前View 下边缘距离父视图最上边的距离

  • 第二类为当前触点的位置:
    getX() 当前触点距离所处View的左边边距的位置
    getY() 当前触点距离所处View的上边边距的位置
    getRawX() 当前触点距离屏幕左边边距的位置
    getRawY() 当前触点距离屏幕上边边距的位置

屏幕尺寸相关概念

屏幕大小:屏幕对角线的长度,单位为英寸
分辨率:手机屏幕的像素点个数
像素密度:每英寸像素个数,它是对角线的像素除以屏幕大小
目前收集根据像素密度可以分成如下几类,其中Android系统中以160dpi mdpi作为参考,也就是说以mdpi的1像素,作为1dpi
各个dpi之间的换算比为3:4:6:8:12

| ldpi | mdpi |hdpi | xhdpi | xxhdpi |
|——–|——–|——–|——–|——–|——–|——–|——–|
| 120dip | 160dpi | 240dpi | 320dip | 480dip |

语音朗读

语音朗读:Text-to-Speech

  • 首先确认是否安装了语音包
Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(intent, TTS_DATA_CHECK);

如果安装了语言包那么返回的将是Engine.CHECK_VOICE_DATA_PASS

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TTS_DATA_CHECK) {
if (resultCode == Engine.CHECK_VOICE_DATA_PASS) {
//表示已经安装了语言包
}
super.onActivityResult(requestCode, resultCode, data);
}
}
  • 创建并初始化TTS
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TTS_DATA_CHECK) {
if (resultCode == Engine.CHECK_VOICE_DATA_PASS) {
tts = new TextToSpeech(MainActivity.this, new OnInitListener() {
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
ttsInit = true;
if (tts.isLanguageAvailable(Locale.UK) > 0) {
tts.setLanguage(Locale.UK);
tts.setPitch(0.8f);
tts.setSpeechRate(1.1f);
}
} else {
Intent intent = new Intent(
Engine.ACTION_INSTALL_TTS_DATA);
startActivity(intent);
}
}
});
}
}
super.onActivityResult(requestCode, resultCode, data);
}
  • 使用TTS朗读
public void onClick(View v) {
String str = mTextToSpeechEt.getText().toString().trim();
switch (v.getId()) {
case R.id.readBtn:
if (!TextUtils.isEmpty(str)) {
if (tts != null && ttsInit) {
tts.speak(str, TextToSpeech.QUEUE_ADD, null);
}
}
break;
default:
break;
}
}
  • 在销毁的时候关闭TTS资源
protected void onDestroy() {
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onDestroy();
}

语音识别

应用程序中接收输入语音
/*允许在应用程序中接收输入语音*/
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
/*指定用于分析输入音频的语言模型*/
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
/*指定返回潜在识别的结果*/
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINA);
/*提示用户讲话*/
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "请讲话");
startActivityForResult(intent, RECOGNIZE);

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode==RECOGNIZE){
if(resultCode==RESULT_OK){
ArrayList<String> result = data.getStringArrayListExtra(
RecognizerIntent.EXTRA_RESULTS);
float [] configence =
data.getFloatArrayExtra(
RecognizerIntent.EXTRA_CONFIDENCE_SCORES);
StringBuffer sb = new StringBuffer();
for(int i=0;i<result.size();i++){
sb.append(result.get(i)+": "+configence[i]+'\n');
}
mResultTV.setText(sb.toString());
}}
super.onActivityResult(requestCode, resultCode, data);
}
启动网络搜索
Intent webIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
webIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
startActivityForResult(webIntent, 0);

剪贴板

Android 3.0 引入了使用ClipBoard Manager在应用程序内部之间进行完全复制和粘贴操作,剪贴板的获得如下:

ClipboardManager clipBoard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);

剪贴板支持文本字符串,URI通常指向一个Content Provider项,Intent用于复制应用程序的快捷方式。

要想把数据添加到剪贴板上可以使用setPrimaryClip(new Clip);

复制

ClipData类提供了大量的方法用来创建一个ClipData:
使用newPaintText方法创建一个新的ClipData对象,该对象包含一个字符用于对当前的数据进行描述,并且把数据类型设置为MIMETYPE_TEXT_PLAIN.

ClipData newClip = ClipData.newPlainText("newClips", mSetClipEt.getText().toString());

对于基于Content Provider的项,可以使用newUri方法,指定一个Content Resolver,标签和待粘贴数据的URI

ClipData.newUri(getContentResolver(), "label", uri);

粘贴

粘贴自定义对象

读取自定义对象,并将其转换为字符串,写入剪切板:

MyData md = new MyData("kimifdw",26);  
//定义字符串
String baseToString ="";
//1.将对象转换成字符串
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try
{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(md);
baseToString = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT);
objectOutputStream.close();
}
catch(Exception e)
{
e.printStackTrace();
}

读取剪切板的字符串,并将其转换为对象

byte[] base64ToString = Base64.decode(item.getText().toString(), Base64.DEFAULT);  
//从流中读取数据
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(base64ToString);
try
{
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
MyData md = (MyData)objectInputStream.readObject();
objectInputStream.close();
txtView.setText(md.toString());
}
catch(Exception e)
{
e.printStackTrace();
}

一个简单的例子

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mShowClipTv = (TextView) findViewById(R.id.showClip);
mSetClipEt = (EditText) findViewById(R.id.setClip);
mClipBtn = (Button) findViewById(R.id.clipBtn);
mPasteBtn = (Button) findViewById(R.id.pasteBtn);
clipBoard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
mClipBtn.setOnClickListener(this);
mPasteBtn.setOnClickListener(this);
}

public void onClick(View v) {
switch (v.getId()) {
case R.id.clipBtn:
ClipData newClip = ClipData.newPlainText("newClips",mSetClipEt.getText().toString());
clipBoard.setPrimaryClip(newClip);
Toast.makeText(MainActivity.this, "复制成功", 0).show();
break;
case R.id.pasteBtn:
if(clipBoard.getPrimaryClipDescription()
.hasMimeType(ClipDescription
.MIMETYPE_TEXT_PLAIN)) {
Item item = clipBoard.getPrimaryClip().getItemAt(0);
mShowClipTv.setText(item.getText());
Toast.makeText(MainActivity.this, "粘贴成功", 0).show();
}else{
Toast.makeText(MainActivity.this, "文本格式", 0).show();
}
break;
default:break;
}
}

概述

Android 是一个多任务的系统,所以安全成了一个系统中一大重要的课题,为了保证应用的数据安全采用了应用签名,沙箱机制,权限机制等三种机制,在Android系统中用户在安装了一个应用程序后,操作系统为该应用程序创建一个与之关联的新的用户配置文件,每个应用程序都作为不同的用户运行,它在文件系统中拥有自己的文件,用户ID和一个安全的操作环境。应用程序在操作系统上使用自己的用户ID,自己的Dalvik虚拟机实例运行在自己的进程内。要访问系统上的共享资源,Android应用程序需要注册所需的权限,同时可以将自己的权限声明为可供其他程序使用,也可以为了更加精细得控制应程序而声明任意数量的不同权限,如只读,读写权限。作为内容提供的应用程序也可能需要为其他应用程序提供即时的权限,以共享特定的信息,这可以通过使用URI来进行临时的授权与撤销。应用程序通过签名建立用户信任,所有的Android应用程序包都使用证书进行了签名,这样用户可以了解应用程序的可靠性。证书的私钥为开发人员所有,这有助于在开发人员和用户之间建立一种信任关系。要在Android市场上发布应用程序,开发人员需要创建一个账号,Android市场管理非常严密,不允许出现任何的恶意软件。Android平台上没有本地应用和开发人员创建的第三方应用程序的区别,在为应用程序提供适当授权后,所有的应用程序都具有对核心库和底层硬件接口的访问权。

沙箱机制

应用的签名已经在之前的博客中已经做了介绍,这里就不再详细展开。

下面重点介绍沙箱机制和权限机制:

上面介绍过Linux内核给每个用户分配一个UID,用户可同时拥有多个运行进程,每个进程都运行于各自独立的内存空间,进程与进程之前无法进行数据访问,Android为每个应用程序分配一个UID,通过这种方式将每一个应用程序置于“沙箱”之内,实现应用程序之间的隔离,通过权限限制API调用及数据访问

不同数据之间数据是不能相互访问是,而同一个UID的应用之间可以共享数据,如下图所示:


要想两个应用通过UID共享数据需要在在Manifest节点中增加相同的android:sharedUserId属性,并确保共享数据的两个应用拥有相同的签名

Android系统权限机制

系统已定义的权限,我们可以通过执行

$ adb shell pm list permissions查看

我们可以通过在应用的Manifest使用这些权限来达到访问被保护的API或资源的目的,但并不是使用了就一定能够访问的,需要看指定权限的保护级别。

定义权限:

在应用中自定义权限,我们可以在应用的Manifest文件中进行权限声明,基本格式如下:

<permission       
android:name="com.tct.permission.START_JIMMY_ACTIVITY"
android:protectionLevel="normal"
android:label="@string/lable_start_Act"
android:description="@string/permdesc_startAct">
</permission>

定义权限的四个关键属性如下所示:



权限的使用方式,通过在Manifest文件中声明标签来使用已定义的权限,从而达到访问受保护数据的目的:

<uses-permission android:name="com.idealist.permission.START_JIMMY_ACTIVITY"/>
<uses-permission android:name="com.idealist.permission.WRITE_CONTENTPROVIDER"/>
<uses-permission android:name="com.idealist.permission.READ_CONTENTPROVIDER"/>

下面是几个比较典型的例子:

从一个应用的Activity启动另一个应用中的Activity
ComponentName componentName = new ComponentName(
"com.idealist.test.activity",/*包路径*/
"com.idealist.test.activity.DemoActivity"/*Activity类*/
);

Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("resUrl", resurl);
bundle.putSerializable("picUrlList", picurllist);
intent.putExtras(bundle);
intent.setComponent(componentName);
startActivity(intent);
从一个应用中控制另一个应用中的服务

首先需要在声明服务的时候将enable和exported设置为true

<service Android:name=".xxxService" android:enabled="true" android:exported="true">

然后和上一个例子一样使用完整的包名来设置component

Intent  testIntent = newIntent();
testIntent.setComponent(new ComponentName("包名","包名.xxxService"));
booleanisbind=bindService(testIntent,serviceConnection,Context.BIND_AUTO_CREATE);
发送带权限的广播以及给广播接收器添加权限
  • 权限定义,并且使用自定义权限
<uses-permission android:name="com.idealist.permissions.MY_BROADCAST" />
<permission
android:name="com.idealist.permissions.MY_BROADCAST"
android:protectionLevel="signature" >
</permission>
  • 自定义并注册广播
private static final String BROADCAST_PERMISSION_DISC = "com.idealist.permissions.MY_BROADCAST";
private static final String BROADCAST_ACTION_DISC = "com.idealist.permissions.my_broadcast";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_permissions_activity);
//......................................
// 注册广播接收
BroadcastReceiver receiveBroadCast = new ReceiveBroadCast();
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION_DISC);
registerReceiver(receiveBroadCast, filter,BROADCAST_PERMISSION_DISC,null);
}

public class ReceiveBroadCast extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(BroadcastPermissionsActivity.this,
"Hello!", 0).show();
}

}

注册一个广播,并且申明,这个广播需要BROADCAST_PERMISSION_DISC权限才能收到消息。但是我们应用程序已经注册了这个权限。所以是有这个权限的。

  • 发送广播
    发送广播的函数有好几个,其中一个是sendBroadcast(Intent intent, String receiverPermission)。使用这个函数发送广播之后,接收器需要先注册权限才可以接收的。
    public void sendBroadcastWithPermissions() {
    Intent intent = new Intent();
    intent.setAction(BROADCAST_ACTION_DISC);
    sendBroadcast(intent,BROADCAST_PERMISSION_DISC);
    }
给Content Provider设置权限
<provider
android:name="com.example.activityplancontentprovider.contentprovider.CustomProvider"
android:authorities="com.example.provider.activityplancontentprovider"
android:exported="true"
android:readPermission="com.example.provider.activityplancontentprovider.permission.READPROVIDER"
android:writePermission="com.example.provider.activityplancontentprovider.permission.WRITEPROVIDER" >
<path-permission
android:pathPattern="/activity" android:readPermission="com.example.provider.activityplancontentprovider.permission.READACTIVITY_PROVIDER" />
<path-permission
android:pathPattern="/attend_person" android:readPermission="com.example.provider.activityplancontentprovider.permission.READATTEND_PEOPLE_PROVIDER" />
</provider>

上面的例子中为整个数据库的写入设置了
android:writePermission=”com.example.provider.activityplancontentprovider.permission.WRITEPROVIDER”权限,
为整个数据库的读设置了
android:readPermission=”com.example.provider.activityplancontentprovider.permission.READPROVIDER”权限
为activity表格设置了
android:readPermission=”com.example.provider.activityplancontentprovider.permission.READACTIVITY_PROVIDER”权限
为attend_person表格设置了
android:readPermission=”com.example.provider.activityplancontentprovider.permission.READATTEND_PEOPLE_PROVIDER”权限

将权限临时赋予另一个没有权限的Activity
<provider
android:name="com.example.activityplancontentprovider.contentprovider.CustomProvider"
android:authorities="com.example.provider.activityplancontentprovider"
android:exported="true"
android:readPermission="com.example.provider.activityplancontentprovider.permission.READPROVIDER"
android:writePermission="com.example.provider.activityplancontentprovider.permission.WRITEPROVIDER" >
<path-permission
android:pathPattern="/activity" android:readPermission="com.example.provider.activityplancontentprovider.permission.READACTIVITY_PROVIDER" />
<path-permission
android:pathPattern="/attend_person" android:readPermission="com.example.provider.activityplancontentprovider.permission.READATTEND_PEOPLE_PROVIDER" />
<grant-uri-permission android:pathPrefix="/activity" />
</provider>

假设ActivityWithNoPermission中没有访问活动表格的权限,但是在ActivityPlans启动ActivityWithNoPermission的时候使用
startActivityIntent.setData(Uri.parse(“content://com.example.provider.activityplancontentprovider/activity”));
startActivityIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
自己拥有的访问活动表格的权限通过Intent赋予ActivityWithNoPermission,这样在ActivityWithNoPermission就可以使用临时的访问权限,以及传递过来的Uri访问活动表格。

Android studio 版本: Android Studio 2.0 Beta 6.0
Gradle 版本:gradle-2.10
NDK 版本:android-ndk-r10e

  1. 创建一个空项目
    ![](Android studio 下使用JNI/project.png)
  2. 打开Setting 在 Setting中设置NDK的路径:
    ![](Android studio 下使用JNI/setNDK.png)
  3. 添加Native代码,以及LoadLibrary,由于这里还没生成这些所以还是显示错误的,但是不要管它。
    ![](Android studio 下使用JNI/native.png)
  4. 点击Build-> make project 编译项目:
    ![](Android studio 下使用JNI/makepro.png)
    这时候会在,[项目名]/app/build/intermediates/classes/debug/com/idealist/testjniusage 下产生编译出来的class文件
    进入到[项目名]/app/src/main 目录下,运行如下javah命令:
    ![](Android studio 下使用JNI/path.png)
    javah -d jni -classpath /home/jimmy/Dev/sdk_m/sdk/platforms/android-23/android.jar:/home/jimmy/Dev/sdk_m/sdk/extras/android/support/v7/appcompat/libs/android-support-v4.jar:/home/jimmy/Dev/sdk_m/sdk/extras/android/support/v7/appcompat/libs/android-support-v7-appcompat.jar:/home/jimmy/GitHub_Opensrc/TestJNIUsage/app/build/intermediates/classes/debug/ com.idealist.testjniusage.MainActivity
    这时候会在项目目录树下产生一个jni的文件夹,如下图所示:
    ![](Android studio 下使用JNI/protree.png)

打开生成的头文件可以看到如下内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_idealist_testjniusage_MainActivity */

#ifndef _Included_com_idealist_testjniusage_MainActivity
#define _Included_com_idealist_testjniusage_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_idealist_testjniusage_MainActivity_BIND_ABOVE_CLIENT
#define com_idealist_testjniusage_MainActivity_BIND_ABOVE_CLIENT 8L
#undef com_idealist_testjniusage_MainActivity_BIND_ADJUST_WITH_ACTIVITY
#define com_idealist_testjniusage_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L
#undef com_idealist_testjniusage_MainActivity_BIND_ALLOW_OOM_MANAGEMENT
#define com_idealist_testjniusage_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef com_idealist_testjniusage_MainActivity_BIND_AUTO_CREATE
#define com_idealist_testjniusage_MainActivity_BIND_AUTO_CREATE 1L
#undef com_idealist_testjniusage_MainActivity_BIND_DEBUG_UNBIND
#define com_idealist_testjniusage_MainActivity_BIND_DEBUG_UNBIND 2L
#undef com_idealist_testjniusage_MainActivity_BIND_IMPORTANT
#define com_idealist_testjniusage_MainActivity_BIND_IMPORTANT 64L
#undef com_idealist_testjniusage_MainActivity_BIND_NOT_FOREGROUND
#define com_idealist_testjniusage_MainActivity_BIND_NOT_FOREGROUND 4L
#undef com_idealist_testjniusage_MainActivity_BIND_WAIVE_PRIORITY
#define com_idealist_testjniusage_MainActivity_BIND_WAIVE_PRIORITY 32L
#undef com_idealist_testjniusage_MainActivity_CONTEXT_IGNORE_SECURITY
#define com_idealist_testjniusage_MainActivity_CONTEXT_IGNORE_SECURITY 2L
#undef com_idealist_testjniusage_MainActivity_CONTEXT_INCLUDE_CODE
#define com_idealist_testjniusage_MainActivity_CONTEXT_INCLUDE_CODE 1L
#undef com_idealist_testjniusage_MainActivity_CONTEXT_RESTRICTED
#define com_idealist_testjniusage_MainActivity_CONTEXT_RESTRICTED 4L
#undef com_idealist_testjniusage_MainActivity_MODE_APPEND
#define com_idealist_testjniusage_MainActivity_MODE_APPEND 32768L
#undef com_idealist_testjniusage_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING
#define com_idealist_testjniusage_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING 8L
#undef com_idealist_testjniusage_MainActivity_MODE_MULTI_PROCESS
#define com_idealist_testjniusage_MainActivity_MODE_MULTI_PROCESS 4L
#undef com_idealist_testjniusage_MainActivity_MODE_PRIVATE
#define com_idealist_testjniusage_MainActivity_MODE_PRIVATE 0L
#undef com_idealist_testjniusage_MainActivity_MODE_WORLD_READABLE
#define com_idealist_testjniusage_MainActivity_MODE_WORLD_READABLE 1L
#undef com_idealist_testjniusage_MainActivity_MODE_WORLD_WRITEABLE
#define com_idealist_testjniusage_MainActivity_MODE_WORLD_WRITEABLE 2L
#undef com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_DIALER
#define com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_DIALER 1L
#undef com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_DISABLE
#define com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_DISABLE 0L
#undef com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL
#define com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L
#undef com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL
#define com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SHORTCUT
#define com_idealist_testjniusage_MainActivity_DEFAULT_KEYS_SHORTCUT 2L
#undef com_idealist_testjniusage_MainActivity_RESULT_CANCELED
#define com_idealist_testjniusage_MainActivity_RESULT_CANCELED 0L
#undef com_idealist_testjniusage_MainActivity_RESULT_FIRST_USER
#define com_idealist_testjniusage_MainActivity_RESULT_FIRST_USER 1L
#undef com_idealist_testjniusage_MainActivity_RESULT_OK
#define com_idealist_testjniusage_MainActivity_RESULT_OK -1L
#undef com_idealist_testjniusage_MainActivity_HONEYCOMB
#define com_idealist_testjniusage_MainActivity_HONEYCOMB 11L
#undef com_idealist_testjniusage_MainActivity_MSG_REALLY_STOPPED
#define com_idealist_testjniusage_MainActivity_MSG_REALLY_STOPPED 1L
#undef com_idealist_testjniusage_MainActivity_MSG_RESUME_PENDING
#define com_idealist_testjniusage_MainActivity_MSG_RESUME_PENDING 2L
/*
* Class: com_idealist_testjniusage_MainActivity
* Method: getNativeString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_idealist_testjniusage_MainActivity_getNativeString
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. 创建对应的源码文件如下: 很简单就是返回一个Hello From JNI!字符串。
#include <jni.h>
#include <android/log.h>
#ifndef LOG_TAG
#define LOG_TAG "ANDROID_LAB"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif
#ifndef _Included_com_idealist_testjniusage_MainActivity
#define _Included_com_idealist_testjniusage_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: lab_sodino_jnitest_MainActivity
* Method: getStringFromNative
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_idealist_testjniusage_MainActivity_getNativeString
(JNIEnv * env, jobject jObj){
return (*env)->NewStringUTF(env,"Hello From JNI!");
}

#ifdef __cplusplus
}
#endif
#endif

点击运行按钮,这时候很有可能会出现如下的错误:
![](Android studio 下使用JNI/ndkbug.png)
这个问题很简单其实上面已经给出解决方案了,就是在gradle.property这个文件下添加如下配置:
![](Android studio 下使用JNI/fixed.png)

6.修改Module 下的 gradle.build

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"

defaultConfig {
applicationId "com.idealist.testjni"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
//这里是新增加的
ndk {
moduleName "Testjni" //这个是LoadLibary 参数相同的名字
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
//ldLibs "log", "z", "m" //链接时使用到的库,对应LOCAL_LDLIBS
//cFlags 编译gcc的flag,对应LOCAL_CFLAGS
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//这里是新增加的,用于支持JNIdebug的
ndk {
debuggable = true
}
}
debug {
//这里是新增加的,用于支持JNIdebug的
debuggable = true
jniDebuggable = true
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:design:23.1.0'
}

  1. 点击运行。
    在手机上就可以看到如下的界面了:

    ![](Android studio 下使用JNI/result.png)

可能长期被eclipse虐惯的人会问了,不用写mk吗?是不用写了,其实在bradle.build上面已经做了类似的工作,这个mk文件会根据你的配置在output 目录下生成,包括各个平台的库文件都会在这个目录下生成,如下图所示:
![](Android studio 下使用JNI/output.png)

下面是生成的mk文件内容:
![](Android studio 下使用JNI/mk.png)

Demo可以在如下地址进行下载:
Demo Github

在Android中每个应用程序都运行在自己的进程空间,不同的进程不能直接访问对方的进程空间,如果要在进程间通信,必须将传递对象解析成操作系统可以理解的基本类型,通过操作系统作为桥梁来传递到对方的进程。
AIDL(Android Interface Definition Language) Android 接口定义语言用于生成两个进程之间进行进程间通信的代码。

AIDL服务端实现

定义AIDL接口
  • AIDL接口文件的后缀名为.aidl.保存在src目录下
  • AIDL接口文件与一般的接口大体相似,都是以interface作为关键字,每个aidl文件只能定义一个接口,与一般接口不同的是AIDL文件只能定义接口声明和方法声明,不能定义静态常量。
  • AIDL文件中方法声明的参数和返回值可以是基本数据类型,String,CharSequence,List,Map,实现了Parcelable接口的类,甚至是其他AIDL生成的接口。其中后两者即使位于同一个包也需要使用import包含进来。实现了Parcelable接口的类除了要建立一个实现android.os.Parcelable接口的类外,还需要为这个类单独建立一个aidl文件,并使用parcelable关键字进行声明。
  • 除了基本类型,String,CharSequence这些类型,其他的都需要指明方向,in表示由客户端设置,out表示由服务端设置,inout表示两者均可设置。
  • 如果其他应用程序需要进行IPC,则这些应用程序的src也需要有这个aidl文件
  1. 首先,先创建一个继承Service的服务类 MyAIDLService

  1. 紧接着选中刚刚创建的MyAIDLService.java文件在右键弹出的菜单中选择File->New->AIDL->AIDL File
    这时候就会在main目录下自动创建一个aidl目录,并且在MyAIDLService.java 平行的对应包下创建一个aidl文件。
package com.idealist.testaidl.service;

import com.idealist.testaidl.bean.Person;
import com.idealist.testaidl.callback.IResultCallback;
interface IMyAidlInterface {
void savePersonInfo(in Person person,IResultCallback callback);//保存用户信息
List<Person> getPersonInfos();//获取所有用户信息的列表
}

这里用到了Person这个Bean以及IResultCallback这个回调接口,前者是实现了Parcelable接口的类,后者是其他AIDL生成的接口。具体如下:
com/idealist/testaidl/callback/IResultCallback.aidl

package com.idealist.testaidl.callback;

interface IResultCallback {
void reportResult(String result);
}

com/idealist/testaidl/bean/Person.aidl

package com.idealist.testaidl.bean;
parcelable Person;

下面是Person的实现类:
com.idealist.testaidl.bean.Person

package com.idealist.testaidl.bean;
import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {

public Person() {}
protected Person(Parcel in) {
name = in.readString();
tel = in.readString();
}

public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}

@Override
public Person[] newArray(int size) {
return new Person[size];
}
};

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getTel() {
return tel;
}

public void setTel(String tel) {
this.tel = tel;
}

private String name = null;
private String tel = null;


@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeString(tel);
}
}

点击build后就可以编译,编译结束后就可以产生如下的Stub文件了:

  1. 实现MyAIDLService类:
    这里实现的功能很简单就是在客户端绑定并调用Serive的savePersonInfo方法的时候,将会存储起来,如果客户端调用getPersonInfos将会返回存储信息的列表。
package com.idealist.testaidl.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import com.idealist.testaidl.bean.Person;
import com.idealist.testaidl.callback.IResultCallback;

import java.util.ArrayList;
import java.util.List;

public class MyAIDLService extends Service {
private List<Person> mPersonInfoList = new ArrayList<Person>();

private IMyAidlInterface.Stub mIBinder = new IMyAidlInterface.Stub() {
@Override
public void savePersonInfo(Person person, IResultCallback callback) throws RemoteException {
if(mPersonInfoList != null && person != null) {
mPersonInfoList.add(person);
Log.i("xiaohai.lin", "Name = " + person.getName() + " -- " +
"Tel " + person.getTel() + " Has add to list");
}
if(callback != null) {
callback.reportResult("Person info had add Sucessfully !");
}
}

@Override
public List<Person> getPersonInfos() throws RemoteException {
return mPersonInfoList;
}
};

@Nullable
@Override
public IBinder onBind(Intent intent) {
return mIBinder;
}

public boolean onUnbind(Intent intent) {
Log.i("xiaohai.lin", "客户端解除绑定");
return super.onUnbind(intent);
}
}
  1. 最后一定要注意在AndroidManifest.xml文件中作如下声明:
<service
android:name=".service.MyAIDLService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MyAIDLService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>

整个服务端源码结构如下:

AIDL客户端实现

创建一个新的Android项目工程,将服务器端的AIDL相关的文件连同包复制到客户端项目,项目结构如下图所示:

package com.ideallist.testaidlclient;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.idealist.testaidl.bean.Person;
import com.idealist.testaidl.callback.IResultCallback;
import com.idealist.testaidl.service.IMyAidlInterface;

import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private IMyAidlInterface mInterface = null;
private Button mBindServiceBtn = null;
private Button mUnbindServceBtn = null;
private Button mSetPersonInfoBtn = null;
private Button mGetPersonInfoBtn = null;
private boolean isBind = false;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mInterface = IMyAidlInterface.Stub.asInterface(iBinder);
isBind = true;
Toast.makeText(MainActivity.this, "Binded to Server !",Toast.LENGTH_LONG).show();
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
if(mInterface != null) {
mInterface = null;
}
Toast.makeText(MainActivity.this, "Ubind to Server !",Toast.LENGTH_LONG).show();
isBind = false;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindServiceBtn = (Button) findViewById(R.id.bindservice);
mUnbindServceBtn = (Button) findViewById(R.id.unbindservice);
mSetPersonInfoBtn = (Button) findViewById(R.id.setpersonInfo);
mGetPersonInfoBtn = (Button) findViewById(R.id.getpersonInfo);

mBindServiceBtn.setOnClickListener(this);
mUnbindServceBtn.setOnClickListener(this);
mSetPersonInfoBtn.setOnClickListener(this);
mGetPersonInfoBtn.setOnClickListener(this);
}

private IResultCallback.Stub mResultCallBack = new IResultCallback.Stub() {
@Override
public void reportResult(String result) throws RemoteException {
Toast.makeText(MainActivity.this, "Result = " + result,Toast.LENGTH_LONG).show();
}
};

@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.bindservice:
if(!isBind) {
Intent intent = new Intent("android.intent.action.MyAIDLService");
intent.setPackage("com.idealist.testaidl");
bindService(intent, conn, BIND_AUTO_CREATE);
}
break;
case R.id.unbindservice:
if(isBind) {
unbindService(conn);
}
break;
case R.id.setpersonInfo:
if(mInterface != null ) {
Person person = new Person();
person.setName("Jimmy");
person.setTel("1111111111");
try {
mInterface.savePersonInfo(person,mResultCallBack);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case R.id.getpersonInfo:
if(mInterface != null ) {
try {
List<Person> mList = mInterface.getPersonInfos();
for (Person p : mList) {
Toast.makeText(MainActivity.this,"Name = " + p.getName()+" : "+ " Tel "+ p.getTel(),Toast.LENGTH_LONG).show();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;

}
}
}

写客户端最需要注意的就是每个类和Service中的对应关系。一旦对应错误就有可能访问不到对应的方法。

Android的定位技术

在开发Android位置相关的应用的时候,可以从GPS或者网络获取用户位置,通过GPS能够获得最精确的信息,但是仅适于户外,不但耗电,而且不能及时返回用户需要的信息,使用网络能从发射塔和Wifi信号获得用户位置,提供一种适用于户内和户外的获取位置信息的方式。不但相应速度迅速,而且更加省电。

在Android系统中,开发人员需要使用如下的类来访问定位服务。

  • LocationManager:该类提供系统定位服务访问功能

  • LocationListener :当位置发生变化的时候,该接口从LocationManager中获得通知

  • Location:该类表示特定时间地理位置变化信息,位置由经度,纬度,UTC时间戳以及可选的高度,速度,方向等组成

  • 获取可用定位服务

    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv= (TextView) findViewById(R.id.tv);
    LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
    List<String> list = manager.getAllProviders();
    StringBuilder sb = new StringBuilder();
    for(String locationResouse:list){
    sb.append(locationResouse+"\n");
    }
    tv.setText(sb.toString());
    }
  • 查看位置源属性
    对于位置源而言,精度和耗电量是用户十分关心的属性。

    LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
    LocationProvider provider = manager.getProvider(LocationManager.GPS_PROVIDER);
    int accuracy = provider.getAccuracy();
    StringBuilder sb = new StringBuilder();
    sb.append("GPS Info:\n");
    switch (accuracy) {
    case Criteria.ACCURACY_HIGH:
    sb.append("accuracy:High\n");break;
    case Criteria.ACCURACY_LOW:
    sb.append("accuracy:Low\n");break;
    case Criteria.ACCURACY_MEDIUM:
    sb.append("accuracy:Medium\n");break;
    default:break;
    }
    int power = provider.getPowerRequirement();
    switch (power) {
    case Criteria.POWER_LOW:
    sb.append("power:Low\n");break;
    case Criteria.POWER_HIGH:
    sb.append("power:High\n");break;
    case Criteria.POWER_MEDIUM:
    sb.append("power:Medium\n");break;
    default:break;
    }

  • 监听位置变化事件
    对于位置变化的用户,可以在变化后接收到相关的通知,在LocationManager中定义了多个requestLocationUpdates()方法,它用来为当前Activity注册位置变化通知事件。

requestLocationUpdates(provider, minTime, minDistance, listener)

参数说明如下:

provider:注册的provider的名称
minTime:通知间隔的最小事件,单位是毫秒,系统为了省电可以延长该时间
minDistance:更新通知的最小变化距离,单位是米。
listener:用于处理通知的监听器,可以是下表的其中一种:

Android 网络访问技术

使用HttpURLConnection访问网络

HttpURLConnection 位于java.net包中,用于发送Http请求和获取Http响应,由于该类是抽象类,不能直接实例化对象,需要使用URL的openConnection()方法来获取.

  1. 创建HttpURLConnection对象:
  • Get方式:
//创建一个URL对象
uri = new URL("http://php.weather.sina.com.cn/xml.php&city="+ URLEncoder.encode(“惠州”, "gb2312")+ "&password=DJOYnieT8234jlsK&day=0");
// 获得HttpURLConnection 实例
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
  • Post方式:
uri = new URL(“http://php.weather.sina.com.cn/xml.php”);
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
// 设置连接属性
urlConn.setDoInput(true);
urlConn.setDoOutput(true);
urlConn.setRequestMethod("POST");  // 设置POST方式
urlConn.setUseCaches(false); // 不设置缓存
urlConn.setInstanceFollowRedirects(false);
// 配置本次连接的Content-type
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
urlConn.connect(); // 连接
out = new DataOutputStream(urlConn.getOutputStream());
String content =“city="+ URLEncoder.encode(“惠州”, "gb2312")+ "&password=DJOYnieT8234jlsK&day=0");
out.writeBytes(content); // 写入输出流
out.flash();
out.close();
  • 判断是否成功建立连接:
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
}
  • 获得InputStream读取流数据:
InputStreamReader  in = new InputStreamReader(urlConn.getInputStream());
BufferedReader buffer = new BufferedReader(in);
Stream inputLine = null;
While((inputLine=buffer.readLine())!=null){
result += inputLine+”\n”;
}
  • 关闭流,关闭连接:
In.close();
urlConn.disconnect();
  • 设置网络访问权限:
<uses-permission android:name="android.permission.INTERNET"/>

访问网络需要在工作线程中完成否则会产生ANR

使用HttpClient访问网络

一般情况下如果只需完成简单的页面提交请求并获取服务器的响应,可以使用HttpURLConnection来实现,而对于比较复杂的联网操作则需要使用Apache组织提供的一个HttpClient来访问网络。
HttpClient实际上是对Java提供的访问网络的方法的封装,在HttpURLConnection类中的输入输出流操作,在HttpClient类中都被统一封装成HttpGet,HttpPost,HttpResponse这几类,从而减少了操作的繁琐性。

Get方式:

  • 创建HttpGet对象
HttpGet httpget = new HttpGet("http://php.weather.sina.com.cn/xml.php&city=" + URLEncoder.encode(“惠州”, "gb2312")+ "&password=DJOYnieT8234jlsK&day=0");
  • 创建HttpClient对象
HttpClient httpclient =  new DefaultHttpClient();
  • 调用HttpClient对象的excute()方法发送请求,返回一个HttpResponse对象。
HttpResponse  httpresponse= httpclient.execute(httpget);
  • 判断连接状态
If(httpresponse.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
}
  • 调用HttpResponse的getEntity()方法,可以获得包含服务器响应内容的HttpEntity对象,通过该对象可以获取服务器的响应内容。
EntityUtils.toString(httpresponse.getEntity());
EntityUtils. toByteArray (httpresponse.getEntity());
httpresponse.getEntity().getContent()

Post方式:

  • 创建HttpPost对象
HttpPost  httppost = new HttpPost(“http://php.weather.sina.com.cn/xml.php”);
  • 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HttpParams params)方法来添加请求参数;也可调用HttpPost对象的setEntity(HttpEntity entity)方法来设置请求参数。
List<NameValuePair> param = new ArrayList<NameValuePair>();
Param.add(new BasicNameValuePair(“city”,”huizhou”));
Param.add(new BasicNameValuePair(“password”, “DJOYnieT8234jlsK”));
Param.add(new BasicNameValuePair(“day”, “0”));
Httppost.setEntity(new UrlEncodedFormEntity(param,”utf-8”));
  • 创建HttpClient对象
HttpClient httpclient =  new DefaultHttpClient();
  • 调用HttpClient的execute方法发送请求返回一个HttpResponse对象
HttpResponse  httpresponse= httpclient.execute(httppost);
  • 判断连接状态
If(httpresponse.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
}
  • 调用HttpResponse的getEntity方法通过该方法获得HttpEntity对象,通过该对象获取服务器的响应信息。

Http请求乱码问题解决方案

使用EntityUtils的toString方法,传递编码,默认编码是ISO-8859-1
因此需要根据网页编码方式进行转码:

charset = getContentCharSet(entity);  
result = EntityUtils.toString(entitycharset);  
//获取页面编码方式
public static String getContentCharSet(final HttpEntity entity) {
String charset = null;
if (entity.getContentType() != null) {
HeaderElement values[] = entity.getContentType().getElements();
if (values.length > 0) {
NameValuePair param = values[0].getParameterByName("charset" );
if (param != null) {
charset = param.getValue();
}
}
}
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
return charset;
}