您的位置:首页 > 其它

关于地图拍照上传项目的一些总结

2011-12-22 14:59 162 查看
这个项目是为某个运动品牌商店定做,一开始就是十分吸引我的。因为它的功能点十分普遍,所以如果我完成了这个项目,自然会沉淀下来一些功能代码,项目框架,和相关的经验,以方便日后使用 。首先它是款地图应用,可以获得所有商店,在地图上以小图钉的方式呈现。然后要支持查找用户当前的位置,进入某个商店,查看里面的商店信息和店内的视频和照片,支持拍照,录制视频并上传,也可以进行评论。用户也可自己添加,编辑,删除商店。
功能点就是这些,算是个小项目,为期也就3周时间,但是由于一些新东西没有接触过,所以还是需要总结一下这次遇到的问题。

1.注册MAPKEY这个是众所周知的,可以理解为不同的开发电脑有不同的debug.keystore文件,所以需要对应不同的MAPKEY。 这只是限于开发,发布APK不会影响。

keytool -list -alias androiddebugkey -keystore C:\Documents and Settings\user\.android\debug.keystore

这个keytool 是java/bin环境下的

然后得到MD5值后再去http://code.google.com/android/maps-api-signup.html 点击打开链接验证获得KEY就行了

2.这次项目结构很清晰,吸取以往的经验,告别application这个类.之前的项目就是把很多需要传递的数据装在application里面,包括activity之间的数据传递,我都完全没有使用intent传递(觉得序列化很麻烦) 然后getApplication满天飞,数据管理起来非常混乱,各种未知的数据不同步的bug.这次发现Parcelable序列化接口蛮好用的

只需要实现以下几个方法就好,逻辑很清晰

@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
//把你想序列化的数据都写进去
dest.writeDouble(gp.getLatitudeE6());
dest.writeDouble(gp.getLongitudeE6());
dest.writeInt(isTemp ? 1 : 0);
dest.writeInt(isEdit ? 1 : 0);
dest.writeSerializable(storeBean);
}

public static final Parcelable.Creator<StoreOverlay> CREATOR = new Parcelable.Creator<StoreOverlay>() {
public StoreOverlay createFromParcel(Parcel in) {                        //根据你上面写的数据,再读出来重新生成,注意是新的对象生成,类似深度clone
double mLat = in.readDouble();
double mLon = in.readDouble();
boolean isTemp = in.readInt() == 1 ? true : false;
boolean isEdit = in.readInt() == 1 ? true : false;
StoreBean storeBean = (StoreBean) in.readSerializable();
GeoPoint gp = new GeoPoint((int) mLat, (int) mLon);
StoreOverlay storeOverlay = new StoreOverlay(gp, storeBean, isTemp);
storeOverlay.setEdit(isEdit);
return storeOverlay;
}

public StoreOverlay[] newArray(int size) {
return new StoreOverlay[size];
}
};


3.既然用到了intent传递数据,这次也用到一个十分方便的方法startActivityForResult().这样activity之间的数据交互就更方便了,比如A提交数据给B, 需要B做一些处理然后再告知A,以往我的做法不是发广播就是传handler回调,这些的可读性和管理都很糟糕 .而使用startActivityForResult启动B之后, 当B结束之后,只需要setResult 返回成功或者失败,并可将数据返回,当然记得调用finish().A这边才会从onActivityResult()方法中得到回调并获取数据.这样使你的代码结构非常清晰.

4.碰到这样一个需求 有A,B两个线程,A是获取所有的商店的一个网络请求,B是获得当前的经纬度 然后传给服务器得到附近最近的一家商店. 这个B请求一定是建立在A执行完毕之后才执行, 也许我们以往的写法就是A线程执行完毕后 然后在A线程里面发送message到handler(post到UI线程) 然后再调用B线程启动. 这样扩展性很糟糕,比如我有个刷新商店的功能,自然是启动A线程执行完毕就OK, 而现在还会导致B线程也启动. 所以我最后用到一个很不错的java的方法 就是Thread的join()方法.

这个join方法很绕不是很好理解.于是写了一个demo

