Code Monkey home page Code Monkey logo

android-notes's People

Contributors

fengfeilong0529 avatar

Watchers

 avatar

Forkers

qtly

android-notes's Issues

Android Camera开发

Andorid Camera开发:

一、Camera(android.hardware.Camera)相关api:

image

二、相机预览,Camera采集的图像需要在SurfaceView(新开一个线程进行绘制)或者TextureView上显示,大致代码实现如下:

PS:SurfaceView和TextureView对比
性能 SurfaceView TextureView
内存
绘制 及时 1-3帧的延迟
耗电
动画和截图 不支持 支持
2.1、SurfaceView+Camera实现如下:
surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
surfaceHolder = surfaceView.getHolder();
surfaceView.setKeepScreenOn(true);
//打开摄像头,并且旋转90度
camera = Camera.open();
camera.setDisplayOrientation(90);
//Camera预览的数据回调:
camera.setPreviewCallback(new Camera.PreviewCallback() {
	@Override
	public void onPreviewFrame(byte[] bytes, Camera camera) {
		//bytes即为相机采集出来的单帧Yuv格式数据,可转为Bitmap等格式使用
	}
});

surfaceHolder.addCallback(new SurfaceHolder.Callback() {

	@Override
	public void surfaceCreated(SurfaceHolder surfaceHolder) {
		//surface创建了,绑定holder并开启预览
		try {
			camera.setPreviewDisplay(surfaceHolder);
			camera.startPreview();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
		camera.release();
	}
});

//相机的一些设置:
Camera.Parameters parameters = camera.getParameters();
//Camera Preview Callback的YUV420常用数据格式有两种:一个是NV21,一个是YV12。Android一般默认使用YUV_420_SP的格式(NV21)
    parameters.setPreviewFormat(ImageFormat.NV21);//设置回调数据的格式
    parameters.setPreviewSize(width,height); //对应手机的height和width

    camera.setParameters(parameters);//传入参数
    camera.setPreviewDisplay(surfaceHolder);
    camera.setPreviewCallback(this);
    camera.startPreview();
2.2、TextureView+Camera实现如下:
(PS:TextureView是Android 4.0之后加入的。TextureView必须工作在开启硬件加速的环境中,也即配置文件里Activity的设置项里:android:hardwareAccelerated="true" 默认的这个属性就是true,因此不用再写了。但如果写成false,onSurfaceTextureAvailable()这个回调就进不来了)
textureView = findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(this);

// 打开摄像头并将展示方向旋转90度
camera = Camera.open();
camera.setDisplayOrientation(90);
camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        //这里面的Bytes的数据就是NV21格式的数据。
    }
});

//------ Texture 预览 -------
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    try {
        camera.setPreviewTexture(surfaceTexture);
        camera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    camera.release();//释放相机
    return false;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

}

三、取到 NV21 的数据回调:

PS:Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是YV12。Android一般默认使用YCbCr_420_SP的格式(NV21)
camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        //这里面的Bytes的数据就是NV21格式的数据。
		//处理data,转为Bitmap显示到ImageView上
        mPreviewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
        //转为YuvImage
        YuvImage yuvimage = new YuvImage(
                data,
                ImageFormat.NV21,
                mPreviewSize.width,
                mPreviewSize.height,
                null);
        mBaos = new ByteArrayOutputStream();
        //yuvimage 转换成jpg格式
        yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
        mImageBytes = mBaos.toByteArray();

        //将mImageBytes转换成bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;

        mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
        icon.setImageBitmap(rotateBitmap(mBitmap,getDegree()));
    }
});
3.1、Bitmap旋转:
/**
 * 顺时针旋转bitmap
 */
public static Bitmap rotateBitmap(Bitmap bitmap, int degress) {
	if (bitmap != null) {
		Matrix m = new Matrix();
		m.postRotate(degress);
		bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
		return bitmap;
	}
	return bitmap;
}

/**
 * 水平翻转bitmap
 *
 * @param bitmap
 * @return
 */
public static Bitmap horMirrorBitmap(Bitmap bitmap) {
	if (bitmap != null) {
		Matrix m = new Matrix();
		m.postScale(-1, 1);
		bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
		return bitmap;
	}
	return bitmap;
}

/**
 * 垂直翻转bitmap
 *
 * @param bitmap
 * @return
 */
public static Bitmap verMirrorBitmap(Bitmap bitmap) {
	if (bitmap != null) {
		Matrix m = new Matrix();
		m.postScale(1, -1);
		bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
		return bitmap;
	}
	return bitmap;
}

四、前后摄像头切换:

if (Camera.getNumberOfCameras() > 1) {
	if (camera != null) {
		//切换摄像头要释放资源,否则会报错
		camera.setPreviewCallback(null);
		camera.stopPreview();
		camera.lock();
		camera.release();
		camera = null;
	}

	if (!mIsFront) {
		camera = open(Camera.CameraInfo.CAMERA_FACING_FRONT);
	} else {
		camera = open(Camera.CameraInfo.CAMERA_FACING_BACK);
	}
	mIsFront = !mIsFront;

	initCamParams();
	camera.setPreviewDisplay(surfaceHolder);
	camera.startPreview();
	camera.startFaceDetection();//一定要在startPreview()后调用
} else {
	Toast.makeText(this, "没有可切换的摄像头", Toast.LENGTH_SHORT).show();
}
public Camera open() {
    int numberOfCameras = Camera.getNumberOfCameras();
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    for (int i = 0; i < numberOfCameras; i++) {
        Camera.getCameraInfo(i, cameraInfo);
        return Camera.open(i);
    }
    return null;
}

五、YV12和NV21/NV12数据格式区别:

参考:

总结:

  • 因为系统默认只能同时开启一个摄像头不管是前置摄像头还是后置摄像头,所以在关闭页面 或者 打开其他摄像头之前,一定要先调用release()方法释放当前相机资源。
  • Android中的摄像头Camera是区分前置和后置的,所以这里就要做一个前置和后置摄像头的切换功能了。
  • 注意预览画布旋角度问题。
  • 相机图像数据都是来自于相机硬件的图像传感器这个方向是不能改变的。
  • 相机在预览的时候是有一个预览方向的,可以通过setDisplayOrientation()设置预览方向。
  • 相机所采集的照片也是有一个方向的,这个方向与预览时的方向互不相干。
  • Camera在执行startPreview时必须保证TextureView的SurfaceTexture上来了,如果因为一些性能原因onSurfaceTextureAvailable()这个回调上不来就开预览,就开不了的。如果发生这种情况,就在onSurfaceTextureAvailable()回调里执行open和startPreview操作,保证万无一失。

