Android调用系统相机拍照

在这次的开发中用到了拍照上传图片的功能,还好Android中调用系统相机很方便,代码如下:

cameraBtn.setOnClickListener(new OnClickListener() {
	public void onClick() {
		Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
		startActivityForResult(intent, TAKE_PHOTO_REQUEST_CODE);
    }
});

获取拍照后图片数据,代码如下:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
		case TAKE_PHOTO_REQUEST_CODE:
			if(data!=null){
                Bundle extras = data.getExtras();
                Bitmap bmp = (Bitmap) extras.get("data");
        
                imageView.setImageBitmap(bmp);  //设置照片现实在界面上
            }  
			break;
		}
	}

此时便遇到了问题,测试发现不同手机调用系统相机拍照时,有些并不会把照片存储起来,导致返回时无法获取照片数据,使用Log打印数据之后发现Bitmap bmp = (Bitmap) extras.get(“data”); 处出现异常。Android的兼容性真是一个头大的难题,但是问题依然是要解决,google了有了解决方案。就是拍照时会把数据指定存储在SDcard上,然后读取SDcard上的图片数据显示在ImageView上。下面就看下解决问题的代码:

cameraBtn.setOnClickListener(new OnClickListener() {
	public void onClick() {
		if (!isHasSdCard()) {
			Helper.showToast(ctx, R.string.sdcard_no_avaiable);
			return;
		}

		Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
		// 指定存储照片的路径
		Uri imageUri = Uri.fromFile(getTempImage());
		intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
		startActivityForResult(intent, TAKE_PHOTO_REQUEST_CODE);
    }
});

上面代码会指定调用系统相机拍照时图片的存储路径,其中指定了一个临时文件temp.jpg来存储。

public static File getTempImage() {
	if (android.os.Environment.getExternalStorageState().equals(
			android.os.Environment.MEDIA_MOUNTED)) {
		File tempFile = new File(Environment.getExternalStorageDirectory(), "temp.jpg");
		try {
			tempFile.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return tempFile;
	}
	return null;
}

这时的onActivityResult的方法是下面这样的:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
	switch (requestCode) {
	case TAKE_PHOTO:
		if (resultCode == RESULT_OK) {
			Bitmap bmp = BitmapFactory.decodeFile(getTempImage().getPath());
			if (null != bmp) {
				postImage.setImageBitmap(bmp);
			}
		}
		break;
	}
}

兼容性问题终于解决了,不过不幸的是新的问题又出现了。

由于目前Android手机的相机像素普遍已经比较高了,拍出来的照片可能都会达到1-2M,把这么大的一张图片加载进内存里肯定会报出OOM错误。即:

Bitmap bmp = BitmapFactory.decodeFile(getTempImage().getPath());

这行代码会报出内存不足的错误。所以解决方案是在拍照后返回的bitmap必须要经过压缩,然后加载进内存从而渲染出来才不会报出内存不足的错误。下面就看代码:

public static Bitmap getScaleBitmap(Context ctx, String filePath) {
	BitmapFactory.Options opt = new BitmapFactory.Options();
	opt.inJustDecodeBounds = true;
	Bitmap bmp = BitmapFactory.decodeFile(filePath, opt);

	int bmpWidth = opt.outWidth;
	int bmpHeght = opt.outHeight;

	WindowManager windowManager = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
	Display display = windowManager.getDefaultDisplay();
	int screenWidth = display.getWidth();
	int screenHeight = display.getHeight();

	opt.inSampleSize = 1;
	if (bmpWidth > bmpHeght) {
		if (bmpWidth > screenWidth)
			opt.inSampleSize = bmpWidth / screenWidth;
	} else {
		if (bmpHeght > screenHeight)
			opt.inSampleSize = bmpHeght / screenHeight;
	}
	opt.inJustDecodeBounds = false;

	bmp = BitmapFactory.decodeFile(filePath, opt);
	return bmp;
	}

上述代码的关键在于inJustDecodeBounds属性。如果设置inJustDecodeBounds为true,仍可以获取到bitmap信息,但完全不用分配内存,因为没有获取像素,所以我们可以利用得到的Bitmap的大小,重新压缩图片,然后在内存中生成一个更小的Bitmap,这样即便是一个4MB的JPG,我们也可以随心所欲地把他压缩到任意大小,从而节省了内存。

下面看下更改后的代码:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
	switch (requestCode) {
	case TAKE_PHOTO:
		if (resultCode == RESULT_OK) {
			Bitmap bmp = getScaleBitmap(ctx, getTempImage().getPath());
			if (null != bmp) {
				postImage.setImageBitmap(bmp);
			}
		}
		break;
	}
}

多个github帐号的SSH key切换

背景

今天给我女朋友弄了个github账号,由于准备全部在一台mbp上管理,当准备给新的github账号添加ssh key的时候提示我该ssh key已经使用过了,因为我之前已经把该ssh key添加到我的github账号上了。

原因

github使用SSH与客户端连接。如果是单用户(first),生成密钥对后,将公钥保存至github,每次连接时SSH客户端发送本地私钥(默认~/.ssh/id_rsa)到服务端验证。单用户情况下,连接的服务器上保存的公钥和发送的私钥自然是配对的。但是如果是多用户(first,second),我们在连接到second的帐号时,second保存的是自己的公钥,但是SSH客户端依然发送默认私钥,即first的私钥,那么这个验证自然无法通过。不过,要实现多帐号下的SSH key切换在客户端做一些配置即可。

解决方案