CustomThread1 = new Thread(new Runnable() {

@Override
public void run() {

System.out.println("CustomThread1" + " start.");
try {
for (int i = 0; i < 4; i++) {
System.out.println("CustomThread1" + " loop at " + i);
Thread.sleep(1000);
}
System.out.println("CustomThread1" + " end.");
} catch (Exception e) {
e.printStackTrace();
}

}
});
CustomThread2 = new Thread(new Runnable() {

@Override
public void run() {
try {
CustomThread1.join();//这里线程2会一直等待线程1结束后才会执行
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("CustomThread2" + " start.");
try {
for (int i = 0; i < 4; i++) {
System.out.println("CustomThread2" + " loop at " + i);
Thread.sleep(1000);
}
System.out.println("CustomThread2" + " end.");
} catch (Exception e) {
e.printStackTrace();
}

}
});
CustomThread1.start();
CustomThread2.start();


所以你想B线程排在A线程后面执行,就要在B线程里面调用A.join(); 如果A没执行完,B会一直sleep等待

5.关于GPS定位, 我们都知道在室内GPS几乎是无法定位,这个时候我们就需要依赖基站定位. 所以室内是基站定位,室外是GPS定位.

这次定位我采用的是google现有的API, MyLocationOverlay这个类.你只需要

mylocOverlay = new MyLocationOverlay(this, mapView);
mylocOverlay.enableMyLocation();//允许定位
mylocOverlay.enableCompass();//打开指南针
mapListOverlay.add(mylocOverlay);//添加到mapview


就可以在你的地图里面显示一个蓝色的亮点,并且一直闪烁.这个MyLocationOverlay可以自动选择合适的定位方式 包括GPS定位和基站定位,当他定位成功你也可以通过他得到经度维度.你可以通过下面的方法,当他成功获得当前经纬度后,自动回调该方法,做一些你后续的工作.

mylocOverlay.runOnFirstFix(new Runnable() {
@Override
public void run() {
System.out.println("get my location !");
GeoPoint gp = mylocOverlay.getMyLocation();
updateMapView(gp);
b_mylocation.post(new Runnable() {
@Override
public void run() {
b_mylocation.setBackgroundResource(R.drawable.selector_aim);
}
});
}
});


不过MyLocationOverlay有个唯一的缺点,他会频繁的调用你所有的Overlay对象的onDraw()方法,我猜想是它需要一直更新当前的位置并刷新那个蓝点,这个问题我还无法解决,不过对我的功能不会有影响。

6.关于照片裁剪,用户拍得照片大小各异,然后可能是横着竖着的,所以需要裁剪,统一大小放在一个照片列表里面.我没有重新BitmapFactory.decode进行裁剪

而是直接

<ImageView
android:id="@+id/iv_photoalbum_photo"
android:layout_width="200px"
android:layout_height="150px"
android:layout_centerInParent="true"
android:scaleType="centerCrop"
android:src="@null" />


采用android:scaleType="centerCrop"的办法 他会根据我们设置的宽高选择适中的区域裁剪。比较简单

7.关于post上传照片,然后照片名和评论是中文时候是乱码的情况。这个问题我困扰了我一天,最后还是顺利解决了(见注释部分)。代码发一下,方便给需要用到的朋友

public static String postFile(String actionUrl, Map<String, String> params, String filePath,
String encoding) {
String newName = "";
if (params.get("type").equals("1")) {
newName = "image.jpg";
} else if (params.get("type").equals("2")) {
newName = "video.mp4";
} else {
newName = "image.jpg";
}
// String uploadFile = "/sdcard/image.JPG";
String LINEND = "\r\n";
String PREFIX = "--";
String BOUNDARY = "*****";
String MULTIPART_FROM_DATA = "multipart/form-data";
String CHARSET = encoding;
try {
URL url = new URL(actionUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
// con.setReadTimeout(DEF.TIMEOUT_POST_CONNECTION);
// con.setConnectTimeout(timeout)
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setRequestProperty("connection", "keep-alive");
con.setRequestProperty("Charset", CHARSET);
con.setRequestProperty("Content-Type", MULTIPART_FROM_DATA + ";boundary=" + BOUNDARY);

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(PREFIX);
sb.append(BOUNDARY);
sb.append(LINEND);
sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINEND);
sb.append("Content-Type: text/plain; charset=" + CHARSET + LINEND);
// sb.append("Content-Transfer-Encoding: 8bit" + LINEND);
sb.append(LINEND);
// sb.append(URLEncoder.encode(entry.getValue(), encoding));
sb.append(entry.getValue());
sb.append(LINEND);
}

DataOutputStream outStream = new DataOutputStream(con.getOutputStream());

//这句是关键的关键,加了这句才帮我解决中文乱码的问题
outStream.write(EncodingUtils.getBytes(sb.toString(), CHARSET));// encode
StringBuilder sb1 = new StringBuilder();
sb1.append(PREFIX);
sb1.append(BOUNDARY);
sb1.append(LINEND);
sb1.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + newName + "\"" + LINEND);
sb1.append("Content-Type: application/octet-stream; charset=" + CHARSET + LINEND);
sb1.append(LINEND);
outStream.writeBytes(sb1.toString());
FileInputStream fStream = new FileInputStream(filePath);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = fStream.read(buffer)) != -1) {
outStream.write(buffer, 0, length);
}
fStream.close();
outStream.writeBytes(LINEND);
outStream.writeBytes(PREFIX + BOUNDARY + PREFIX + LINEND);
outStream.flush();
outStream.close();
String response = read(con.getInputStream());
con.disconnect();
return response;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}