AS中Make Project、Clean Project、Rebuild Project 的区别

Make Project:

  • 编译Project下所有Module,一般是自上次编译后Project下有更新的文件,增量编译,不生成Apk。

Make Selected Modules:

  • 编译指定的Module,一般是自上次编译后Module下有更新的文件,增量编译,不生成Apk。

Clean Project:

  • 删除之前编译后的编译文件。部分版本的AS会自动重新编译整个Project,不生成Apk。

Rebuild Project:

  • 先执行Clean操作,删除之前编译的编译文件和可执行文件,然后重新编译新的编译文件,不生成Apk

Build APK:

  • 前面4个选项都是编译,没有生成apk文件[旧版本AS,新版本的AS会自动生成debug.apk],如果想生成Apk,需要点击Build APK。

Generate Signed APK:

  • 生成有签名的Apk(一般项目嵌入第三方,生成release包时必须混淆,否则无法生成Apk)。

Sync Project with Gradle files:

  • 按照Gradle文件同步引用库

Android中图片压缩

常用的图片压缩格式:

compress

其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

  • ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
  • ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
  • ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
  • RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
      

我们在做压缩处理的时候,可以先通过改变Bitmap的图片编码格式,来达到压缩的效果,其实压缩最主要就是要么改变其宽高,要么就通过减少其单个像素占用的内存。

常用的压缩方法:

public class CompressUtil {
    private static final String TAG = "CompressUtil";

    /**
     * 质量压缩
     * <p>
     * 质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,
     * 图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的
     * <p>
     * PS:对PNG图片无效,因为png是无损压缩
     *
     * @param bit
     * @param quality 质量
     * @return
     */
    public static Bitmap qualityCompress(Bitmap bit, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bit.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

        Log.i(TAG, "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                + "M  宽度为" + bm.getWidth() + "  高度为" + bm.getHeight()
                + "  bytes.length=" + (bytes.length / 1024) + "KB"
                + "  quality=" + quality);
        return bm;
    }

    /**
     * 采样率压缩
     * <p>
     * 采样率压缩其原理是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4
     * <p>
     *
     * @param context
     * @param sampleSize 采样率
     * @return
     */
    public static Bitmap sampleSizeCompress(Context context, int sampleSize) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = sampleSize;
        Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.mipmap.animal2, options);

        Log.i(TAG, "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                + "M 宽度为" + bm.getWidth() + " 高度为" + bm.getHeight());
        return bm;
    }

    /**
     * matrix缩放法压缩
     * <p>
     * 通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样
     * <p>
     *
     * @param bit
     * @param sx
     * @param sy
     * @return
     */
    public static Bitmap matrixScaleCompress(Bitmap bit, float sx, float sy) {
        Matrix matrix = new Matrix();
        matrix.setScale(sx, sy);
        Bitmap bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
                bit.getHeight(), matrix, true);

        Log.i(TAG, "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                + "M 宽度为" + bm.getWidth() + " 高度为" + bm.getHeight());
        return bm;
    }

    /**
     * RGB_565压缩
     * <p>
     * RGB_565压缩是通过改用内存占用更小的编码格式来达到压缩的效果。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。
     * 一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销
     * <p>
     *
     * @param context
     * @return
     */
    public static Bitmap RGB565Compress(Context context) {
        BitmapFactory.Options options2 = new BitmapFactory.Options();
        options2.inPreferredConfig = Bitmap.Config.RGB_565;

        Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.mipmap.animal2, options2);

        Log.i(TAG, "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                + "M 宽度为" + bm.getWidth() + " 高度为" + bm.getHeight());
        return bm;
    }

    /**
     * 创建缩放Bitmap
     * <p>
     * 将图片压缩成用户所期望的长度和宽度,但是这里要说,如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰
     * <p>
     *
     * @param bit
     * @param dstWidth
     * @param dstHeight
     * @return
     */
    public static Bitmap createScaledBitmapCompress(Bitmap bit, int dstWidth, int dstHeight) {
        Bitmap bm = Bitmap.createScaledBitmap(bit, dstWidth, dstHeight, true);

        Log.i(TAG, "压缩后图片的大小" + (bm.getByteCount() / 1024) + "KB 宽度为"
                + bm.getWidth() + " 高度为" + bm.getHeight());
        return bm;
    }

/**
 * 将图片压缩到指定size
 * <p>
 * 动态降低图片quality
 *
 * @param bit
 * @param maxFileSize 最大文件大小,单位KB
 */
public static Bitmap compressBmp2LimitSize(Bitmap bit, int maxFileSize) {
	//进行有损压缩
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	int quality = 100;
	bit.compress(Bitmap.CompressFormat.JPEG, quality, baos);    //质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小)
	int baosLength = baos.toByteArray().length;
	while (baosLength / 1024 > maxFileSize) {   //循环判断如果压缩后图片是否大于maxMemmorrySize,大于继续压缩
		baos.reset();   //重置baos即让下一次的写入覆盖之前的内容
		quality = Math.max(0, quality - 10);    //图片质量每次减少10
		bit.compress(Bitmap.CompressFormat.JPEG, quality, baos);    //将压缩后的图片保存到baos中
		baosLength = baos.toByteArray().length;
		if (quality == 0)   //如果图片的质量已降到最低,则不再进行压缩
			break;
	}
	Bitmap bm = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.toByteArray().length);

	String tip = "压缩后图片的大小" + ((float) bm.getByteCount() / 1024 / 1024)
			+ "M  宽度为" + bm.getWidth() + "  高度为" + bm.getHeight()
			+ "  bytes.length=" + (baos.toByteArray().length / 1024) + "KB"
			+ "  quality=" + quality;
	Log.i(TAG, tip);

	return bm;
}
}

参考:

Android调用系统图片裁剪

private File cameraSavePath;//拍照照片路径
private Uri mUri;
private String photoName = System.currentTimeMillis() + ".jpg";

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	...
	cameraSavePath = new File(Environment.getExternalStorageDirectory().getPath() + "/" + photoName);
}

/**
 * 从相册获取照片
 */
public void takePhotoByAlbum() {
	checkReadStoragePermission();
	Intent intent = new Intent(Intent.ACTION_PICK);
	intent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
	startActivityForResult(intent, ALBUM_REQUEST_CODE);
}