首先,在新增私钥的时候,通过指定不同的文件名来生成不同的私钥文件

ssh-keygen -t rsa -f ~/.ssh/id_rsa_second -C "second@mail.com"

默认SSH只会读取id_rsa,所以为了让SSH识别新的私钥,需要将其添加到SSH agent

ssh-add ~/.ssh/id_rsa_second

该命令如果报错:Could not open a connection to your authentication agent.无法连接到ssh agent,可执行ssh-agent bash命令后再执行ssh-add命令。

完成以上步骤后在~/.ssh目录创建config文件,该文件用于配置私钥对应的服务器。内容如下:

# first.github (first@gmail.com)
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa

# second (second@gmail.com)
Host github-second
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_second

Host随意即可,方便自己记忆,后续在添加remote是还需要用到。

配置完成后,在连接非默认帐号的github仓库时,远程库的地址要对应地做一些修改,比如现在添加second帐号下的一个仓库origin,则需要这样添加:

git remote add origin git@github-second:second/test.git
# 并非原来的git remote add origin git@github.com:second/test.git

这样每次连接都会使用id_rsa_second与服务器进行连接。至此,大功告成!

注意:github根据配置文件的user.email来获取github帐号显示author信息,所以对于多帐号用户一定要记得将user.email改为相应的email(second@mail.com)。

参考github帮助文档:


Android生成唯一标识符UUID

在这次新版本的app开发中,其中增加了游客账号访问,但需要为该游客生成一个唯一标识符identity,最初考虑通过mac地址或者imei来唯一标识,但是后面有一项需求,游客账号可以升级,升级之后退出重新游客访问的话就需要生成新的identity,这样一来就必须手动生成唯一的identity了,好在java提供了生成UUID唯一标示符。

java.util.UUID.randomUUID().toString();

如果未升级的游客账号退出重新登录这个时候再重新访问还是用原来的identity,解决方法也很简单,把第一次生成的identity存起来,下次就直接取值了,看下代码吧:

public String getIdentity() {
    SharedPreferences preference = PreferenceManager.getDefaultSharedPreferences(context);
    String identity = preference.getString("identity", null);
    if (identity == null) {
        identity = java.util.UUID.randomUUID().toString();
        preference.edit().putString("identity", identity);
    }
    return identity;
}

然后只需要在升级成功后把identity remove掉就ok。


小米2S开启USB调试模式

拿了同事的新买的小米2S来调试应用程序,没想到已经在设置->开发者选项里已经打开了“USB调试模式”,但是不管是在eclipse的DDMS上还是在命令行下输入adb devices都找不到设备,最后google了才知道原来小米2s提供了另一种方法来开启USB模式,故记录在此:

1.首先在【设置】→【全部设置】→【开发者选项】→【调试】下开启【USB 调试】模式 2.然后打开手机拨号界面,在拨号界面按

*#*#717717#*#*

即可使 USB 接口生效,设置成功后会在当前界面弹出吐司 Diag USB port enable。

设置成功后即可查看设备的信息了,


Android自定义点击效果的ImageView

我们知道在Android开发中一些可以点击的Button或者ImageView一般都会有一些特效,这样的设计比较友好,让用户确切的知道那个组件有没有成功点击。最简单最常用的办法就是设计两套背景图片,然后给Button或者ImageView设置一个xml的selector,从而达到这样的效果。但是如果整个应用下来每个可点击的组件都需要准备两张图片,未免有些太麻烦,而且一般的点击效果只是让透明度有些变化而已。那么针对只是透明度变化的点击效果,有没有可能自定义一个组件呢,从而达到方便重用的目的。带着整个想法,最后终于找到一个比较好的解决方案。废话不多说,见代码:

public class AutoBgImageView extends ImageView {

	public AutoBgImageView(Context context) {
		super(context);
	}

	public AutoBgImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

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

	@Override
	public void setBackgroundDrawable(Drawable d) {
		// Replace the original background drawable (e.g. image) with a
		// LayerDrawable that
		// contains the original drawable.
		SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d);
		super.setBackgroundDrawable(layer);
	}

	protected class SAutoBgButtonBackgroundDrawable extends LayerDrawable {

		// The color filter to apply when the button is pressed
		protected ColorFilter _pressedFilter = new LightingColorFilter(Color.LTGRAY, 1);
		// Alpha value when the button is disabled
		protected int _disabledAlpha = 100;

		public SAutoBgButtonBackgroundDrawable(Drawable d) {
			super(new Drawable[] { d });
		}

		@Override
		protected boolean onStateChange(int[] states) {
			boolean enabled = false;
			boolean pressed = false;

			for (int state : states) {
				if (state == android.R.attr.state_enabled)
					enabled = true;
				else if (state == android.R.attr.state_pressed)
					pressed = true;
			}

			mutate();
			if (enabled && pressed) {
				setColorFilter(_pressedFilter);
			} else if (!enabled) {
				setColorFilter(null);
				setAlpha(_disabledAlpha);
			} else {
				setColorFilter(null);
			}

			invalidateSelf();

			return super.onStateChange(states);
		}

		@Override
		public boolean isStateful() {
			return true;
		}
	}
}

上面代码定义了一个AutoBgImageView组件继承自ImageView,重写了ImageView的setBackgroundDrawable()方法,从而达到点击ImageView的时候透明度为100(最大为255)。这样整个系统只要是可点击的ImageView只需要用整个组件代替即可,非常方便。上述代码同样可以自定义Button达到类似的点击效果。