8.关于地图上的小图钉OverlayItem的问题, google提供的OverlayItem很薄弱,仅仅只有getTittle 和getSnippet这些方法
连set方法都不提供。所以建议大家继承OverlayItem,给他包装上你需要的一些业务逻辑和属性。

9.关于拍照和录制视频的问题

拍照和录像我都是采用调用内置实现的,只是由于android的系统的平台定制的多样化,碰到一些问题.比如录制视频我就没有采用给他分配路径的形式,因为在三星的galaxy平板上会出现相机无法退出的情况.所以改成了

private void recordVideo() {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
startActivityForResult(intent, MENU_RECORD_VIDEO_ID);
}


然后在onAcitivityResult里面:

case MENU_RECORD_VIDEO_ID:
Uri uriVideo = data.getData();
Cursor cursorVideo = this.getContentResolver().query(uriVideo, null, null, null, null);
if (cursorVideo.moveToNext()) {
String s[] = cursorVideo.getColumnNames();
String videoPath = cursorVideo.getString(cursorVideo.getColumnIndex("_data"));
Toast.makeText(this, videoPath, Toast.LENGTH_LONG).show();
System.out.println("videoPath :" + videoPath);
}
break;


而拍照如果不指定路径,我发现在MIUI系统上生成的图片会找不到.(真的想对android的开源性吐槽,屏幕UI设计繁琐我都不说了,结果连一些基本的性质都不一致了)

所以图片采用的是分配地址:

private void takePhotoToMyFolder() {
String sdcardState = Environment.getExternalStorageState();
if (sdcardState.equals(Environment.MEDIA_MOUNTED)) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String photoDirPath = Environment.getExternalStorageDirectory() + File.separator
+ NIKEPHOTO_FOLDER + File.separator;
String fileName = "IMG_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg";
File out = new File(photoDirPath);
if (!out.exists()) {
out.mkdirs();
}
out = new File(photoDirPath, fileName);
photoPath = photoDirPath + fileName;
Uri uri = Uri.fromFile(out);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, MENU_TAKE_PHOTO_ID);
} else {
Util.show1BtnDialog(this, 0, 0, R.string.hint_nosdcard_disconnect, R.string.OK, null);
}
}


然后在onAcitivityResult里面:

case MENU_TAKE_PHOTO_ID:
Toast.makeText(this, photoPath, Toast.LENGTH_LONG).show();
Intent intentUpload = new Intent();
intentUpload.putExtra(UploadActivity.KEY_DATAPATH, photoPath);
intentUpload.setClass(this, UploadActivity.class);
startActivity(intentUpload);
break;


先暂时写这么多...如果还有想到什么再补充





                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: