主要代码

  1. HomeActivity.java
    代码如下:
package com.itau.jingdong.ui;
import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.TabActivity;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.RadioGroup;
 import android.widget.RelativeLayout;
 import android.widget.TabHost;
 import android.widget.RadioGroup.OnCheckedChangeListener;import com.itau.jingdong.AppManager;
 import com.itau.jingdong.R;
 import com.nostra13.universalimageloader.core.ImageLoader;
 //主页面
 public class HomeActivity extends TabActivity {
 //打印日志标记
 public static final String TAG = HomeActivity.class.getSimpleName();
 //定义一个单选按钮组 用于切换底部选项栏
 private RadioGroup mTabButtonGroup;
 private TabHost mTabHost;//选项卡组件
 //定义选项卡对应的类名
 public static final String TAB_MAIN = “MAIN_ACTIVITY”;//主页
 public static final String TAB_SEARCH = “SEARCH_ACTIVITY”;//查询
 public static final String TAB_CATEGORY = “CATEGORY_ACTIVITY”;//分类
 public static final String TAB_CART = “CART_ACTIVITY”;//购物车
 public static final String TAB_PERSONAL = “PERSONAL_ACTIVITY”;//个人
@Override
protected void onCreate(Bundle savedInstanceState) {
	// TODO Auto-generated method stub
	super.onCreate(savedInstanceState);
	AppManager.getInstance().addActivity(this);
	setContentView(R.layout.activity_home);
	findViewById();//绑定控件
	initView();
}

private void findViewById() {
	//绑定单选按钮组控件
	mTabButtonGroup = (RadioGroup) findViewById(R.id.home_radio_button_group);
}

private void initView() {
	//获取选项
	mTabHost = getTabHost();
	//定义响应的intent对象
	Intent i_main = new Intent(this, IndexActivity.class);
	Intent i_search = new Intent(this, SearchActivity.class);
	Intent i_category = new Intent(this, CategoryActivity.class);
	Intent i_cart = new Intent(this, CartActivity.class);
	Intent i_personal = new Intent(this, PersonalActivity.class);
	//将选项与对应页面加入tab项
	mTabHost.addTab(mTabHost.newTabSpec(TAB_MAIN).setIndicator(TAB_MAIN)
			.setContent(i_main));
	mTabHost.addTab(mTabHost.newTabSpec(TAB_SEARCH)
			.setIndicator(TAB_SEARCH).setContent(i_search));
	mTabHost.addTab(mTabHost.newTabSpec(TAB_CATEGORY)
			.setIndicator(TAB_CATEGORY).setContent(i_category));
	mTabHost.addTab(mTabHost.newTabSpec(TAB_CART).setIndicator(TAB_CART)
			.setContent(i_cart));
	mTabHost.addTab(mTabHost.newTabSpec(TAB_PERSONAL)
			.setIndicator(TAB_PERSONAL).setContent(i_personal));
	//设置当前显示页
	mTabHost.setCurrentTabByTag(TAB_MAIN);
	//设置单选监听事件 根据单选项跳转至指定页面
	mTabButtonGroup
			.setOnCheckedChangeListener(new OnCheckedChangeListener() {
				public void onCheckedChanged(RadioGroup group, int checkedId) {
					switch (checkedId) {
						//主页面
					case R.id.home_tab_main:
						mTabHost.setCurrentTabByTag(TAB_MAIN);
						break;
						//查询页面
					case R.id.home_tab_search:
						mTabHost.setCurrentTabByTag(TAB_SEARCH);
						break;
						//分类页面
					case R.id.home_tab_category:
						mTabHost.setCurrentTabByTag(TAB_CATEGORY);
						break;
						//购物车页面
					case R.id.home_tab_cart:
						mTabHost.setCurrentTabByTag(TAB_CART);
						break;
						//用户信息页面
					case R.id.home_tab_personal:
						mTabHost.setCurrentTabByTag(TAB_PERSONAL);
						break;
					default:
						break;
					}
				}
			});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
	//加载菜单项
	getMenuInflater().inflate(R.menu.activity_menu, menu);
	return true;
}
//根据菜单项跳转至不同页面
@Override
public boolean onOptionsItemSelected(MenuItem item) {
	// TODO Auto-generated method stub
	switch (item.getItemId()) {
	case R.id.menu_about:
		break;
	case R.id.menu_setting:
		break;
	case R.id.menu_history:
		break;
	case R.id.menu_feedback:
		break;
	case R.id.menu_exit:
		//启动退出页面对话框
		showAlertDialog("退出程序", "确定退出京东商城?", "确定", new OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				// TODO Auto-generated method stub
				AppManager.getInstance().AppExit(getApplicationContext());
				ImageLoader.getInstance().clearMemoryCache();
			}
		}, "取消", new OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				// TODO Auto-generated method stub
				dialog.dismiss();
			}
		});
		break;
	case R.id.menu_help:
		break;
	default:
		break;
	}
	return true;
}
//显示一个对话框 有两个按钮
protected void showAlertDialog(String title, String message,
		String positiveText,
		DialogInterface.OnClickListener onPositiveClickListener,
		String negativeText,
		DialogInterface.OnClickListener onNegativeClickListener) {
	//创建一个含有确定 取消的 对话框
	new AlertDialog.Builder(this).setTitle(title).setMessage(message)
			.setPositiveButton(positiveText, onPositiveClickListener)
			.setNegativeButton(negativeText, onNegativeClickListener)
			.show();//记得设置显示
}

}

