• Android 自定义拍照,解决图片旋转,拍照参数设置兼容问题


      今天客户提了一个需求: 需要结合摄像头将拍照的图片和自己的产品(图片)整合到一张图片上面,如下图:  
     
    20150908143828051.jpg
     
    针对这个功能需要做自定义相机,根据Camera相机类和SurfaceView类来实现自定义图形预览拍照功能。
    但在实现过程中出现几个难点:
    1.如何将自己产品图片(上图的台灯)和摄像头预览的图片结合成一张图片。
    2.拍照的图片在有些手机上面出现旋转了90度的情况(Android兼容性问题)。
    3.某些手机会出现在camera.setParameters(parameters)的调用时候出现设置失败的异常java.lang.RuntimeException: setParameters failed
     
    针对第一个问题:
        开始的考虑是在布局中SurfaceView上加一层ImageView来显示产品图片,然后通过截取View的方法截取SurfaceView和ImageView的父布局来实现,但截取view的时候发现只能截取静态的方法,对于像surfaceview之类的动态的view是不能够截取的(具体的截取方法请参考下面的源码),后来才有的是绘图的方式将预览图片的bitmap和产品图片的bitmap绘制到一张图片实现图片的合并(具体方法请参考下面源码)。
     
    针对第二个问题:
        这个显然是android手机的兼容性问题,网上也有很大类似的网友提出此类问题,但给出的解决方法大都是先通过ExifInterface类来读取bitmap的旋转角度,然后根据旋转角度旋转bitmap实现,我也用这个方法检验过,但实际上此方法并不靠谱,原因是读取bitmap的角度始终为0,即使我图片在拍照后出现的旋转了90度,也是读取不了的。后来找一篇文章:http://www.xuebuyuan.com/1223069.html 该文章介绍了ExifInterface类在某些机型的有兼容性问题,并指出了问题产生的原因,我采用,拍照完成后,强制调用setPictureDegreeZero方法(具体实现参考下面源码)来重新将图片的旋转角度设置为0,终于以次方解决了问题。终于松了口气,啊哈哈。
     
     
    针对第三个问题: 
      某些机型出现了设置拍照参数失败,是因为某些参数设置对应该机型不支持,这里我需要做到的是需要确认哪些参数有兼容性问题的,经测试发现,
    // parameters.setPreviewFrameRate(3);// 每秒3帧 每秒从摄像头里面获得3个画面, 此参数在小米2上面支持,在红米note2上面不支持,
    // parameters.setPreviewSize(PreviewWidth,PreviewHeight);// 获得摄像区域的大小
    // parameters.setPictureSize(PreviewWidth,PreviewHeight);// 获得保存图片的大小 ----这个两个参数也会有兼容性问题。
     
        目前发现这里3个参数有兼容性问题,其它若有发现在补充,同时如果网友们发现了其它参数存在兼容性问题也烦请指出,谢谢。
    针对不兼容性问题,尽量避免去设置它,
    对应setPreviewSize和setPictureSize可以通过以下方式取得合适的预览尺寸进行设置,具体方法请参考下面源代码。
    // 选择合适的预览尺寸List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
     
     
    具体实现代码:
    1. package com.example.eclipsetest;
      
      import java.io.BufferedOutputStream;
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.lang.reflect.Method;
      import java.util.Iterator;
      import java.util.List;
      
      import android.app.Activity;
      import android.content.Context;
      import android.graphics.Bitmap;
      import android.graphics.Bitmap.Config;
      import android.graphics.BitmapFactory;
      import android.graphics.Canvas;
      import android.graphics.Matrix;
      import android.graphics.PixelFormat;
      import android.graphics.drawable.Drawable;
      import android.hardware.Camera;
      import android.hardware.Camera.AutoFocusCallback;
      import android.hardware.Camera.Parameters;
      import android.hardware.Camera.PictureCallback;
      import android.hardware.Camera.ShutterCallback;
      import android.hardware.Camera.Size;
      import android.media.ExifInterface;
      import android.os.Build;
      import android.os.Bundle;
      import android.os.Environment;
      import android.util.DisplayMetrics;
      import android.util.Log;
      import android.view.Display;
      import android.view.Gravity;
      import android.view.SurfaceHolder;
      import android.view.SurfaceView;
      import android.view.View;
      import android.view.View.MeasureSpec;
      import android.view.View.OnClickListener;
      import android.view.WindowManager;
      import android.widget.Button;
      import android.widget.FrameLayout;
      import android.widget.ImageView;
      import android.widget.ImageView.ScaleType;
      import android.widget.Toast;
      
      /**
       * 自定义拍照,将特定图片添加到预览图片中保存起来
       * 
       * @ClassName: MyCameraDemo
       * @Description:
       * @author xiaoxiao
       * @date modify by 2015-9-8 下午2:09:16
       * 
       */
      public class MyCameraDemo extends Activity {
      	private SurfaceView surface = null;
      	private Button but = null;
      	private SurfaceHolder holder = null;
      	private Camera cam = null;
      	private boolean previewRunning = true;
      	private Button but2;
      	private ImageView iv_img;
      	private FrameLayout flay_view;
      
      	@Override
      	public void onCreate(Bundle savedInstanceState) {
      		super.onCreate(savedInstanceState);
      		super.setContentView(R.layout.main);
      		this.but = (Button) super.findViewById(R.id.but);
      		this.but2 = (Button) super.findViewById(R.id.but2);
      		this.surface = (SurfaceView) super.findViewById(R.id.surface);
      		iv_img = (ImageView) findViewById(R.id.iv_img);
      		flay_view = (FrameLayout) findViewById(R.id.flay_view);
      		this.holder = this.surface.getHolder();
      		this.holder.addCallback(new MySurfaceViewCallback());
      		this.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
      		this.holder.setFixedSize(500, 350);
      		this.but.setOnClickListener(new OnClickListenerImpl());
      		but2.setOnClickListener(new OnClickListener() {
      
      			@Override
      			public void onClick(View v) {
      				iv_img.setVisibility(View.VISIBLE);
      				iv_img.setImageResource(R.drawable.taideng);
      			}
      		});
      	}
      
      	private class OnClickListenerImpl implements OnClickListener {
      
      		@Override
      		public void onClick(View v) {
      			if (cam != null) {
      				cam.autoFocus(new AutoFocusCallbackImpl());
      			}
      		}
      
      	}
      
      	private class MySurfaceViewCallback implements SurfaceHolder.Callback {
      
      		@Override
      		public void surfaceChanged(SurfaceHolder holder, int format, int width,
      				int height) {
      
      		}
      
      		@Override
      		public void surfaceCreated(SurfaceHolder holder) {
      			if (cam != null) {
      				cam.stopPreview();// 停掉原来摄像头的预览
      				cam.release();// 释放资源
      				cam = null;// 取消原来摄像头
      			}
      			try {
      				cam = Camera.open(0); // 取得第一个摄像头
      			} catch (Exception e) {
      				// TODO: handle exception
      				Toast.makeText(MyCameraDemo.this, "摄像头打开失败", 0).show();
      				return;
      			}
      			cam = deal2(cam);
      		}
      
      		@Override
      		public void surfaceDestroyed(SurfaceHolder holder) {
      			if (cam != null) {
      				if (MyCameraDemo.this.previewRunning) {
      					cam.stopPreview(); // 停止预览
      					MyCameraDemo.this.previewRunning = false;
      				}
      				cam.stopPreview();
      				cam.release();
      				cam = null;
      			}
      		}
      
      	}
      
      	private class AutoFocusCallbackImpl implements AutoFocusCallback {
      
      		@Override
      		public void onAutoFocus(boolean success, Camera camera) {
      			if (success) { // 成功
      				cam.takePicture(sc, pc, jpgcall);
      			}
      		}
      
      	}
      
      	private PictureCallback jpgcall = new PictureCallback() {
      
      		@Override
      		public void onPictureTaken(byte[] data, Camera camera) { // 保存图片的操作
      			Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
      			// Bitmap bmp2 = new BitmapDrawable(iv_img).getDrawable();
      			// Bitmap bmp2 = BitmapFactory.decodeResource(getResources(),
      			// R.drawable.taideng);
      			// Bitmap bmp2 = drawableToBitamp(iv_img.getDrawable());
      			String fileName = "test_" + System.currentTimeMillis() + ".jpg";
      			String filePath = Environment.getExternalStorageDirectory()
      					.toString()
      					+ File.separator
      					+ "xiao"
      					+ File.separator
      					+ fileName;
      			bmp = rotateBitmapByDegree(bmp, 90);
      
      			save(bmp, filePath, fileName);
      			bmp = loadBitmap(filePath, true);
      			setPictureDegreeZero(filePath);
      
      			Bitmap bmp2 = getSmallBitmap(MyCameraDemo.this,
      					R.drawable.taideng, dip2px(MyCameraDemo.this, 200),
      					dip2px(MyCameraDemo.this, 200));
      			Bitmap bmp3 = combineBitmap(bmp, bmp2);
      			save(bmp3, filePath, fileName);
      			cam.stopPreview();
      			cam.startPreview();
      		}
      
      	};
      
      	private ShutterCallback sc = new ShutterCallback() {
      		@Override
      		public void onShutter() {
      			// 按下快门之后进行的操作
      		}
      	};
      	private PictureCallback pc = new PictureCallback() {
      
      		@Override
      		public void onPictureTaken(byte[] data, Camera camera) {
      
      		}
      
      	};
      
      	/**
      	 * 开始考虑用剪切的方法,但是截取只适合静态界面,这里surfaceView是动态的(在不断重绘)不能剪切,后来考虑用绘图的方式将两个bitmap合在一起。
      	 * 
      	 * @param view
      	 * @return
      	 */
      	private Bitmap cropView(View view) {
      		view.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.UNSPECIFIED),
      				MeasureSpec.makeMeasureSpec(100, MeasureSpec.UNSPECIFIED));
      		view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
      		view.buildDrawingCache();
      		Bitmap bitmap = view.getDrawingCache();
      		return bitmap;
      	}
      
      	
      	/**
      	 * 合并两张bitmap为一张
      	 * 
      	 * @param background
      	 * @param foreground
      	 * @return Bitmap
      	 */
      	public static Bitmap combineBitmap(Bitmap background, Bitmap foreground) {
      		if (background == null) {
      			return null;
      		}
      		int bgWidth = background.getWidth();
      		int bgHeight = background.getHeight();
      		int fgWidth = foreground.getWidth();
      		int fgHeight = foreground.getHeight();
      		Bitmap newmap = Bitmap
      				.createBitmap(bgWidth, bgHeight, Config.ARGB_8888);
      		Canvas canvas = new Canvas(newmap);
      		canvas.drawBitmap(background, 0, 0, null);
      		canvas.drawBitmap(foreground, (bgWidth - fgWidth) / 2,
      				(bgHeight - fgHeight) / 2, null);
      		canvas.save(Canvas.ALL_SAVE_FLAG);
      		canvas.restore();
      		return newmap;
      	}
      
      	private void save(Bitmap bitmap, String filePath, String fileName) {
      		File file = new File(filePath);
      		if (!file.getParentFile().exists()) {
      			file.getParentFile().mkdirs(); // 创建文件夹
      		}
      		try {
      			BufferedOutputStream bos = new BufferedOutputStream(
      					new FileOutputStream(file));
      			bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos); // 向缓冲区之中压缩图片
      			bos.flush();
      			bos.close();
      			Toast.makeText(MyCameraDemo.this,
      					"拍照成功,照片已保存在" + fileName + "文件之中!", Toast.LENGTH_SHORT)
      					.show();
      		} catch (Exception e) {
      			Toast.makeText(MyCameraDemo.this, "拍照失败!", Toast.LENGTH_SHORT)
      					.show();
      		}
      	}
      
      	private Bitmap bitmap;
      
      	private Bitmap drawableToBitamp(Drawable drawable) {
      		int w = drawable.getIntrinsicWidth();
      		int h = drawable.getIntrinsicHeight();
      		System.out.println("Drawable转Bitmap");
      		Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
      				: Bitmap.Config.RGB_565;
      		bitmap = Bitmap.createBitmap(w, h, config);
      		// 注意,下面三行代码要用到,否在在View或者surfaceview里的canvas.drawBitmap会看不到图
      		Canvas canvas = new Canvas(bitmap);
      		drawable.setBounds(0, 0, w, h);
      		drawable.draw(canvas);
      		return bitmap;
      	}
      
      	/**
      	 * 读取图片的旋转的角度, 某些机型此方法无效
      	 * 
      	 * @param path
      	 *            图片绝对路径
      	 * @return 图片的旋转角度
      	 */
      	private int getBitmapDegree(String path) {
      		int degree = 0;
      		try {
      			// 从指定路径下读取图片,并获取其EXIF信息
      			ExifInterface exifInterface = new ExifInterface(path);
      			// 获取图片的旋转信息
      			int orientation = exifInterface.getAttributeInt(
      					ExifInterface.TAG_ORIENTATION,
      					ExifInterface.ORIENTATION_NORMAL);
      			switch (orientation) {
      			case ExifInterface.ORIENTATION_ROTATE_90:
      				degree = 90;
      				break;
      			case ExifInterface.ORIENTATION_ROTATE_180:
      				degree = 180;
      				break;
      			case ExifInterface.ORIENTATION_ROTATE_270:
      				degree = 270;
      				break;
      			}
      		} catch (IOException e) {
      			e.printStackTrace();
      		}
      		return degree;
      	}
      
      	/**
      	 * 将图片按照某个角度进行旋转
      	 * 
      	 * @param bm
      	 *            需要旋转的图片
      	 * @param degree
      	 *            旋转角度
      	 * @return 旋转后的图片
      	 */
      	public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
      		Bitmap returnBm = null;
      
      		// 根据旋转角度,生成旋转矩阵
      		Matrix matrix = new Matrix();
      		matrix.postRotate(degree);
      		try {
      			// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
      			returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),
      					bm.getHeight(), matrix, true);
      		} catch (OutOfMemoryError e) {
      		}
      		if (returnBm == null) {
      			returnBm = bm;
      		}
      		if (bm != returnBm) {
      			bm.recycle();
      		}
      		return returnBm;
      	}
      
      	// 控制图像的正确显示方向
      	private void setDispaly(Camera.Parameters parameters, Camera camera) {
      		if (Integer.parseInt(Build.VERSION.SDK) >= 8) {
      			setDisplayOrientation(camera, 90);
      		} else {
      			parameters.setRotation(90);
      		}
      
      	}
      
      	// 实现的图像的正确显示
      	private void setDisplayOrientation(Camera camera, int i) {
      		Method downPolymorphic;
      		try {
      			downPolymorphic = camera.getClass().getMethod(
      					"setDisplayOrientation", new Class[] { int.class });
      			if (downPolymorphic != null) {
      				downPolymorphic.invoke(camera, new Object[] { i });
      			}
      		} catch (Exception e) {
      			Log.e("Came_e", "图像出错");
      		}
      	}
      
      	private Camera deal2(Camera mCamera) {
      		// 设置camera预览的角度,因为默认图片是倾斜90度的
      		// mCamera.setDisplayOrientation(90);
      
      		int PreviewWidth = 0;
      		int PreviewHeight = 0;
      		WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);// 获取窗口的管理器
      		Display display = wm.getDefaultDisplay();// 获得窗口里面的屏幕
      		Camera.Parameters parameters = mCamera.getParameters();
      		// parameters.setFlashMode(Parameters.FLASH_MODE_TORCH); //开启闪光灯,支持
      		setDispaly(parameters, mCamera);
      		// parameters.setRotation(90);
      		// parameters.setPreviewFrameRate(3);// 每秒3帧 每秒从摄像头里面获得3个画面,
      		// 某些机型(红米note2)不支持
      		parameters.setPictureFormat(PixelFormat.JPEG);// 设置照片输出的格式
      		parameters.set("jpeg-quality", 100);// 设置照片质量
      		try {
      			// 选择合适的预览尺寸
      			List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
      			// 如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择
      			if (sizeList.size() > 1) {
      				Iterator<Camera.Size> itor = sizeList.iterator();
      				while (itor.hasNext()) {
      					Camera.Size cur = itor.next();
      					if (cur.width >= PreviewWidth
      							&& cur.height >= PreviewHeight) {
      						PreviewWidth = cur.width;
      						PreviewHeight = cur.height;
      						break;
      					}
      				}
      			}
      			parameters.setPreviewSize(PreviewWidth, PreviewHeight); // 获得摄像区域的大小
      			parameters.setPictureSize(PreviewWidth, PreviewHeight); // 获得保存图片的大小
      			// parameters.setPreviewSize(display.getWidth(),
      			// display.getWidth()); // 获得摄像区域的大小
      			// parameters.setPictureSize(display.getWidth(),
      			// display.getWidth());// 设置拍出来的屏幕大小
      
      		} catch (Exception e) {
      			Log.e("MyCameraDemo", e.toString());
      		}
      		try {
      			cam.setPreviewDisplay(MyCameraDemo.this.holder);
      		} catch (IOException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		}
      		mCamera.setParameters(parameters);// 把上面的设置 赋给摄像头
      		mCamera.startPreview();// 开始预览
      		mCamera.cancelAutoFocus();// 2如果要实现连续的自动对焦,这一句必须加上
      		previewRunning = true;
      		return mCamera;
      	}
      
      	/** 从给定路径加载图片 */
      	public Bitmap loadBitmap(String imgpath) {
      		return BitmapFactory.decodeFile(imgpath);
      	}
      
      	/** 从给定的路径加载图片,并指定是否自动旋转方向 */
      	public Bitmap loadBitmap(String imgpath, boolean adjustOritation) {
      		if (!adjustOritation) {
      			return loadBitmap(imgpath);
      		} else {
      			Bitmap bm = loadBitmap(imgpath);
      			int digree = 0;
      			ExifInterface exif = null;
      			try {
      				exif = new ExifInterface(imgpath);
      			} catch (IOException e) {
      				e.printStackTrace();
      				exif = null;
      			}
      			if (exif != null) {
      				// 读取图片中相机方向信息
      				// int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
      				// ExifInterface.ORIENTATION_NORMAL);
      				int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
      						ExifInterface.ORIENTATION_FLIP_VERTICAL);
      				// 计算旋转角度
      				switch (ori) {
      				case ExifInterface.ORIENTATION_ROTATE_90:
      					digree = 90;
      					break;
      				case ExifInterface.ORIENTATION_ROTATE_180:
      					digree = 180;
      					break;
      				case ExifInterface.ORIENTATION_ROTATE_270:
      					digree = 270;
      					break;
      				default:
      					digree = 0;
      					break;
      				}
      			}
      			if (digree != 0) {
      				// 旋转图片
      				Matrix m = new Matrix();
      				m.postRotate(digree);
      				bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),
      						bm.getHeight(), m, true);
      			}
      			return bm;
      		}
      	}
      
      	/**
      	 * 将图片的旋转角度置为0  ,此方法可以解决某些机型拍照后图像,出现了旋转情况
      	 * 
      	 * @Title: setPictureDegreeZero
      	 * @param path
      	 * @return void
      	 * @date 2012-12-10 上午10:54:46
      	 */
      	private void setPictureDegreeZero(String path) {
      		try {
      			ExifInterface exifInterface = new ExifInterface(path);
      			// 修正图片的旋转角度,设置其不旋转。这里也可以设置其旋转的角度,可以传值过去,
      			// 例如旋转90度,传值ExifInterface.ORIENTATION_ROTATE_90,需要将这个值转换为String类型的
      			exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, "no");
      			exifInterface.saveAttributes();
      		} catch (IOException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		}
      
      	}
      
      	/**
      	 * 根据路径获取图片并压缩返回bitmap用于显示
      	 * 
      	 * @param context
      	 * @param id
      	 * @return
      	 */
      
      	private  Bitmap getSmallBitmap(Context context, int id,int width,int height) {
      		final BitmapFactory.Options options = new BitmapFactory.Options();
      		options.inJustDecodeBounds = true;
      		BitmapFactory.decodeResource(context.getResources(), id, options);
      		// 计算 缩略图大小为原始图片大小的几分之一 inSampleSize:缩略图大小为原始图片大小的几分之一
      		options.inSampleSize = calculateInSampleSize(options, width, height);
      		options.inJustDecodeBounds = false;
      
      		return BitmapFactory.decodeResource(context.getResources(), id, options);
      	}
      	
      	/**
      	 * 计算图片的缩放值
      	 * 
      	 * @param options
      	 * @param reqWidth
      	 * @param reqHeight
      	 * @return
      	 */
      	private  int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
      		// Raw height and width of image
      		final int height = options.outHeight;
      		final int width = options.outWidth;
      		int inSampleSize = 1;
      
      		if (height > reqHeight || width > reqWidth) {
      			final int heightRatio = Math.round((float) height / (float) reqHeight);
      			final int widthRatio = Math.round((float) width / (float) reqWidth);
      			inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
      
      		}
      		return inSampleSize;
      
      	}
      	/**
      	 * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
      	 */
      	public static int dip2px(Context context, float dpValue) {
      		final float scale = context.getResources().getDisplayMetrics().density;
      		return (int) (dpValue * scale + 0.5f);
      	}
      
      	/**
      	 * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
      	 */
      	public static int px2dip(Context context, float pxValue) {
      		final float scale = context.getResources().getDisplayMetrics().density;
      		return (int) (pxValue / scale + 0.5f);
      	}
      }
      

        


     
    布局文件:
    1. <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >
          <FrameLayout
              android:id="@+id/flay_view"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:layout_weight="1000" >
              <SurfaceView
                  android:id="@+id/surface"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
                  android:layout_weight="1000" />
              <ImageView 
                  android:id="@+id/iv_img"
                  android:layout_width="200dp"
                  android:layout_height="200dp"
                  android:layout_gravity="center"
                  android:visibility="gone"
                  android:src="@drawable/taideng"
                  android:scaleType="fitCenter"/>
          </FrameLayout>
          <LinearLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:orientation="horizontal" >
              <Button
                  android:id="@+id/but"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_weight="1"
                  android:text="照相" />
              <Button
                  android:id="@+id/but2"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_weight="1"
                  android:text="添加图片" />
          </LinearLayout>
      </LinearLayout>  
      

        

     
    最后不要忘了权限:
        
    1. <uses-feature android:name="android.hardware.camera" />
          <uses-feature android:name="android.hardware.camera.autofocus" />
          <uses-permission android:name="android.permission.CAMERA" />
          <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
          <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
     
     



  • 相关阅读:
    Entity Framework版本历史概览
    读书笔记—CLR via C#章节3
    读书笔记—CLR via C#章节1-2
    C#编程实践–产假方案优化版
    老调重弹--面向对象设计原则--包设计原则
    Redis(七)分布式锁
    Redis(九)高可用专栏之Sentinel模式
    Redis(九)高可用专栏之《简介篇》
    Redis(七)持久化(Persistence)
    Redis(六)管道(Pipelining)
  • 原文地址:https://www.cnblogs.com/xiaoxiao-study/p/867d2ad9206c8600186c90690f1e7965.html
Copyright © 2020-2023  润新知