分享

android选择图片或拍照图片上传到服务器(包括上传参数)

 非動 2015-07-11
最近要搞一个项目,需要上传相册和拍照的图片,不负所望,终于完成了! 不过需要说明一下,其实网上很多教程拍照的图片,都是缩略图不是很清晰,所以需要在调用照相机的时候,事先生成一个地址,用于标识拍照的图片URI

具体上传代码:
1.选择图片和上传界面,包括上传完成和异常的回调监听

  1. package com.spring.sky.image.upload;

  2. import java.util.HashMap;
  3. import java.util.Map;

  4. import android.app.Activity;
  5. import android.app.ProgressDialog;
  6. import android.content.Intent;
  7. import android.graphics.Bitmap;
  8. import android.graphics.BitmapFactory;
  9. import android.os.Bundle;
  10. import android.os.Handler;
  11. import android.os.Message;
  12. import android.util.Log;
  13. import android.view.View;
  14. import android.view.View.OnClickListener;
  15. import android.widget.Button;
  16. import android.widget.ImageView;
  17. import android.widget.ProgressBar;
  18. import android.widget.TextView;
  19. import android.widget.Toast;

  20. import com.spring.sky.image.upload.network.UploadUtil;
  21. import com.spring.sky.image.upload.network.UploadUtil.OnUploadProcessListener;
  22. /**
  23. * @author spring sky<br>
  24. * Email :vipa1888@163.com<br>
  25. * QQ: 840950105<br>
  26. * 说明:主要用于选择文件和上传文件操作
  27. */
  28. public class MainActivity extends Activity implements OnClickListener,OnUploadProcessListener{
  29. private static final String TAG = "uploadImage";

  30. /**
  31. * 去上传文件
  32. */
  33. protected static final int TO_UPLOAD_FILE = 1;
  34. /**
  35. * 上传文件响应
  36. */
  37. protected static final int UPLOAD_FILE_DONE = 2; //
  38. /**
  39. * 选择文件
  40. */
  41. public static final int TO_SELECT_PHOTO = 3;
  42. /**
  43. * 上传初始化
  44. */
  45. private static final int UPLOAD_INIT_PROCESS = 4;
  46. /**
  47. * 上传中
  48. */
  49. private static final int UPLOAD_IN_PROCESS = 5;
  50. /***
  51. * 这里的这个URL是我服务器的javaEE环境URL
  52. */
  53. private static String requestURL = "http://192.168.10.160:8080/fileUpload/p/file!upload";
  54. private Button selectButton,uploadButton;
  55. private ImageView imageView;
  56. private TextView uploadImageResult;
  57. private ProgressBar progressBar;

  58. private String picPath = null;
  59. private ProgressDialog progressDialog;

  60. /** Called when the activity is first created. */
  61. @Override
  62. public void onCreate(Bundle savedInstanceState) {
  63. super.onCreate(savedInstanceState);
  64. setContentView(R.layout.main);
  65. initView();
  66. }

  67. /**
  68. * 初始化数据
  69. */
  70. private void initView() {
  71. selectButton = (Button) this.findViewById(R.id.selectImage);
  72. uploadButton = (Button) this.findViewById(R.id.uploadImage);
  73. selectButton.setOnClickListener(this);
  74. uploadButton.setOnClickListener(this);
  75. imageView = (ImageView) this.findViewById(R.id.imageView);
  76. uploadImageResult = (TextView) findViewById(R.id.uploadImageResult);
  77. progressDialog = new ProgressDialog(this);
  78. progressBar = (ProgressBar) findViewById(R.id.progressBar1);
  79. }

  80. @Override
  81. public void onClick(View v) {
  82. switch (v.getId()) {
  83. case R.id.selectImage:
  84. Intent intent = new Intent(this,SelectPicActivity.class);
  85. startActivityForResult(intent, TO_SELECT_PHOTO);
  86. break;
  87. case R.id.uploadImage:
  88. if(picPath!=null)
  89. {
  90. handler.sendEmptyMessage(TO_UPLOAD_FILE);
  91. }else{
  92. Toast.makeText(this, "上传的文件路径出错", Toast.LENGTH_LONG).show();
  93. }
  94. break;
  95. default:
  96. break;
  97. }
  98. }

  99. @Override
  100. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  101. if(resultCode==Activity.RESULT_OK && requestCode == TO_SELECT_PHOTO)
  102. {
  103. picPath = data.getStringExtra(SelectPicActivity.KEY_PHOTO_PATH);
  104. Log.i(TAG, "最终选择的图片="+picPath);
  105. Bitmap bm = BitmapFactory.decodeFile(picPath);
  106. imageView.setImageBitmap(bm);
  107. }
  108. super.onActivityResult(requestCode, resultCode, data);
  109. }

  110. /**
  111. * 上传服务器响应回调
  112. */
  113. @Override
  114. public void onUploadDone(int responseCode, String message) {
  115. progressDialog.dismiss();
  116. Message msg = Message.obtain();
  117. msg.what = UPLOAD_FILE_DONE;
  118. msg.arg1 = responseCode;
  119. msg.obj = message;
  120. handler.sendMessage(msg);
  121. }

  122. private void toUploadFile()
  123. {
  124. uploadImageResult.setText("正在上传中...");
  125. progressDialog.setMessage("正在上传文件...");
  126. progressDialog.show();
  127. String fileKey = "pic";
  128. UploadUtil uploadUtil = UploadUtil.getInstance();;
  129. uploadUtil.setOnUploadProcessListener(this); //设置监听器监听上传状态

  130. Map<String, String> params = new HashMap<String, String>();
  131. params.put("orderId", "11111");
  132. uploadUtil.uploadFile( picPath,fileKey, requestURL,params);
  133. }

  134. private Handler handler = new Handler(){
  135. @Override
  136. public void handleMessage(Message msg) {
  137. switch (msg.what) {
  138. case TO_UPLOAD_FILE:
  139. toUploadFile();
  140. break;

  141. case UPLOAD_INIT_PROCESS:
  142. progressBar.setMax(msg.arg1);
  143. break;
  144. case UPLOAD_IN_PROCESS:
  145. progressBar.setProgress(msg.arg1);
  146. break;
  147. case UPLOAD_FILE_DONE:
  148. String result = "响应码:"+msg.arg1+"\n响应信息:"+msg.obj+"\n耗时:"+UploadUtil.getRequestTime()+"秒";
  149. uploadImageResult.setText(result);
  150. break;
  151. default:
  152. break;
  153. }
  154. super.handleMessage(msg);
  155. }

  156. };

  157. @Override
  158. public void onUploadProcess(int uploadSize) {
  159. Message msg = Message.obtain();
  160. msg.what = UPLOAD_IN_PROCESS;
  161. msg.arg1 = uploadSize;
  162. handler.sendMessage(msg );
  163. }

  164. @Override
  165. public void initUpload(int fileSize) {
  166. Message msg = Message.obtain();
  167. msg.what = UPLOAD_INIT_PROCESS;
  168. msg.arg1 = fileSize;
  169. handler.sendMessage(msg );
  170. }

  171. }
复制代码

2.选择图片界面,主要涉及两种方式:选择图片和及时拍照图片

  1. package com.spring.sky.image.upload;

  2. import android.app.Activity;
  3. import android.content.ContentValues;
  4. import android.content.Intent;
  5. import android.database.Cursor;
  6. import android.net.Uri;
  7. import android.os.Bundle;
  8. import android.os.Environment;
  9. import android.provider.MediaStore;
  10. import android.util.Log;
  11. import android.view.MotionEvent;
  12. import android.view.View;
  13. import android.view.View.OnClickListener;
  14. import android.widget.Button;
  15. import android.widget.LinearLayout;
  16. import android.widget.Toast;

  17. /**
  18. * @author spring sky<br>
  19. * Email :vipa1888@163.com<br>
  20. * QQ: 840950105<br>
  21. * @version 创建时间:2012-11-22 上午9:20:03
  22. * 说明:主要用于选择文件操作
  23. */

  24. public class SelectPicActivity extends Activity implements OnClickListener{

  25. /***
  26. * 使用照相机拍照获取图片
  27. */
  28. public static final int SELECT_PIC_BY_TACK_PHOTO = 1;
  29. /***
  30. * 使用相册中的图片
  31. */
  32. public static final int SELECT_PIC_BY_PICK_PHOTO = 2;

  33. /***
  34. * 从Intent获取图片路径的KEY
  35. */
  36. public static final String KEY_PHOTO_PATH = "photo_path";

  37. private static final String TAG = "SelectPicActivity";

  38. private LinearLayout dialogLayout;
  39. private Button takePhotoBtn,pickPhotoBtn,cancelBtn;

  40. /**获取到的图片路径*/
  41. private String picPath;

  42. private Intent lastIntent ;

  43. private Uri photoUri;
  44. @Override
  45. protected void onCreate(Bundle savedInstanceState) {
  46. super.onCreate(savedInstanceState);
  47. setContentView(R.layout.select_pic_layout);
  48. initView();
  49. }
  50. /**
  51. * 初始化加载View
  52. */
  53. private void initView() {
  54. dialogLayout = (LinearLayout) findViewById(R.id.dialog_layout);
  55. dialogLayout.setOnClickListener(this);
  56. takePhotoBtn = (Button) findViewById(R.id.btn_take_photo);
  57. takePhotoBtn.setOnClickListener(this);
  58. pickPhotoBtn = (Button) findViewById(R.id.btn_pick_photo);
  59. pickPhotoBtn.setOnClickListener(this);
  60. cancelBtn = (Button) findViewById(R.id.btn_cancel);
  61. cancelBtn.setOnClickListener(this);

  62. lastIntent = getIntent();
  63. }

  64. @Override
  65. public void onClick(View v) {
  66. switch (v.getId()) {
  67. case R.id.dialog_layout:
  68. finish();
  69. break;
  70. case R.id.btn_take_photo:
  71. takePhoto();
  72. break;
  73. case R.id.btn_pick_photo:
  74. pickPhoto();
  75. break;
  76. default:
  77. finish();
  78. break;
  79. }
  80. }

  81. /**
  82. * 拍照获取图片
  83. */
  84. private void takePhoto() {
  85. //执行拍照前,应该先判断SD卡是否存在
  86. String SDState = Environment.getExternalStorageState();
  87. if(SDState.equals(Environment.MEDIA_MOUNTED))
  88. {

  89. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//"android.media.action.IMAGE_CAPTURE"
  90. /***
  91. * 需要说明一下,以下操作使用照相机拍照,拍照后的图片会存放在相册中的
  92. * 这里使用的这种方式有一个好处就是获取的图片是拍照后的原图
  93. * 如果不实用ContentValues存放照片路径的话,拍照后获取的图片为缩略图不清晰
  94. */
  95. ContentValues values = new ContentValues();
  96. photoUri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
  97. intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, photoUri);
  98. /**-----------------*/
  99. startActivityForResult(intent, SELECT_PIC_BY_TACK_PHOTO);
  100. }else{
  101. Toast.makeText(this,"内存卡不存在", Toast.LENGTH_LONG).show();
  102. }
  103. }

  104. /***
  105. * 从相册中取图片
  106. */
  107. private void pickPhoto() {
  108. Intent intent = new Intent();
  109. intent.setType("image/*");
  110. intent.setAction(Intent.ACTION_GET_CONTENT);
  111. startActivityForResult(intent, SELECT_PIC_BY_PICK_PHOTO);
  112. }

  113. @Override
  114. public boolean onTouchEvent(MotionEvent event) {
  115. finish();
  116. return super.onTouchEvent(event);
  117. }

  118. @Override
  119. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  120. if(resultCode == Activity.RESULT_OK)
  121. {
  122. doPhoto(requestCode,data);
  123. }
  124. super.onActivityResult(requestCode, resultCode, data);
  125. }

  126. /**
  127. * 选择图片后,获取图片的路径
  128. * @param requestCode
  129. * @param data
  130. */
  131. private void doPhoto(int requestCode,Intent data)
  132. {
  133. if(requestCode == SELECT_PIC_BY_PICK_PHOTO ) //从相册取图片,有些手机有异常情况,请注意
  134. {
  135. if(data == null)
  136. {
  137. Toast.makeText(this, "选择图片文件出错", Toast.LENGTH_LONG).show();
  138. return;
  139. }
  140. photoUri = data.getData();
  141. if(photoUri == null )
  142. {
  143. Toast.makeText(this, "选择图片文件出错", Toast.LENGTH_LONG).show();
  144. return;
  145. }
  146. }
  147. String[] pojo = {MediaStore.Images.Media.DATA};
  148. Cursor cursor = managedQuery(photoUri, pojo, null, null,null);
  149. if(cursor != null )
  150. {
  151. int columnIndex = cursor.getColumnIndexOrThrow(pojo[0]);
  152. cursor.moveToFirst();
  153. picPath = cursor.getString(columnIndex);
  154. cursor.close();
  155. }
  156. Log.i(TAG, "imagePath = "+picPath);
  157. if(picPath != null && ( picPath.endsWith(".png") || picPath.endsWith(".PNG") ||picPath.endsWith(".jpg") ||picPath.endsWith(".JPG") ))
  158. {
  159. lastIntent.putExtra(KEY_PHOTO_PATH, picPath);
  160. setResult(Activity.RESULT_OK, lastIntent);
  161. finish();
  162. }else{
  163. Toast.makeText(this, "选择图片文件不正确", Toast.LENGTH_LONG).show();
  164. }
  165. }
  166. }
复制代码

3. 上传工具类,主要实现了图片的上传,上传过程的初始化监听和上传完成的监听,还有上传耗时的计算

  1. package com.spring.sky.image.upload.network;

  2. import java.io.DataOutputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.net.HttpURLConnection;
  8. import java.net.MalformedURLException;
  9. import java.net.URL;
  10. import java.util.Iterator;
  11. import java.util.Map;
  12. import java.util.UUID;

  13. import android.util.Log;

  14. /**
  15. *
  16. * 上传工具类
  17. * @author spring sky<br>
  18. * Email :vipa1888@163.com<br>
  19. * QQ: 840950105<br>
  20. * 支持上传文件和参数
  21. */
  22. public class UploadUtil {
  23. private static UploadUtil uploadUtil;
  24. private static final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
  25. private static final String PREFIX = "--";
  26. private static final String LINE_END = "\r\n";
  27. private static final String CONTENT_TYPE = "multipart/form-data"; // 内容类型
  28. private UploadUtil() {

  29. }

  30. /**
  31. * 单例模式获取上传工具类
  32. * @return
  33. */
  34. public static UploadUtil getInstance() {
  35. if (null == uploadUtil) {
  36. uploadUtil = new UploadUtil();
  37. }
  38. return uploadUtil;
  39. }

  40. private static final String TAG = "UploadUtil";
  41. private int readTimeOut = 10 * 1000; // 读取超时
  42. private int connectTimeout = 10 * 1000; // 超时时间
  43. /***
  44. * 请求使用多长时间
  45. */
  46. private static int requestTime = 0;

  47. private static final String CHARSET = "utf-8"; // 设置编码

  48. /***
  49. * 上传成功
  50. */
  51. public static final int UPLOAD_SUCCESS_CODE = 1;
  52. /**
  53. * 文件不存在
  54. */
  55. public static final int UPLOAD_FILE_NOT_EXISTS_CODE = 2;
  56. /**
  57. * 服务器出错
  58. */
  59. public static final int UPLOAD_SERVER_ERROR_CODE = 3;
  60. protected static final int WHAT_TO_UPLOAD = 1;
  61. protected static final int WHAT_UPLOAD_DONE = 2;

  62. /**
  63. * android上传文件到服务器
  64. *
  65. * @param filePath
  66. * 需要上传的文件的路径
  67. * @param fileKey
  68. * 在网页上<input type=file name=xxx/> xxx就是这里的fileKey
  69. * @param RequestURL
  70. * 请求的URL
  71. */
  72. public void uploadFile(String filePath, String fileKey, String RequestURL,
  73. Map<String, String> param) {
  74. if (filePath == null) {
  75. sendMessage(UPLOAD_FILE_NOT_EXISTS_CODE,"文件不存在");
  76. return;
  77. }
  78. try {
  79. File file = new File(filePath);
  80. uploadFile(file, fileKey, RequestURL, param);
  81. } catch (Exception e) {
  82. sendMessage(UPLOAD_FILE_NOT_EXISTS_CODE,"文件不存在");
  83. e.printStackTrace();
  84. return;
  85. }
  86. }

  87. /**
  88. * android上传文件到服务器
  89. *
  90. * @param file
  91. * 需要上传的文件
  92. * @param fileKey
  93. * 在网页上<input type=file name=xxx/> xxx就是这里的fileKey
  94. * @param RequestURL
  95. * 请求的URL
  96. */
  97. public void uploadFile(final File file, final String fileKey,
  98. final String RequestURL, final Map<String, String> param) {
  99. if (file == null || (!file.exists())) {
  100. sendMessage(UPLOAD_FILE_NOT_EXISTS_CODE,"文件不存在");
  101. return;
  102. }

  103. Log.i(TAG, "请求的URL=" + RequestURL);
  104. Log.i(TAG, "请求的fileName=" + file.getName());
  105. Log.i(TAG, "请求的fileKey=" + fileKey);
  106. new Thread(new Runnable() { //开启线程上传文件
  107. @Override
  108. public void run() {
  109. toUploadFile(file, fileKey, RequestURL, param);
  110. }
  111. }).start();

  112. }

  113. private void toUploadFile(File file, String fileKey, String RequestURL,
  114. Map<String, String> param) {
  115. String result = null;
  116. requestTime= 0;

  117. long requestTime = System.currentTimeMillis();
  118. long responseTime = 0;

  119. try {
  120. URL url = new URL(RequestURL);
  121. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  122. conn.setReadTimeout(readTimeOut);
  123. conn.setConnectTimeout(connectTimeout);
  124. conn.setDoInput(true); // 允许输入流
  125. conn.setDoOutput(true); // 允许输出流
  126. conn.setUseCaches(false); // 不允许使用缓存
  127. conn.setRequestMethod("POST"); // 请求方式
  128. conn.setRequestProperty("Charset", CHARSET); // 设置编码
  129. conn.setRequestProperty("connection", "keep-alive");
  130. conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
  131. conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
  132. // conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

  133. /**
  134. * 当文件不为空,把文件包装并且上传
  135. */
  136. DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
  137. StringBuffer sb = null;
  138. String params = "";

  139. /***
  140. * 以下是用于上传参数
  141. */
  142. if (param != null && param.size() > 0) {
  143. Iterator<String> it = param.keySet().iterator();
  144. while (it.hasNext()) {
  145. sb = null;
  146. sb = new StringBuffer();
  147. String key = it.next();
  148. String value = param.get(key);
  149. sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
  150. sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_END).append(LINE_END);
  151. sb.append(value).append(LINE_END);
  152. params = sb.toString();
  153. Log.i(TAG, key+"="+params+"##");
  154. dos.write(params.getBytes());
  155. // dos.flush();
  156. }
  157. }

  158. sb = null;
  159. params = null;
  160. sb = new StringBuffer();
  161. /**
  162. * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
  163. * filename是文件的名字,包含后缀名的 比如:abc.png
  164. */
  165. sb.append(PREFIX).append(BOUNDARY).append(LINE_END);
  166. sb.append("Content-Disposition:form-data; name=\"" + fileKey
  167. + "\"; filename=\"" + file.getName() + "\"" + LINE_END);
  168. sb.append("Content-Type:image/pjpeg" + LINE_END); // 这里配置的Content-type很重要的 ,用于服务器端辨别文件的类型的
  169. sb.append(LINE_END);
  170. params = sb.toString();
  171. sb = null;

  172. Log.i(TAG, file.getName()+"=" + params+"##");
  173. dos.write(params.getBytes());
  174. /**上传文件*/
  175. InputStream is = new FileInputStream(file);
  176. onUploadProcessListener.initUpload((int)file.length());
  177. byte[] bytes = new byte[1024];
  178. int len = 0;
  179. int curLen = 0;
  180. while ((len = is.read(bytes)) != -1) {
  181. curLen += len;
  182. dos.write(bytes, 0, len);
  183. onUploadProcessListener.onUploadProcess(curLen);
  184. }
  185. is.close();

  186. dos.write(LINE_END.getBytes());
  187. byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END).getBytes();
  188. dos.write(end_data);
  189. dos.flush();
  190. //
  191. // dos.write(tempOutputStream.toByteArray());
  192. /**
  193. * 获取响应码 200=成功 当响应成功,获取响应的流
  194. */
  195. int res = conn.getResponseCode();
  196. responseTime = System.currentTimeMillis();
  197. this.requestTime = (int) ((responseTime-requestTime)/1000);
  198. Log.e(TAG, "response code:" + res);
  199. if (res == 200) {
  200. Log.e(TAG, "request success");
  201. InputStream input = conn.getInputStream();
  202. StringBuffer sb1 = new StringBuffer();
  203. int ss;
  204. while ((ss = input.read()) != -1) {
  205. sb1.append((char) ss);
  206. }
  207. result = sb1.toString();
  208. Log.e(TAG, "result : " + result);
  209. sendMessage(UPLOAD_SUCCESS_CODE, "上传结果:"
  210. + result);
  211. return;
  212. } else {
  213. Log.e(TAG, "request error");
  214. sendMessage(UPLOAD_SERVER_ERROR_CODE,"上传失败:code=" + res);
  215. return;
  216. }
  217. } catch (MalformedURLException e) {
  218. sendMessage(UPLOAD_SERVER_ERROR_CODE,"上传失败:error=" + e.getMessage());
  219. e.printStackTrace();
  220. return;
  221. } catch (IOException e) {
  222. sendMessage(UPLOAD_SERVER_ERROR_CODE,"上传失败:error=" + e.getMessage());
  223. e.printStackTrace();
  224. return;
  225. }
  226. }

  227. /**
  228. * 发送上传结果
  229. * @param responseCode
  230. * @param responseMessage
  231. */
  232. private void sendMessage(int responseCode,String responseMessage)
  233. {
  234. onUploadProcessListener.onUploadDone(responseCode, responseMessage);
  235. }

  236. /**
  237. * 下面是一个自定义的回调函数,用到回调上传文件是否完成
  238. *
  239. * @author shimingzheng
  240. *
  241. */
  242. public static interface OnUploadProcessListener {
  243. /**
  244. * 上传响应
  245. * @param responseCode
  246. * @param message
  247. */
  248. void onUploadDone(int responseCode, String message);
  249. /**
  250. * 上传中
  251. * @param uploadSize
  252. */
  253. void onUploadProcess(int uploadSize);
  254. /**
  255. * 准备上传
  256. * @param fileSize
  257. */
  258. void initUpload(int fileSize);
  259. }
  260. private OnUploadProcessListener onUploadProcessListener;

  261. public void setOnUploadProcessListener(
  262. OnUploadProcessListener onUploadProcessListener) {
  263. this.onUploadProcessListener = onUploadProcessListener;
  264. }

  265. public int getReadTimeOut() {
  266. return readTimeOut;
  267. }

  268. public void setReadTimeOut(int readTimeOut) {
  269. this.readTimeOut = readTimeOut;
  270. }

  271. public int getConnectTimeout() {
  272. return connectTimeout;
  273. }

  274. public void setConnectTimeout(int connectTimeout) {
  275. this.connectTimeout = connectTimeout;
  276. }
  277. /**
  278. * 获取上传使用的时间
  279. * @return
  280. */
  281. public static int getRequestTime() {
  282. return requestTime;
  283. }

  284. public static interface uploadProcessListener{

  285. }

  286. }
复制代码

以上代码,我就不详细讲解原理,相关难点注释已经写得很清楚了!分享出来,和大家一起学习!

原文链接:http://blog.csdn.net/vipa1888/article/details/8213898

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约