/**
 * 拍照获取照片
 */
private void takePhotoByCamera() {
	checkCameraPermission();
	Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
		mUri = FileProvider.getUriForFile(MainActivity.this, "com.example.hxd.pictest.fileprovider", cameraSavePath);
		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
	} else {
		mUri = Uri.fromFile(cameraSavePath);
	}
	intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
	MainActivity.this.startActivityForResult(intent, CAMERA_REQUEST_CODE);
}

/**
 * 裁剪照片
 *
 * @param uri
 */
public void cropImg(Uri uri) {
	Intent intent = new Intent("com.android.camera.action.CROP");
	intent.setDataAndType(uri, "image/*");
	intent.putExtra("crop", true);
	// 裁剪框的比例,1:1
	intent.putExtra("aspectX", 1);
	intent.putExtra("aspectY", 1);
	// 裁剪后输出图片的尺寸大小
	intent.putExtra("outputX", 250);
	intent.putExtra("outputY", 250);

	intent.putExtra("outputFormat", "JPEG");// 图片格式
	intent.putExtra("noFaceDetection", true);// 取消人脸识别
	intent.putExtra("return-data", true);
	startActivityForResult(intent, REQUEST_CUT_PHOTO);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
	if (resultCode == RESULT_OK) {
		if (requestCode == ALBUM_REQUEST_CODE) {
			try {
				cropImg(data.getData());
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else if (requestCode == CAMERA_REQUEST_CODE) {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
				cropImg(Uri.fromFile(cameraSavePath));
			} else {
				cropImg(mUri);
			}
		} else if (requestCode == REQUEST_CUT_PHOTO) {
			try {
				Bundle bundle = data.getExtras();
				if (bundle != null) {
					//在这里获得了剪裁后的Bitmap对象,可以用于上传
					Bitmap image = bundle.getParcelable("data");
					mIvCropedImg.setImageBitmap(image);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

注意:各Android版本、各机型适配兼容问题,待实际使用时解决!

Android原生API获取经纬度等位置信息

Purpose:通过GPS或NetWork来获取当前移动终端设备的经纬度功能

前提:设备需要带有GPS模块或NetWork模块;

参考:
https://www.jianshu.com/p/e97d55262bd4
https://www.jianshu.com/p/05f85f2f74c1

实现:

/**
 * 调用本地GPS来获取经纬度
 * @param context
 */
private void getLocation(Context context) {
	//1.获取位置管理器
	locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
	//2.获取位置提供器,GPS或是NetWork
	List<String> providers = locationManager.getProviders(true);
	if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
		//如果是网络定位
		locationProvider = LocationManager.NETWORK_PROVIDER;
	} else if (providers.contains(LocationManager.GPS_PROVIDER)) {
		//如果是GPS定位
		locationProvider = LocationManager.GPS_PROVIDER;
	} else if (providers.contains(LocationManager.PASSIVE_PROVIDER)) {
		//如果是PASSIVE定位
		locationProvider = LocationManager.PASSIVE_PROVIDER;
	}
	else {
		Toast.makeText(this, "没有可用的位置提供器", Toast.LENGTH_SHORT).show();
		return;
	}

	//3.获取上次的位置,一般第一次运行,此值为null
	if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
		// TODO: Consider calling
		//    ActivityCompat#requestPermissions
		// here to request the missing permissions, and then overriding
		//   public void onRequestPermissionsResult(int requestCode, String[] permissions,
		//                                          int[] grantResults)
		// to handle the case where the user grants the permission. See the documentation
		// for ActivityCompat#requestPermissions for more details.
		return;
	}
	Location location = locationManager.getLastKnownLocation(locationProvider);
	if (location != null) {
		showLocation(location);
	} else {
		// 监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace
		locationManager.requestLocationUpdates(locationProvider, MIN_TIME, MIN_DISTANCE, mListener);
	}
}


LocationListener mListener = new LocationListener() {
	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
	}

	@Override
	public void onProviderEnabled(String provider) {
	}

	@Override
	public void onProviderDisabled(String provider) {
	}

	// 如果位置发生变化,重新显示
	@Override
	public void onLocationChanged(Location location) {
		showLocation(location);
	}
};


 /**
 * 获取经纬度
 * @param location
 */
private void showLocation(Location location) {
	longtitude=String.valueOf(location.getLongitude());
	latitude=String.valueOf(location.getLatitude());
	Log.e("经纬度信息:",longtitude+"  "+latitude);
}

Android常用图片加载框架对比

Glide:

  • Google开源的项目,模仿了Picasso的功能,并做了扩展,如加载gif;
  • Glide默认的Bitmap格式是RGB_565,比Picasso默认的ARGB_8888格式的内存开销要小一半;
  • Picasso缓存的是全尺寸的图像(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的图像(即5656和128128是两个缓存) 。
  • 支持gif;

Picasso:

  • Square公司开源的项目,主导者是Android大婶JakeWharton;
  • Picasso和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。
  • 不支持gif;

Fresco:

  • FaceBook开源的项目;
  • 支持gif;
  • 优点:5.0以下内存优化做的非常好,在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。
  • 缺点:体积非常大,不够轻量

Universal ImageLoader:

  • 很早的图片加载框架了
  • ImageLoader 整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor 五大模块,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。

注意:

  • 按体积计算:Fresco > Glide > Picasso
  • Picasso能做的Glide都能做,但是Picasso更轻量级,在使用Okhttp、retrofit网络框架的项目中使用可以优先考虑,在类似美拍、爱拍类视频应用中,还是选择Gilde好,因为Gilde在大型图片流处理更优,如gif、video。

使用参考:

Java设计模式之观察者模式

概念:当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

参考:

特点:

  • 当被观察者状态发生变化,会通知所有跟它有依赖关系的观察者。

实际应用:

线程池的submit和execute方法区别

线程池中的execute方法大家都不陌生,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果。下面简要介绍一下两者的三个区别:

1、接收的参数不一样

2、submit有返回值,而execute没有

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。
然后我就可以把所有失败的原因综合起来发给调用者。

个人觉得cancel execution这个用处不大,很少有需要去取消执行的。

而最大的用处应该是第二点。

3、submit方便Exception处理

意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

下面一个小程序演示一下submit方法

public class RunnableTestMain {

public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(2);
    
    /**
     * execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
     */
    pool.execute(new RunnableTest("Task1")); 
    
    /**
     * submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
     */
    Future future = pool.submit(new RunnableTest("Task2"));
    
    try {
        if(future.get()==null){//如果Future's get返回null,任务完成
            System.out.println("任务完成");
        }
    } catch (InterruptedException e) {
    } catch (ExecutionException e) {
        //否则我们可以看看任务失败的原因是什么
        System.out.println(e.getCause().getMessage());
    }

}

}

public class RunnableTest implements Runnable {

private String taskName;

public RunnableTest(final String taskName) {
    this.taskName = taskName;
}

@Override
public void run() {
    System.out.println("Inside "+taskName);
    throw new RuntimeException("RuntimeException from inside " + taskName);
}

}

串口通讯相关

串口通讯(SerialPort Communication)在Android中应用:

  • RS485、RS232等
  • 串行端口 (SerialPort)简称:串口,主要用于数据被逐位按顺序传送的通讯方式称为串口通讯(简单来讲就是按顺序一位一位地传输数据)。
  • 常见的串口有25针和9针(遵循RS-232标准)

原理:

  • 串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。
  • 串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线(GND)、发送(TX)、接收(RX)。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。

基本参数:

  • 波特率:主要作用是控制数据传输速率;
  • 数据位
  • 停止位
  • 奇偶校验:错误检测;

测试工具:串口调试助手

image

参考文章:

AS debug技巧

参考:https://blog.csdn.net/My_TrueLove/article/details/52240558

调试窗口概览:

image

条件断点:

顾名思义,带有条件的断点,一般在循环时用的比较多。
image

日志断点:

如果我们很关心某个变变量的变化过程,一般会使用Log打印一堆值来观察,观察完了再删除,说真的有点麻烦。这时候,你需要日志断点,看图:
image

Camera开发之人脸检测画框

Camera人脸检测画框:

参考:https://www.jianshu.com/p/3bb301c302e8
demo:https://github.com/fengfeilong0529/Fig-FaceDetection

调用camera.startFaceDetection()开启人脸检测;
调用camera.stopFaceDetection()停止人脸检测;
调用camera.setFaceDetectionListener()监听人脸,会返回face,包含人脸框坐标,眼睛嘴巴等Point,可根据坐标进行人脸画框,Camera.Face属性如图:
image

PS:需要注意的是,这里返回的rect坐标范围是左上角(-1000,-1000),右上角(1000,1000),对应手机屏幕宽高需做坐标转换。

Rect:人脸的边界。它所使用的坐标系中,左上角的坐标是(-1000,-1000),右下角的坐标是(1000,1000)
例如:假设屏幕的尺寸是1280 * 720,有一个矩形在相机的坐标系中的位置是(-1000,-1000,0,0),它相对应的在安卓屏幕坐标系中的位置就是(0,0,360,640),如下图:
image

人脸坐标转换代码:

List<RectF> rects = new ArrayList<>();
//返回的face.rect坐标需做转换
Matrix matrix = new Matrix();
boolean mirror = mIsFront;//前置摄像头需做镜像翻转
matrix.setScale(mirror ? -1 : 1, 1);
matrix.postRotate(90);//setDisplayOrientation的角度
matrix.postScale(mSurfaceview.getWidth() / 2000f, mSurfaceview.getHeight() / 2000f);
matrix.postTranslate(mSurfaceview.getWidth() / 2f, mSurfaceview.getHeight() / 2f);

for (Camera.Face face : faces) {
	RectF srcRect = new RectF(face.rect);
	RectF dstRect = new RectF(0f, 0f, 0f, 0f);
	matrix.mapRect(dstRect, srcRect);
	rects.add(dstRect);
}
mDetectView.onDetectFace(rects);

DetectView.java:

public class DetectView extends View {

    private Paint mPaint;
    private Paint mEyePaint;
    private List<Point> leftEyes;
    private List<Point> rightEyes;
    private List<RectF> rects;

    public DetectView(Context context) {
        this(context, null);
    }

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

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.GREEN);
        float mStrokeWidth = 6;
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE);

        mEyePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEyePaint.setColor(Color.RED);
        mEyePaint.setStrokeWidth(mStrokeWidth);
        mEyePaint.setDither(true);
        mEyePaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        try {
            for (Point leftEye : leftEyes) {
                canvas.drawPoint(leftEye.x, leftEye.y, mEyePaint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            for (Point rightEye : rightEyes) {
                canvas.drawPoint(rightEye.x, rightEye.y, mEyePaint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            for (RectF rect : rects) {
                canvas.drawRect(rect, mPaint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void onDetectEyes(List<Point> leftEyes, List<Point> rightEyes) {
        this.leftEyes = leftEyes;
        this.rightEyes = rightEyes;
        invalidate();
    }

    public void onDetectFace(List<RectF> rects) {
        this.rects = rects;
        invalidate();
    }
}

转换前的效果:
image
转换后的效果:
image

Wifi直连(Wifi Direct)

特点:

  • 通过 WIFI 直接建立连接,允许无线网络中的设备无须通过无线路由器即可相互连接;
  • 既支持基础设施网络,也支持蓝牙式P2P(Peer To Peer,点对点)连接,在传输速度与传输距离方面都比蓝牙有大幅提升;
  • 作用原理类似于蓝牙,设备连接之后就能相互传文件了,WLAN直连的传输速度是近乎蓝牙速度的100倍。

应用场景:

  • 手机投屏到电视、电脑等设备;
  • 两台手机间的文件传输;

参考:http://c.biancheng.net/view/3190.html
demo:https://github.com/fengfeilong0529/WifiDirectDemo

API:

WifiP2pManager:

  • WifiP2pManager 类提供相关 API 用于发现可连接的点,并进行请求和建立连接。
  • 每个 WifiP2pManager 的方法都要求传入对应的监听器,用于监听对该方法是否成功运行。
  • 当检测到特定事件,如可连接的点减少或者发现了新的可连接的点,WIFI Direct 框架会通过 Intent 通知用户。

Java设计模式之简单工厂模式

概念:一个工厂类根据传入的参量决定创建出那一种产品类的实例。

优点:

  • 工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化

缺点:

  • 由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利

代码示例如下:

创建产品类:

public interface Fruit{
      String name();
}

public class Apple implements Fruit{
      @Override
      private String name() {
         return "Apple";
   }
}

public class Orange implements Fruit{
      @Override
      private String name() {
         return "Orange";
   }
}

public class Pear implements Fruit{
      @Override
      private String name() {
         return "Pear";
   }
}

创建工厂类:

public class FruitFactory {

    //根据传入类型生产不同水果
    public static Fruit newInstance(String type) {
        if (type.equals("apple")) {
            Fruit apple= new Apple();
            return apple;
        } else if(type.equals("orange")) {
            Fruit orange= new Orange();
            return orange;
        } else if(type.equals("pear")) {
            Fruit pear= new Pear();
            return pear;
        } else {
            System.out.println("生产不出来");
            return null;
        }            
    }

}

创建工厂类2(反射方式创建对象):

public class SampleFactory1 {

    //传入class
    public static Fruit newInstance(Class c) {
        Fruit fruit = null;
        try {
            fruit = (Fruit) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            System.out.println("不支持抽象类或接口");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.out.println("没有足够权限,即不能访问私有对象");
        } catch (ClassNotFoundException e) {
            System.out.println("类不存在");
            e.printStackTrace();
        }    
        return fruit;
    }

}

Android埋点/全埋点技术

概念:

  • 埋点就是在用户使用APP时,对用户的操作行为进行记录,比如用户点击了一个 Button 然后跳转至了哪个界面,然后在这个界面上又点击了哪个控件,等等等一系列操作进行记录,然后APP将行为记录传至后台,这就是埋点。
  • 应用:利用这些信息,可以对用户进行数字画像,根据用户的行为特点,针对性地提供功能服务,以及对软件的优化等。现在市场上已经有很多第三方的埋点SDK,比如说友盟的用户行为数据检测。

参考:

应用场景:

  • 用户行为分析
  • 软件优化
  • 第三方统计sdk就有应用,如友盟,腾讯移动统计:统计用户打开了哪些界面,停留了多久等等

实现方案:

  • 1、点击事件代理类,具体埋点操作在此类种处理,减少业务入侵
  • 2、AOP(Aspect Oriented Programming)切面,在编译期间对代码进行动态管理,以达到统一维护的目的

Okhttp加载https证书

工具类:

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
 * Https 证书工具类
 */
public class SSLUtil {
    //使用命令keytool -printcert -rfc -file srca.cer 导出证书为字符串,然后将字符串转换为输入流,如果使用的是okhttp可以直接使用new Buffer().writeUtf8(s).inputStream()

    /**
     * 返回SSLSocketFactory
     *
     * @param certificates 证书的输入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
        return getSSLSocketFactory(null, certificates);
    }

    /**
     * 双向认证
     *
     * @param keyManagers  KeyManager[]
     * @param certificates 证书的输入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(KeyManager[] keyManagers, InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            return socketFactory;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获得双向认证所需的参数
     *
     * @param bks          bks证书的输入流
     * @param keystorePass 秘钥
     * @return KeyManager[]对象
     */
    public static KeyManager[] getKeyManagers(InputStream bks, String keystorePass) {
        KeyStore clientKeyStore = null;
        try {
            clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bks, keystorePass.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, keystorePass.toCharArray());
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            return keyManagers;
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用:

  • 将签名文件放到assets目录;
  • 调用如下:
    public RequestManager() {
        try {
            mSslSocketFactory = SSLUtil.getSSLSocketFactory(MyApp.getInstance().getAssets().open("abc.cer")); //SSL证书
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private OkHttpClient getHttpClient() {
        if (mOkHttpClient == null){
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(5, TimeUnit.SECONDS);
            builder.readTimeout(5, TimeUnit.SECONDS);
            builder.writeTimeout(5, TimeUnit.SECONDS);
            if (mSslSocketFactory != null){
                builder.sslSocketFactory(mSslSocketFactory);
            }
            mOkHttpClient = builder.build();
        }
        return mOkHttpClient;
    }

相关:https://www.jianshu.com/p/4f750317a52d

Android Camera相机预览、获取单帧数据并转为Bitmap图片

基本步骤:

  • 1、通过Camera api获取相机预览数据onPreviewCallback(byte[] data ... );
  • 2、保存单帧数据,转为YuvImage;
  • 3、将Yuv数据转为Bitmap;

参考:

相机捕获数据转Yuv:

byte[] data = preSurfaceView.takePhoto();    //相机抓拍的单帧数据
if (data != null) {
	YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, PreviewHandle.CAMERA_WIDTH, PreviewHandle.CAMERA_HEIGHT, null);
	mBitmap = YuvUtil.compressYuvToBitmap(yuvImage, 100);
	ivAvatar.setImageBitmap(mBitmap);
}

YUV数据转bitmap:

public static Bitmap compressYuvToBitmap(YuvImage yuvImage, int quality) {
	Rect fullRect = new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight());
	byte[] bytes = compressYuvToJpg(yuvImage, fullRect, quality);
	return bytes2Bimap(bytes);
}

public static byte[] compressYuvToJpg(YuvImage yuvImage, int quality) {
	Rect fullRect = new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight());
	return compressYuvToJpg(yuvImage, fullRect, quality);
}

public static byte[] compressYuvToJpg(YuvImage yuvImage, Rect cutRect, int quality) {
	if (yuvImage != null) {
		ByteArrayOutputStream baos = null;
		byte[] datas = null;
		;
		try {
			baos = new ByteArrayOutputStream();
			yuvImage.compressToJpeg(cutRect, quality, baos);
			datas = baos.toByteArray();
			baos.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (baos != null) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return datas;
	}
	return null;
}

避免内存泄漏的Yuv转Bitmap方法:

参考:https://www.jianshu.com/p/7436d6ccc7bf

import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;

/**
 * 使用RenderScript将视频YUV流转换为BMP
 * 注:这个类适用于CameraPreview不变的情况
 */
public class FastYUVtoRGB {
    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private Type.Builder yuvType, rgbaType;
    private Allocation in, out;

    public FastYUVtoRGB(Context context) {
        rs = RenderScript.create(context);
        yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    }


    public Bitmap convertYUVtoRGB(byte[] yuvData, int width, int height) {
        if (yuvType == null) {
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuvData.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }
        in.copyFrom(yuvData);
        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);
        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        out.copyTo(bmpout);
        return bmpout;
    }
}

查看电脑或服务器某端口是否打开的方法

方式一:telnet

  • telnet使用的是tcp协议,只能检测tcp端口是否打开;
  • 需要配置telnet(配置方法请百度)
  • 命令:telnet 192.168.2.169 8080

方式二:nc(netcat)

  • 既可检测tcp端口,也可检测udp端口;
  • 需配置netcat(配置方法请百度)
  • 检测tcp端口:nc -z 192.168.2.169 8080
  • 检测udp端口:nc -uz 192.168.2.169 8080

RadioGroup调用check()方法,会走OnCheckedChanged()回调解决方案

代码示例:

radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                View viewById = group.findViewById(checkedId);
                if (viewById != null && viewById.isPressed()) {
                    //点击触发的回调
                } else {
                    //调用check()方法触发的回调
                }
            }
        });

Android开启http服务

实现方案:

参考:

AndServer使用:

一、添加依赖:

implementation 'com.yanzhenjie:andserver:1.1.4'

二、使用代码:

2.1)新建HttpServer.java,定义IP为设备IP,端口6666,以及添加需要的接口:

public class HttpServer {
    private static final String TAG = "HttpServer";

    private static final String URL_ADD_CARD = "/addCard";
    private static final String URL_DELETE_CARD = "/deleteCard";
    private Server server;

    private static final class Holder {
        private static final HttpServer INSTANCE = new HttpServer();
    }

    public static HttpServer getInstance() {
        return HttpServer.Holder.INSTANCE;
    }

    private HttpServer() {
        RxBus.get().register(this);
    }

    public void init() {
        InetAddress addr = NetUtils.getLocalIPAddress();
        if (addr != null) {
            init(addr);
        }
    }

    public void reInit() {
        deinit();
        init();
    }

    private void init(InetAddress addr) {
        synchronized (this) {
            server = AndServer.serverBuilder()
                    .port(6666)
                    .timeout(10, TimeUnit.SECONDS)
                    .inetAddress(addr)
                    .listener(serverListener)

                    .registerHandler(URL_ADD_CARD, new AddCardHandler())
                    .registerHandler(URL_DELETE_CARD, new DeleteCardHandler())
                    .build();

            if (!server.isRunning()){
                server.startup();
            }
        }
    }

    private void deinit() {
        synchronized (this) {
            if (server != null) {
                server.shutdown();
            }
        }
    }

    private Server.ServerListener serverListener = new Server.ServerListener() {
        @Override
        public void onStarted() {
            Log.d(TAG, "HttpServer started");
        }

        @Override
        public void onStopped() {
            Log.d(TAG, "HttpServer onStopped");
        }

        @Override
        public void onError(Exception e) {
            Log.d(TAG, "HttpServer onError:" + e.getMessage());
        }
    };

}

2.2)AddCardHandler.java:

public class AddCardHandler implements RequestHandler {
    private static final String TAG = "AddCardHandler";

    @RequestMapping(method = {RequestMethod.POST})
    @Override
    public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
        StringBuilder builder = new StringBuilder();
        BaseResponse baseResponse = new BaseResponse(0, "添加成功");
        String content = HttpRequestParser.getContent(request);
        AddCardBean addCardBean = new Gson().fromJson(content, AddCardBean.class);
        if (addCardBean != null) {
            String name = addCardBean.getName();
            String idNum = addCardBean.getIdnum();
            String photo = addCardBean.getPhoto();
            String gender = addCardBean.getGender();
            String nation = addCardBean.getNation();
            String address = addCardBean.getAddress();
            String validStart = addCardBean.getValid_start();
            String validEnd = addCardBean.getValid_end();

            if (TextUtils.isEmpty(name)) {
                builder.append("name不能为空,");
            }
            if (TextUtils.isEmpty(idNum)) {
                builder.append("idNum不能为空,");
            }
            if (TextUtils.isEmpty(photo)) {
                builder.append("photo不能为空,");
            }
            if (TextUtils.isEmpty(validStart)) {
                builder.append("validStart不能为空,");
            }
            if (TextUtils.isEmpty(validEnd)) {
                builder.append("validEnd不能为空,");
            }

            if (!TextUtils.isEmpty(builder.toString())) {
                baseResponse.code = 1;
                baseResponse.msg = builder.toString();
            } else {
                //添加名单
                CardBean cardBean = new CardBean();
                cardBean.setName(name);
                cardBean.setSex(gender);
                cardBean.setNation(nation);
                cardBean.setIdNum(idNum);
                cardBean.setAddress(address);
                cardBean.setUserType(UserType.TYPE_WHITE);
                cardBean.setValidStart(validStart);
                cardBean.setValidEnd(validEnd);

                Log.i(TAG, "handle: " + cardBean.toString());

                //解析base64图片
                Bitmap bitmap = FileUtil.base64StringToBitmap(photo);
                String error = CardManager.getInstance().addCard(bitmap, idNum + Constants.JPG, cardBean);
                RxBus.get().post(new RxUserChange());

                if (!TextUtils.isEmpty(error)) {
                    baseResponse.code = 1;
                    baseResponse.msg = error;
                }
            }
        } else {
            baseResponse.code = 1;
            baseResponse.msg = "参数错误";
        }

        response.setEntity(new StringEntity(new Gson().toJson(baseResponse), "utf-8"));
    }
}

2.3)DeleteCardHandler.java:

public class DeleteCardHandler implements RequestHandler {
    private static final String TAG = "DeleteCardHandler";

    @RequestMapping(method = {RequestMethod.POST})
    @Override
    public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
        StringBuilder builder = new StringBuilder();
        BaseResponse baseResponse = new BaseResponse(0, "删除成功");

        String content = HttpRequestParser.getContent(request);
        DeleteCardBean deleteCardBean = new Gson().fromJson(content, DeleteCardBean.class);
        if (deleteCardBean != null) {
            List<String> idnums = deleteCardBean.getIdnum();
            if (idnums.size() == 0) {
                builder.append("idnum不能为空");
            }
            if (!TextUtils.isEmpty(builder.toString())) {
                baseResponse.code = 1;
                baseResponse.msg = builder.toString();
            } else {
                //删除名单
                for (String idnum : idnums) {
                    CardManager.getInstance().deleteCard(idnum);
                    RxBus.get().post(new RxUserChange());
                }
            }
        } else {
            baseResponse.code = 1;
            baseResponse.msg = "参数错误";
        }

        response.setEntity(new StringEntity(new Gson().toJson(baseResponse), "utf-8"));
    }
}

2.4)在SplashActivity中开启HttpServer服务:

HttpServer.getInstance().init();// 开启http服务

adb命令查看Activity 堆栈情况

adb shell dumpsys activity---------------查看ActvityManagerService 所有信息
adb shell dumpsys activity activities----------查看Activity组件信息
adb shell dumpsys activity services-----------查看Service组件信息
adb shell dumpsys activity providers----------产看ContentProvider组件信息
adb shell dumpsys activity broadcasts--------查看BraodcastReceiver信息
adb shell dumpsys activity intents--------------查看Intent信息
adb shell dumpsys activity processes---------查看进程信息
adb shell dumpsys activity activities | findstr mResumedActivity---------查看当前获取焦点的Activity

Java设计模式之单例模式

概念:保证某个类全局只有一个实例,并提供一个全局访问点。

参考:https://www.runoob.com/design-pattern/singleton-pattern.html

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

实现方式:

  • 懒汉式,线程不安全
  • 饿汉式,线程安全
  • 双重验证锁,多线程安全
  • ......

实际应用:

  • 登录后的用户类
  • 与数据库的连接类
  • 音频播放管理类

Android常用的实现异步的几种方式

一、new thread:

......

二、HandlerThread:

 private class myBackRunnable implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mainHandler.post(new myUIRunnable());
        }
   }

 private class myUIRunnable implements Runnable{

        @Override
        public void run() {
            btnStart.setText("线程结束");
        }
   }

 backHandler = new Handler(handlerThread.getLooper());
 mainHandler = new Handler(getMainLooper());
 backHandler.post(new myBackRunnable());

三、AsyncTask:

public class MyAsyncTask extends AsyncTask<Integer, String, String> {
	private Button btn;
	public MyAsyncTask(Button btn) {
		super();
		this.btn = btn;
	}
	
	//该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
	@Override
	protected void onPreExecute() {
		btn.setText("开始执行异步线程");
	}

	@Override
	protected String doInBackground(Integer... integers) {
		Log.e("xxxxxx","xxxxxxexecute传入参数="+integers[0]);
		try {
			Thread.sleep(1000);
			publishProgress("过了一秒");
			Thread.sleep(1000);
			publishProgress("过了两秒");
			Thread.sleep(1000);
			publishProgress("过了三秒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "doInBackground的返回";
	}
	
	/**
	 * 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
	 * 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
	 */
	@Override
	protected void onPostExecute(String result) {
		btn.setText("线程结束" + result);
	}
	
	/**
	 * 这里的Intege参数对应AsyncTask中的第二个参数
	 * 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
	 * onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
	 */
	@Override
	protected void onProgressUpdate(String... values) {

		String vlaue = values[0]+"";
		Log.e("xxxxxx","xxxxxx vlaue="+vlaue);
		btn.setText(vlaue+"");


	}
}

//调用
myAsyncTask.execute(1000);

AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。

AsyncTask定义了三种泛型类型 Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
对应到上面的demo就是Integer, String, String。

onPreExecute() 这里相当于线程的开始,可以进行UI的处理

doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。

onProgressUpdate(String... Progress) 这里对应的是doInBackground中调用的publicProgress,在这里进行处理,这里是UI主线程可以进行界面的更新

onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回。

四、IntentService:

IntentService具有Service一样的生命周期,也提供了后台线程中的异步处理机制

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(3*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Intent intentresult = new Intent(IntentServiceActivity.RESULT);

        sendBroadcast(intentresult);
    }
    public void onDestroy()
    {
        super.onDestroy();
    }
}

//调用
Intent intent=new Intent(IntentServiceActivity.this,MyIntentService.class);
startService(intent);

五、线程池:

Android中常见的4种线程池

六、RxJava:

Observable.create(new ObservableOnSubscribe<BaseResponse>() {
	@Override
	public void subscribe(ObservableEmitter<BaseResponse> e) throws Exception {
	   
	}
})
		.subscribeOn(Schedulers.io())               //执行在子线程
		.observeOn(AndroidSchedulers.mainThread())  //回调在主线程
		.subscribe(new Consumer<BaseResponse>() {
			@Override
			public void accept(BaseResponse baseResponse) throws Exception {
			   
			}
		});

参考:

https://www.jianshu.com/p/00b130319864
https://blog.csdn.net/xiankog/article/details/83111766

Android中CMake使用

目的:让jni、ndk开发更简便

CMake:

“CMake”即“cross platform make”,跨平台安装工具,可以通过简单的命令语句来执行构建,编译,生成的操作,他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。构建的组态档为CMakeLists.txt文件,CMake 支持 in-place 建构(二进档和源代码在同一个目录树中)和 out-of-place 建构(二进档在别的目录里),因此可以很容易从同一个源代码目录树中建构出多个二进档。CMake 也支持静态与动态程式库的建构。

参考:

使用条件:

  • AS 2.2+
  • Gradle 2.2.0+
  • NDK工具
  • SDK Tools,如下图:
    image

Java设计模式之建造者模式

概念:建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

参考:https://www.runoob.com/design-pattern/builder-pattern.html

优点:

  • 建造者独立,易扩展
  • 便于控制细节风险

缺点:

  • 产品必须有共同点,范围有限制
  • 如内部变化复杂,会有很多的建造类

注意:

  • 与工厂模式的区别:建造者模式更加关注与零件装配的顺序

实际应用:

屏幕适配

相关参考:https://blog.csdn.net/zcn596785154/article/details/77088517

相关概念:

  • 屏幕尺寸:屏幕对角线的长度,单位是英寸,1英寸=2.54厘米;
  • 屏幕分辨率:指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素,如1960 * 1080;
  • px(pixel):像素
  • dpi(density per inch):屏幕像素密度,每英寸的像素点数,mdpi为160dpi密度,此时1dp=1px;
  • dp/dip(Density Independent Pixels):密度无关像素;
  • sp(scale-independent pixels):与dp类似,但是可以根据文字大小首选项进行缩放,是设置字体大小的御用单位;

image
image
image

Android适配机制:

  • drawable文件夹:先找后缀与当前设备对应的文件夹,找不到去更高一级找,还找不到再去再高一级找,最后还找不到再去更低一级找;
  • layout文件夹:找与设备对应的目录,找不到则从比设备分辨率低一级的目录开始依次往下找;
  • values文件夹:找与设备对应的目录,找不到则从比设备分辨率低一级的目录开始依次往下找,最后去默认values找,找不到就报错;

适配方案:

开启手机热点

开启手机热点在Android各版本系统是有区别的:

8.0之前:

/**
 * android 8.0前 创建Wifi热点
 */
public boolean createWifiApBeforeAndroidO(Context context, String ssid, String psk) {
	WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
	//如果wifi处于打开状态,则关闭wifi,
	if (wifiManager.isWifiEnabled()) {
		wifiManager.setWifiEnabled(false);
	}
	if (isWifiApEnable(context)) {
		closeWifiAp(context);
	}

	WifiConfiguration config = new WifiConfiguration();
	if (TextUtils.isEmpty(psk)) {
		config.SSID = ssid;
		config.hiddenSSID = false;
		config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
	} else {
		config.SSID = ssid;
		config.hiddenSSID = false;
		config.preSharedKey = psk;
		config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);//开放系统认证
		config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
		config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
		config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
		config.status = WifiConfiguration.Status.ENABLED;
	}
	try {
		//通过反射调用设置热点
		Method method = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
		boolean result = (Boolean) method.invoke(wifiManager, config, true);
		return result;
	} catch (Exception e) {
		CatchUtil.handle(e);
		return false;
	}
}

/**
 * 8.0前 关闭WiFi热点
 */
public boolean closeWifiAp(Context context) {
	try {
		WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");
		method.setAccessible(true);
		WifiConfiguration config = (WifiConfiguration) method.invoke(wifiManager);
		Method method2 = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
		boolean result = (Boolean) method2.invoke(wifiManager, config, false);
		return result;
	} catch (Exception e) {
		CatchUtil.handle(e);
		return false;
	}
}

8.0之后:

/**
 * Android 8.0以上开启热点
 *
 * @param context
 */
public void openApAfterAndroidO(Context context) {
	try {
		WifiUtil.getWifiManager(context).startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {

			@Override
			public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
				mReservantion = reservation;
				String ssid = reservation.getWifiConfiguration().SSID;
				String psk = reservation.getWifiConfiguration().preSharedKey;
				Log.d(TAG, "onStarted: " + ssid + "   psk:" + psk);
				RxBus.get().post(new RxApInfo(ssid, psk));
			}

			@Override
			public void onStopped() {
				super.onStopped();
				Log.d(TAG, "onStopped: ");
			}

			@Override
			public void onFailed(int reason) {
				super.onFailed(reason);
				Log.d(TAG, "onFailed: " + reason);
			}
		}, new Handler());
	} catch (Exception e) {
		e.printStackTrace();
	}
}

/**
 * Android 8.0以上关闭热点
 *
 * @param context
 */
public void closeWifiApAfterAndroidO(Context context) {
	try {
		WifiManager wifiManager = WifiUtil.getWifiManager(context);
		//如果wifi处于打开状态,则关闭wifi,
		if (wifiManager.isWifiEnabled()) {
			wifiManager.setWifiEnabled(false);
		}
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			if (mReservantion != null) {
				mReservantion.close();
				mReservantion = null;
			}
		}
		Method method = wifiManager.getClass().getMethod("cancelLocalOnlyHotspotRequest");
		method.setAccessible(true);
		method.invoke(wifiManager);
		System.out.println("已关闭热点");
	} catch (NoSuchMethodException e) {
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	} catch (InvocationTargetException e) {
		e.printStackTrace();
	}
}

权限:

 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

//WRITE_SETTINGS需跳转界面用户手动打开:
<uses-permission
        android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />

/**
 * 请求权限
 */
private boolean checkApPremission() {
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
		if (Settings.System.canWrite(this)) {
			return true;
		} else {
			return false;
		}
	} else {
		return true;
	}
}

//请求write_settings权限
public static void goManageWriteSettings(Activity context) {
	Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + context.getPackageName()));
	intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
	context.startActivity(intent);
}

//开启热点
if (checkApPremission()) {
	//todo 开启热点
	
} else {
	goManageWriteSettings(this);
}

Nv21转Bitmap(高效率转化)

前言

在调Camera的时候有个回调方法onPreviewFrame是返回摄像头每一帧的图像数据的,当我们需要对图像数据做处理时就需要Nv21转Bitmap,下面介绍两种方式第一种方式只需要几毫秒时间,第二种方式需要几十毫秒。

第一种方式(高效,推荐)


import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;

public class NV21ToBitmap {

    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private Type.Builder yuvType, rgbaType;
    private Allocation in, out;

    public NV21ToBitmap(Context context) {
        rs = RenderScript.create(context);
        yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    }

    public Bitmap nv21ToBitmap(byte[] nv21, int width, int height){
        if (yuvType == null){
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }

        in.copyFrom(nv21);

        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);

        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        out.copyTo(bmpout);

        return bmpout;

    }

}

第二种方式(时间久了会内存泄漏)

private static Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {
    Bitmap bitmap = null;
    try {
        YuvImage image = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
        bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
        stream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bitmap;
}

参考:
https://blog.csdn.net/bluegodisplay/article/details/53431798
https://blog.csdn.net/qq1137830424/article/details/81980673

韦根(Wiegand)协议

概念:

Wiegand协议是国际上统一的标准,是由摩托罗拉公司制定的一种通讯协议。它适用于涉及门禁控制系统的读卡器和卡片的许多特性。 它有很多格式,标准的26-bit 应该是最常用的格式。此外,还有34-bit 、37-bit 等格式。 而标准26-bit 格式是一个开放式的格式,这就意味着任何人都可以购买某一特定格式的IC卡,并且这些特定格式的种类是公开可选的。26-Bit格式就是一个广泛使用的工业标准,并且对所有IC卡的用户开放。几乎所有的门禁控制系统都接受标准的26-Bit格式。

分类:

image
image

  • 韦根26:输出数据为3个字节,上图数据输出为:9602D2;
  • 韦根34:输出数据为4个字节,上图数据输出为:499602D2;
  • 韦根37:输出数据为4个字节,上图数据输出为:499602D2;

apk静默安装

参考:

示例代码:

    //静默安装
    private void installSlient() {
        String cmd = "pm install -r /mnt/sdcard/test.apk";
        Process process = null;
        DataOutputStream os = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = null;
        StringBuilder errorMsg = null;
        try {
            //静默安装需要root权限
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.write(cmd.getBytes());
            os.writeBytes("\n");
            os.writeBytes("exit\n");
            os.flush();
            //执行命令
            process.waitFor();
            //获取返回结果
            successMsg = new StringBuilder();
            errorMsg = new StringBuilder();
            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
            errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String s;
            while ((s = successResult.readLine()) != null) {
                successMsg.append(s);
            }
            while ((s = errorResult.readLine()) != null) {
                errorMsg.append(s);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (process != null) {
                    process.destroy();
                }
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //显示结果
        tvTest.setText("成功消息:" + successMsg.toString() + "\n" + "错误消息: " + errorMsg.toString());
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.