android 购物类app android购物商城源码_android studio

文件:url80.ctfile.com/f/25127180-735569552-34e4ac?p=551685 (访问密码: 551685)


前端上传文件时,无论是使用比较传统的表单,还是使用FormData对象,其本质都是发送一个multipart/form-data请求。

例如,前端模拟上传代码如下:

var formdata = new FormData();
 formdata.append(“key1”, “value1”);
 formdata.append(“key2”, “value2”);
 formdata.append(“file1”, fileInput.files[0], “/d:/Downloads/rfc1867.pdf”);
 formdata.append(“file2”, fileInput.files[0], “/d:/Downloads/rfc1314.pdf”);var requestOptions = {
 method: ‘POST’,
 body: formdata,
 redirect: ‘follow’
 };fetch(“http://localhost:10001/file/upload”, requestOptions)
 .then(response => response.text())
 .then(result => console.log(result))
 .catch(error => console.log(‘error’, error));
 实际会发送如下HTTP请求:POST /file/upload HTTP/1.1
 Host: localhost:10001
 Content-Length: 536
 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW----WebKitFormBoundary7MA4YWxkTrZu0gW
 Content-Disposition: form-data; name=“key1”value1
 ----WebKitFormBoundary7MA4YWxkTrZu0gW
 Content-Disposition: form-data; name=“key2”value2
 ----WebKitFormBoundary7MA4YWxkTrZu0gW
 Content-Disposition: form-data; name=“file1”; filename=“/d:/Downloads/rfc1867.pdf”
 Content-Type: application/pdf(data)
 ----WebKitFormBoundary7MA4YWxkTrZu0gW
 Content-Disposition: form-data; name=“file2”; filename=“/d:/Downloads/rfc1314.pdf”
 Content-Type: application/pdf(data)
 ----WebKitFormBoundary7MA4YWxkTrZu0gW在后端可以通过MultipartHttpServletRequest接收文件:
@RestController
 @RequestMapping(“file”)
 public class FileUploadController {
 @RequestMapping(“/upload”)
 public String upload(MultipartHttpServletRequest request) {
 // 获取非文件参数
 String value1 = request.getParameter(“key1”);
 System.out.println(value1); // value1
 String value2 = request.getParameter(“key2”);
 System.out.println(value2); // value2
 // 获取文件
 MultipartFile file1 = request.getFile(“file1”);
 System.out.println(file1 != null ? file1.getOriginalFilename() : “null”); // rfc1867.pdf
 MultipartFile file2 = request.getFile(“file2”);
 System.out.println(file2 != null ? file2.getOriginalFilename() : “null”); // rfc1314.pdf
 return “Hello MultipartResolver!”;
 }
 }
 2 MultipartResolver接口#
 2.1 MultipartResolver的功能#
 org.springframework.web.multipart.MultipartResolver是Spring-Web根据RFC1867规范实现的多文件上传的策略接口。
 同时,MultipartResolver是Spring对文件上传处理流程在接口层次的抽象。
 也就是说,当涉及到文件上传时,Spring都会使用MultipartResolver接口进行处理,而不涉及具体实现类。
 MultipartResolver接口源码如下:public interface MultipartResolver {
 /**
 * 判断当前HttpServletRequest请求是否是文件请求
/
 boolean isMultipart(HttpServletRequest request);
 /*
 * 将当前HttpServletRequest请求的数据(文件和普通参数)封装成MultipartHttpServletRequest对象
/
 MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
 /*
 * 清除文件上传产生的临时资源(如服务器本地临时文件)
 */
 void cleanupMultipart(MultipartHttpServletRequest request);
 }
 2.2 在DispatcherServlet中的使用#
 DispatcherServlet中持有MultipartResolver成员变量:public class DispatcherServlet extends FrameworkServlet {
 /** Well-known name for the MultipartResolver object in the bean factory for this namespace. /
 public static final String MULTIPART_RESOLVER_BEAN_NAME = “multipartResolver”;
 /* MultipartResolver used by this servlet. */
 @Nullable
 private MultipartResolver multipartResolver;
 }
 DispatcherServlet在初始化时,会从Spring容器中获取名为multipartResolver的对象(该对象是MultipartResolver实现类),作为文件上传解析器:/**
• Initialize the MultipartResolver used by this class. * If no bean is defined with the given name in the BeanFactory for this namespace,
• no multipart handling is provided. */
 private void initMultipartResolver(ApplicationContext context) {
 try {
 this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
 if (logger.isTraceEnabled()) {
 logger.trace("Detected " + this.multipartResolver);
 }
 else if (logger.isDebugEnabled()) {
 logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
 }
 }
 catch (NoSuchBeanDefinitionException ex) {
 // Default is no multipart resolver.
 this.multipartResolver = null;
 if (logger.isTraceEnabled()) {
 logger.trace(“No MultipartResolver '” + MULTIPART_RESOLVER_BEAN_NAME + “’ declared”);
 }
 }
 }

  • 需要注意的是,如果Spring容器中不存在名为multipartResolver的对象,DispatcherServlet并不会额外指定默认的文件解析器。此时,DispatcherServlet不会对文件上传请求进行处理。也就是说,尽管当前请求是文件请求,也不会被处理成MultipartHttpServletRequest,如果我们在控制层进行强制类型转换,会抛异常。

DispatcherServlet在处理业务时,会按照顺序分别调用这些方法进行文件上传处理,相关核心源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 HttpServletRequest processedRequest = request;
 boolean multipartRequestParsed = false;
 try {
 // 判断&封装文件请求
 processedRequest = checkMultipart(request);
 multipartRequestParsed = (processedRequest != request);
 // 请求处理……
 }
 finally {
 // 清除文件上传产生的临时资源
 if (multipartRequestParsed) {
 cleanupMultipart(processedRequest);
 }
 }
 }


在checkMultipart()方法中,会进行判断、封装文件请求:

/**
• Convert the request into a multipart request, and make multipart resolver available. * If no multipart resolver is set, simply use the existing request.
• @param request current HTTP request
• @return the processed request (multipart wrapper if necessary) * @see MultipartResolver#resolveMultipart
 */
 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
 if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
 logger.trace(“Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter”);
 }
 }
 else if (hasMultipartException(request)) {
 logger.debug("Multipart resolution previously failed for current request - " +
 “skipping re-resolution for undisturbed error rendering”);
 }
 else {
 try {
 return this.multipartResolver.resolveMultipart(request);
 }
 catch (MultipartException ex) {
 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
 logger.debug(“Multipart resolution failed for error dispatch”, ex);
 // Keep processing error dispatch with regular request handle below
 }
 else {
 throw ex;
 }
 }
 }
 }
 // If not returned before: return original request.
 return request;
 }

  • 总的来说,DispatcherServlet处理文件请求会经过以下步骤:

判断当前HttpServletRequest请求是否是文件请求
是:将当前HttpServletRequest请求的数据(文件和普通参数)封装成MultipartHttpServletRequest对象
不是:不处理
DispatcherServlet对原始HttpServletRequest或MultipartHttpServletRequest对象进行业务处理
业务处理完成,清除文件上传产生的临时资源
2.3 MultipartResolver实现类&配置方式#
Spring提供了两个MultipartResolver实现类:

org.springframework.web.multipart.support.StandardServletMultipartResolver:根据Servlet 3.0+ Part Api实现
org.springframework.web.multipart.commons.CommonsMultipartResolver:根据Apache Commons FileUpload实现
在Spring Boot 2.0+中,默认会在org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration中创建StandardServletMultipartResolver作为默认文件解析器:

@AutoConfiguration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = “spring.servlet.multipart”, name = “enabled”, matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

private final MultipartProperties multipartProperties;

public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}

@Bean
@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}

@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
当需要指定其他文件解析器时,只需要引入相关依赖,然后配置一个名为multipartResolver的bean对象:

@Bean
public MultipartResolver multipartResolver() {
MultipartResolver multipartResolver = …;
return multipartResolver;
}
接下来,我们分别详细介绍两种实现类的使用和原理。

3 StandardServletMultipartResolver解析器#
3.1 StandardServletMultipartResolver#isMultipart#
StandardServletMultipartResolver解析器的通过判断请求的Content-Type来判断是否是文件请求:

public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(),
(this.strictServletCompliance ? “multipart/form-data” : “multipart/”));
}
其中,strictServletCompliance是StandardServletMultipartResolver的成员变量,默认false,表示是否严格遵守Servlet 3.0规范。简单来说就是对Content-Type校验的严格程度。如果strictServletCompliance为false,请求头以multipart/开头就满足文件请求条件;如果strictServletCompliance为true,则需要请求头以multipart/form-data开头。

3.2 StandardServletMultipartResolver#resolveMultipart#
StandardServletMultipartResolver在解析文件请求时,会将原始请求封装成StandardMultipartHttpServletRequest对象:

public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
需要注意的是,这里传入this.resolveLazily成员变量,表示是否延迟解析。我们可以来看对应构造函数源码:

public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {

super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
如果需要修改resolveLazily成员变量的值,需要在初始化StandardServletMultipartResolver时指定值。
在Spring Boot 2.0+中,默认会在org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration中创建StandardServletMultipartResolver作为默认文件解析器,此时会从MultipartProperties中读取resolveLazily值。因此,如果是使用Spring Boot 2.0+默认配置的文件解析器,可以在properties或.yml文件中指定resolveLazily值:

spring.servlet.multipart.resolve-lazily=true
如果是使用自定义配置的方式配置StandardServletMultipartResolver,则可以在初始化的手动赋值:

@Bean
public MultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(true);
return multipartResolver;
}
3.3 StandardMultipartHttpServletRequest#parseRequest#
当resolveLazily为true时,会马上调用parseRequest()方法会对请求进行实际解析,该方法会完成两件事情:

使用Servlet 3.0的Part API,获取Part集合
解析Part对象,封装表单参数和表单文件
private void parseRequest(HttpServletRequest request) {
try {
Collection parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<>(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
ContentDisposition disposition = ContentDisposition.parse(headerValue);
String filename = disposition.getFilename();
if (filename != null) {
if (filename.startsWith(“=?”) && filename.endsWith(“?=”)) {
filename = MimeDelegate.decode(filename);
}
files.add(part.getName(), new StandardMultipartFile(part, filename));
}
else {
this.multipartParameterNames.add(part.getName());
}
}
setMultipartFiles(files);
}
catch (Throwable ex) {
handleParseFailure(ex);
}
}
经过parseRequest()方法处理,我们在业务处理时,直接调用StandardMultipartHttpServletRequest接口的getXxx()方法就可以获取表单参数或表单文件信息。

当resolveLazily为false时,在MultipartResolver#resolveMultipart()阶段并不会进行文件请求解析。也就是说,此时StandardMultipartHttpServletRequest对象的成员变量都是空值。那么,resolveLazily为false时文件请求解析是在什么时候完成的呢?
实际上,在调用StandardMultipartHttpServletRequest接口的getXxx()方法时,内部会判断是否已经完成文件请求解析。如果未解析,就会调用partRequest()方法进行解析,例如:

@Override
public Enumeration getParameterNames() {
if (this.multipartParameterNames == null) {
initializeMultipart(); // parseRequest(getRequest());
}
// 业务处理……
}
3.4 HttpServletRequest#getParts#
根据StandardMultipartHttpServletRequest#parseRequest源码可以发现,StandardServletMultipartResolver解析文件请求依靠的是HttpServletRequest#getParts方法。
这是StandardServletMultipartResolver是根据标准Servlet 3.0实现的核心体现。
在Servlet 3.0中定义了javax.servlet.http.Part,用来表示multipart/form-data请求体中的表单数据或文件:

public interface Part {
public InputStream getInputStream() throws IOException;
public String getContentType();
public String getName();
public String getSubmittedFileName();
public long getSize();
public void write(String fileName) throws IOException;
public void delete() throws IOException;
public String getHeader(String name);
public Collection getHeaders(String name);
public Collection getHeaderNames();
}
在javax.servlet.http.HttpServletRequest,提供了获取multipart/form-data请求体各个part的方法:

public interface HttpServletRequest extends ServletRequest {
/**
* Return a collection of all uploaded Parts.
*
* @return A collection of all uploaded Parts.
* @throws IOException
* if an I/O error occurs
* @throws IllegalStateException
* if size limits are exceeded or no multipart configuration is
* provided
* @throws ServletException
* if the request is not multipart/form-data
* @since Servlet 3.0
*/
public Collection getParts() throws IOException, ServletException;

/**  
 * Gets the named Part or null if the Part does not exist. Triggers upload     
 * of all Parts.    
 *     
 * @param name The name of the Part to obtain  
 *     
 * @return The named Part or null if the Part does not exist    
 * @throws IOException  
 *             if an I/O error occurs  
 * @throws IllegalStateException  
 *             if size limits are exceeded  
 * @throws ServletException  
 *             if the request is not multipart/form-data  
 * @since Servlet 3.0     
 */    
public Part getPart(String name) throws IOException, ServletException;

}
所有实现标准Servlet 3.0规范的Web服务器,都必须实现getPart()/getParts()方法。也就是说,这些Web服务器在解析请求时,会将multipart/form-data请求体中的表单数据或文件解析成Part对象集合。通过HttpServletRequest的getPart()/getParts()方法,可以获取这些Part对象,进而获取multipart/form-data请求体中的表单数据或文件。
每个Web服务器对Servlet 3.0规范都有自己的实现方式。对于Spring Boot来说,通常使用的是Tomcat/Undertow/Jetty内嵌Web服务器。通常只需要了解这三种服务器的实现方式即可。

3.4.1 Tomcat实现#
Tomcat是Spring Boot默认使用的内嵌Web服务器,只需要引入如下依赖:

org.springframework.boot spring-boot-starter-web 会默认引入Tomcat依赖: org.springframework.boot spring-boot-starter-tomcat Tomcat解析文件请求的核心在于org.apache.catalina.connector.Request#parseParts方法,核心代码如下:

// 1、创建ServletFileUpload文件上传对象
DiskFileItemFactory factory = new DiskFileItemFactory();
try {
factory.setRepository(location.getCanonicalFile());
} catch (IOException ioe) {
parameters.setParseFailedReason(FailReason.IO_ERROR);
partsParseException = ioe;
return;
}
factory.setSizeThreshold(mce.getFileSizeThreshold());

ServletFileUpload upload = new ServletFileUpload();
upload.setFileItemFactory(factory);
upload.setFileSizeMax(mce.getMaxFileSize());
upload.setSizeMax(mce.getMaxRequestSize());
this.parts = new ArrayList<>();
try {
// 2、解析文件请求
List items =
upload.parseRequest(new ServletRequestContext(this));
// 3、封装Part对象
for (FileItem item : items) {
ApplicationPart part = new ApplicationPart(item, location);
this.parts.add(part);
}
}
success = true;
}
核心步骤如下:

创建ServletFileUpload文件上传对象
解析文件请求
封装Part对象
org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest会进行实际解析文件请求:

public List parseRequest(final RequestContext ctx) throws FileUploadException {
final List items = new ArrayList<>();
boolean successful = false;
try {
final FileItemIterator iter = getItemIterator(ctx);
final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
“No FileItemFactory has been set.”);
final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
while (iter.hasNext()) {
final FileItemStream item = iter.next();
// Don’t use getName() here to prevent an InvalidFileNameException.
final String fileName = item.getName();
final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
} catch (final FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (final IOException e) {