HTTP上传

与文件下载相比,文件上传的场合不是很多,通常用于上传用户头像、朋友圈发布图片/视频动态等等,而且上传文件需要服务器配合,所以容易被app开发者忽略。就上传的形式来说,app一般采用http上传文件,很少用ftp上传文件。

HttpURLConnection上传

很可惜Android没有提供专门的文件上传工具类,所以我们要自己写代码实现上传功能了。其实也不难,一样是按照普通网络访问的POST流程,只是要采用“multipart/form-data”方式来分段传输。另外文件上传需要运用打开文件的对话框,文件对话框的介绍参见《 Android开发笔记(二十三)文件对话框FileDialog》。

下面是HttpURLConnection上传文件的工具类代码:

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;public class UploadUtil {private static final String TAG = "UploadUtil";public static String upload(String uploadUrl, String uploadFile) {String fileName = "";int pos = uploadFile.lastIndexOf("/");if (pos >= 0) {fileName = uploadFile.substring(pos + 1);}String end = "\r\n";String Hyphens = "--";String boundary = "WUm4580jbtwfJhNp7zi1djFEO3wNNm";try {URL url = new URL(uploadUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setDoInput(true);conn.setDoOutput(true);conn.setUseCaches(false);conn.setRequestMethod("POST");conn.setRequestProperty("Connection", "Keep-Alive");conn.setRequestProperty("Charset", "UTF-8");conn.setRequestProperty("Content-Type","multipart/form-data;boundary=" + boundary);DataOutputStream ds = new DataOutputStream(conn.getOutputStream());ds.writeBytes(Hyphens + boundary + end);ds.writeBytes("Content-Disposition: form-data; "+ "name=\"file1\";filename=\"" + fileName + "\"" + end);ds.writeBytes(end);FileInputStream fStream = new FileInputStream(uploadFile);// 每次写入1024字节int bufferSize = 1024;byte[] buffer = new byte[bufferSize];int length = -1;// 将文件数据写入到缓冲区while((length = fStream.read(buffer)) != -1) { ds.write(buffer, 0, length);}ds.writeBytes(end);ds.writeBytes(Hyphens + boundary + Hyphens + end);fStream.close();ds.flush();// 获取返回内容InputStream is = conn.getInputStream();int ch;StringBuffer b = new StringBuffer();while ((ch = is.read()) != -1) {b.append((char) ch);}ds.close();return "SUCC";} catch (Exception e) {e.printStackTrace();return "上传失败:" + e.getMessage();}}
}

android-async-http上传

HttpURLConnection需要开发者自己拼接请求包,容易出错,并且处理单一不够灵活,因此涌现了几个开源的http框架,方便开发者进行http通信的编码。android-async-http是这其中使用较多的一个网络请求框架,它的项目地址在 https://github.com/loopj/android-async-http 。

据官方介绍,android-async-http是基于Apache HttpClient库之上的一个异步网络请求处理库,网络处理均基于Android的非UI线程,通过回调方法处理请求结果。这里我们使用该库进行文件上传,主要用到AsyncHttpClient类的post方法,要上传的文件信息放在RequestParams对象中。

下面是android-async-http上传文件的工具类代码:

import java.io.File;import com.example.exmupload.MainActivity;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.RequestParams;import android.util.Log;public class AsyncUtil {private static final String TAG = "AsyncUtil";public static String upload(final MainActivity act, String uploadServlet, String filePath, boolean isBinary) {try {//服务端的commons-fileupload只支持multipart/form-data方式//application/octet-stream表示任意的二进制文件,包括图片、音频、视频、压缩文件等等String contentType = "application/octet-stream";//String contentType = "multipart/form-data";RequestParams params = new RequestParams();if (isBinary == true) {params.put("file", new File(filePath), contentType);} else {params.put("file", new File(filePath));}Log.d(TAG, "contentType="+contentType);AsyncHttpClient client = new AsyncHttpClient();//AsyncHttpResponseHandle不在ui线程运行,不能直接操作ui//如果在此处定义AsyncHttpResponseHandle对象,则运行报错://java.lang.IllegalArgumentException: Synchronous ResponseHandler used in AsyncHttpClient. You should create your response handler in a looper thread or use SyncHttpClient instead.client.post(uploadServlet, params, act.mAsyncHandler);return "uploadByFile";} catch (Exception e) {e.printStackTrace();return e.getMessage();}}// //下面代码在MainActivity.java中定义
//  public AsyncHttpResponseHandler mAsyncHandler = new AsyncHttpResponseHandler() {
//      @Override
//      public void onSuccess(int status, Header[] headers, byte[] data) {
//          String result = "文件上传成功!";
//          Message msg = Message.obtain();
//          msg.what = ASYNC;
//          msg.obj = result;
//          mHandler.sendMessage(msg);
//      }
//
//      @Override
//      public void onFailure(int status, Header[] headers, byte[] data, Throwable e) {
//          String result = "文件上传失败!"+e.getMessage();
//          Message msg = Message.obtain();
//          msg.what = ASYNC;
//          msg.obj = result;
//          mHandler.sendMessage(msg);
//      }
//  };}

Retrofit上传

Retrofit是网络请求框架中的后起之秀,它的项目地址在 https://github.com/square/retrofit 。据官方介绍,Retrofit是一个类型安全的REST客户端,用于Android平台。Retrofit基于注解方式,提供了JSON to POJO(Plain Ordinary Java Object,简单Java对象)、POJO to JSON、网络请求(POST/GET/PUT/DELETE)等通信行为的封装。

Retrofit依赖okhttp库,早期的版本用的是okhttp2(如okhttp-2.5.0.jar),最新的版本如retrofit-2.1.0.jar用的是okhttp3(如okhttp-3.3.1.jar),所以工程引用Retrofit库时,要注意导入正确的okhttp库。这里我们通过文件上传来演示Retrofit的使用方法,因为采用了注解方式,所以不太方便文字说明,还是直接上代码好了。

下面是Retrofit上传文件的工具类代码:

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;import com.example.exmupload.MainActivity;import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.Multipart;
import retrofit2.http.PartMap;
import retrofit2.http.POST;import android.util.Log;
import android.webkit.MimeTypeMap;public class RetrofitUtil {private static final String TAG = "RetrofitUtil";public interface ApiInterface {//这里的POST是不带ip也不带域名的地址,下面uploadHost才带ip/域名和端口,如http://192.168.0.212:8080/@Multipart@POST("UploadTest/uploadServlet")Call<ResponseBody> upload(@PartMap Map<String, RequestBody> params);}public static void upload(final MainActivity act, String uploadHost, String filePath) {File file1 = new File(filePath);Retrofit retrofit = new Retrofit.Builder().baseUrl(uploadHost).build();ApiInterface apiService = retrofit.create(ApiInterface.class);// 获取文件真实的内容类型
//      String mimeType = MimeTypeMap.getSingleton()
//              .getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(file1.getPath()));
//      RequestBody fileBody = RequestBody.create(MediaType.parse(mimeType), filePath);RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), filePath);Map<String, RequestBody> params = new HashMap<>();// 如果要上传多个文件,可对该Map对象进行put操作params.put("file\"; filename=\"" + file1.getName() + "", fileBody);Call<ResponseBody> call = apiService.upload(params);call.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {try {String jsonString = new String(response.body().bytes()); // 这就是返回的json字符串了。Log.d(TAG, "onResponse succ : "+jsonString);act.notify(act.RETROFIT, "onResponse succ "+jsonString);} catch (IOException e) {e.printStackTrace();act.notify(act.RETROFIT, "onResponse "+e.getMessage());}}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable throwable) {Log.d(TAG, "onFailure : "+throwable.getMessage());act.notify(act.RETROFIT, "onFailure "+throwable.getMessage());}});}}

服务器接收文件

commons-fileupload

文件上传需要服务器配合,服务端若用java编码,则可通过commons-fileupload来接收文件。commons-fileupload是个通用的各类网络协议通信包,包括ftp/telnet/smtp/pop3等协议。该包依赖于commons-io库,它们的下载地址如下:
commons-fileupload-1.2.1.jar https://mirrors.tuna.tsinghua.edu.cn/apache/commons/fileupload/binaries/
commons-io-1.4.jar https://mirrors.tuna.tsinghua.edu.cn/apache/commons/io/binaries/

下面是服务端采用commons-fileupload接收文件的代码例子:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;public class UploadServlet extends HttpServlet {private static final long serialVersionUID = 1L;public UploadServlet() {super();}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doPost(request, response);}@SuppressWarnings("unchecked")protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("request.getContentType()="+request.getContentType());response.setContentType("text/html");response.setCharacterEncoding("UTF-8");Writer o = response.getWriter();//首先判断Content-Type是不是multipart/form-data。同时也判断了form的提交方式是不是postif (ServletFileUpload.isMultipartContent(request)) {request.setCharacterEncoding("utf-8");// 实例化一个硬盘文件工厂,用来配置上传组件ServletFileUpload   DiskFileItemFactory factory =  new DiskFileItemFactory();//设置文件存放的临时文件夹,这个文件夹要真实存在File fileDir = new File("../webapps/");if (fileDir.isDirectory() && fileDir.exists()==false) {fileDir.mkdir();}factory.setRepository(fileDir);//设置最大占用的内存factory.setSizeThreshold(1024000);//创建ServletFileUpload对象ServletFileUpload sfu = new ServletFileUpload(factory);sfu.setHeaderEncoding("utf-8");//设置单个文件最大值byte sfu.setFileSizeMax(102400000);//所有上传文件的总和最大值byte   sfu.setSizeMax(204800000);List<FileItem> items =  null;try {items = sfu.parseRequest(request);System.out.println("items.size()="+items.size());} catch(Exception e) {   e.printStackTrace();   }Iterator<FileItem> iter = items==null?null:items.iterator();//文件上传后存放的路径目录File dir = new File("D:/");if (dir.exists()==false) {dir.mkdirs();}  while (iter!=null && iter.hasNext()) {FileItem item = (FileItem) iter.next();if (item.isFormField()) { //如果传过来的是普通的表单域System.out.print("普通的表单域:");   System.out.print(new String(item.getFieldName()) + "  ");   System.out.println(new String(item.getString("UTF-8")));   } else if (!item.isFormField()) { //文件域System.out.println("源文件:" + item.getName());String fileName = item.getName();if (fileName.indexOf("\\") >= 0) {fileName = item.getName().substring(item.getName().lastIndexOf("\\"));}BufferedInputStream in = new BufferedInputStream(item.getInputStream());BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(dir.getAbsolutePath()+ fileName))); Streams.copy(in, out, true);o.write("文件上传成功");}}} else {System.out.println("表单的Content-Type错误");} }}

FTP上传

普通FTP

在从前带宽有限的互联网时代,FTP共享影视资源曾经盛行一时,如今宽带普及了,特别是步入移动互联网时代,FTP用的便越来越少了。不管怎样,作为一个技术手段,我们还是温习一下FTP上传文件的用法,java上可导入commons-net-3.3.jar,该库是集成了常见的网络通讯协议,包括但不限于:ftp、telnet、smtp、pop3等等。

commons-net进行FTP上传用的是FTPClient类,下面是FTPClient上传文件的工具类代码:

import java.io.FileInputStream;
import java.io.IOException;import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;public class FTPUtil {private static String mUurl;private static String mPort;private static String mUsername;private static String mPassword;public static void setUser(String url, String port, String username, String password) {mUurl = url;mPort = port;mUsername = username;mPassword = password;}public static String upload(String remotePath, String filePath, String fileName) {FTPClient ftpClient = new FTPClient();String result = "SUCC";try {ftpClient.connect(mUurl, Integer.parseInt(mPort));boolean loginResult = ftpClient.login(mUsername, mPassword);int returnCode = ftpClient.getReplyCode();if (loginResult && FTPReply.isPositiveCompletion(returnCode)) { //登录成功ftpClient.makeDirectory(remotePath);// 设置上传目录ftpClient.changeWorkingDirectory(remotePath);ftpClient.setBufferSize(1024);ftpClient.setControlEncoding("UTF-8");ftpClient.enterLocalPassiveMode();FileInputStream fis = new FileInputStream(filePath + fileName);ftpClient.storeFile(fileName, fis);} else { //登录失败result = "FAIL";}} catch (IOException e) {e.printStackTrace();result = "FTP客户端出错!" + e.getMessage();} finally {try {ftpClient.disconnect();} catch (IOException e) {e.printStackTrace();result = "关闭FTP连接发生异常!" + e.getMessage();}}return result;}
}

安全的SFTP

SFTP是Secure FTP的简称,顾名思义它比FTP要安全。不过对于普通用户来说,SFTP用得比FTP还少,就不啰嗦了。

调用SFTP可导入JSch库,最新的jar包是jsch-0.1.53.jar,该库操作SFTP主要用ChannelSftp类,下面是JSch上传文件的工具类代码:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import java.util.Vector;import android.util.Log;import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;public class SFTPUtil {private static final String TAG = "SFTPUtil";public static int SUCC = 0;public static int FAIL = -1;//连接sftp服务器public static ChannelSftp connect(String host, int port, String username, String password) {ChannelSftp sftp = null;try {JSch jsch = new JSch();jsch.getSession(username, host, port);Session session = jsch.getSession(username, host, port);session.setPassword(password);Properties config = new Properties();config.put("StrictHostKeyChecking", "no");session.setConfig(config);session.connect();Channel channel = session.openChannel("sftp");channel.connect();sftp = (ChannelSftp) channel;Log.d(TAG, "Connected to " + host + ".");} catch (Exception e) {e.printStackTrace();}return sftp;}//上传文件public static String upload(ChannelSftp sftp, String directory, String uploadFile) {try {sftp.cd(directory);File file = new File(uploadFile);sftp.put(new FileInputStream(file), file.getName());Log.d(TAG, "Upload succ. " + uploadFile);return "SUCC";} catch (Exception e) {e.printStackTrace();return e.getMessage();}}//下载文件public static int download(ChannelSftp sftp, String directory, String downloadFile, String saveFile) {try {sftp.cd(directory);File file = new File(saveFile);sftp.get(downloadFile, new FileOutputStream(file));Log.d(TAG, "Download succ. " + downloadFile);return SUCC;} catch (Exception e) {e.printStackTrace();return FAIL;}}//删除文件public static int delete(ChannelSftp sftp, String directory, String deleteFile) {try {sftp.cd(directory);sftp.rm(deleteFile);Log.d(TAG, "Delete succ. " + deleteFile);return SUCC;} catch (Exception e) {e.printStackTrace();return FAIL;}}//列出目录下的文件public static Vector<?> listFiles(ChannelSftp sftp, String directory) throws SftpException {//Vector容器内部保存的是LsEntry类型对象。return sftp.ls(directory);}}

下面是文件上传(包括http上传和ftp上传)的页面代码例子:

import java.util.Map;import org.apache.http.Header;import com.aqi00.lib.dialog.FileSelectFragment;
import com.aqi00.lib.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmupload.util.AsyncUtil;
import com.example.exmupload.util.FTPUtil;
import com.example.exmupload.util.RetrofitUtil;
import com.example.exmupload.util.SFTPUtil;
import com.example.exmupload.util.UploadUtil;
import com.jcraft.jsch.ChannelSftp;
import com.loopj.android.http.AsyncHttpResponseHandler;import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;public class MainActivity extends Activity implements OnClickListener,FileSelectCallbacks {private static final String TAG = "MainActivity";private static TextView tv_filename;private Button btn_select;private Button btn_upload;private Button btn_async;private Button btn_retrofit;private Button btn_ftp;private Button btn_sftp;private static TextView tv_result;public static String mUploadHost = "http://192.168.0.212:8080/";public static String mUploadService = "UploadTest/uploadServlet";private String mServletUrl = mUploadHost + mUploadService;@Override  public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv_filename = (TextView) findViewById(R.id.tv_filename);btn_select = (Button) findViewById(R.id.btn_select);btn_upload = (Button) findViewById(R.id.btn_upload);btn_async = (Button) findViewById(R.id.btn_async);btn_retrofit = (Button) findViewById(R.id.btn_retrofit);btn_ftp = (Button) findViewById(R.id.btn_ftp);btn_sftp = (Button) findViewById(R.id.btn_sftp);tv_result = (TextView) findViewById(R.id.tv_result);btn_select.setOnClickListener(this);btn_upload.setOnClickListener(this);btn_async.setOnClickListener(this);btn_retrofit.setOnClickListener(this);btn_ftp.setOnClickListener(this);btn_sftp.setOnClickListener(this);}@Overridepublic void onClick(View v) {String file_path = tv_filename.getText().toString().trim();if (v.getId() == R.id.btn_select) {FileSelectFragment.show(this, new String[]{"jpg","png","gif","txt"}, null);return;}if (file_path.length() <= 0) {Toast.makeText(this, "请选择要上传的文件", Toast.LENGTH_LONG).show();return;}if (v.getId() == R.id.btn_upload) {tv_result.setText("开始HttpURLConnection上传");new ConnnectionThread(file_path).start();} else if (v.getId() == R.id.btn_async) {tv_result.setText("开始async上传");new AsyncThread(file_path).start();} else if (v.getId() == R.id.btn_retrofit) {tv_result.setText("开始retrofit上传");new RetrofitThread(file_path).start();} else if (v.getId() == R.id.btn_ftp) {new FTPThread(file_path).start();} else if (v.getId() == R.id.btn_sftp) {new SFTPThread(file_path).start();}}public void notify(int type, String message) {Message msg = Message.obtain();msg.what = type;msg.obj = message;mHandler.sendMessage(msg);}private class ConnnectionThread extends Thread {private String mFilePath;public ConnnectionThread(String file_path) {mFilePath = file_path;}@Overridepublic void run() {String result = UploadUtil.upload(mServletUrl, mFilePath);MainActivity.this.notify(COMMON, "upload :" + result);}}private class AsyncThread extends Thread {private String mFilePath;public AsyncThread(String file_path) {mFilePath = file_path;}@Overridepublic void run() {String result = AsyncUtil.upload(MainActivity.this, mServletUrl, mFilePath, true);MainActivity.this.notify(ASYNC, "upload :" + result);}}public AsyncHttpResponseHandler mAsyncHandler = new AsyncHttpResponseHandler() {@Overridepublic void onSuccess(int status, Header[] headers, byte[] data) {MainActivity.this.notify(ASYNC, "文件上传成功!");}@Overridepublic void onFailure(int status, Header[] headers, byte[] data, Throwable e) {MainActivity.this.notify(ASYNC, "文件上传失败!"+e.getMessage());}};private class RetrofitThread extends Thread {private String mFilePath;public RetrofitThread(String file_path) {mFilePath = file_path;}@Overridepublic void run() {RetrofitUtil.upload(MainActivity.this, mUploadHost, mFilePath);}}private class FTPThread extends Thread {private String mFilePath;public FTPThread(String file_path) {mFilePath = file_path;}@Overridepublic void run() {String filepath = mFilePath.substring(0, mFilePath.lastIndexOf("/")+1);String filename = mFilePath.substring(mFilePath.lastIndexOf("/")+1);FTPUtil.setUser("172.16.16.146", "21", "testftp", "testftp");String result = FTPUtil.upload("/home/testftp", filepath, filename);MainActivity.this.notify(FTP, result);}}private class SFTPThread extends Thread {private String mFilePath;public SFTPThread(String file_path) {mFilePath = file_path;}@Overridepublic void run() {ChannelSftp sftp = SFTPUtil.connect("172.16.16.133", 22, "oracle", "oracle");String result = SFTPUtil.upload(sftp, "/home/oracle", mFilePath);MainActivity.this.notify(SFTP, result);}}public int COMMON = 0;public int ASYNC = 1;public int RETROFIT = 2;public int FTP = 3;public int SFTP = 4;@SuppressLint("HandlerLeak")public Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {String result = (String) msg.obj;if (msg.what == COMMON) {tv_result.setText(tv_result.getText().toString()+"\n"+result);} else if (msg.what == ASYNC) {tv_result.setText(tv_result.getText().toString()+"\n"+result);} else if (msg.what == RETROFIT) {tv_result.setText(tv_result.getText().toString()+"\n"+result);} else if (msg.what == FTP) {showTip("FTP文件上传结果:"+result);} else if (msg.what == SFTP) {showTip("sftp文件上传结果:"+result);}}};private void showTip(String desc) {Toast.makeText(getApplicationContext(), desc, Toast.LENGTH_SHORT).show();}@Overridepublic void onConfirmSelect(String absolutePath, String fileName,Map<String, Object> map_param) {String path = absolutePath + "/" + fileName;tv_filename.setText(path);}@Overridepublic boolean isFileValid(String absolutePath, String fileName,Map<String, Object> map_param) {return true;}}

点击下载本文用到的文件上传的工程代码

点此查看Android开发笔记的完整目录

Android开发笔记(一百一十)使用http框架上传文件相关推荐

  1. Android开发笔记(五十九)巧用传感器

    传感器Sensor 传感器是Android用来感知周围环境以及运动信息的工具.因为具体的感应信息依赖于相关硬件,所以虽然Android提供了众多的感应器,但不是每部手机都能支持这么多感应器,恰恰相反, ...

  2. Android开发笔记(八十九)单例模式

    基本概念 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,从而方便对实例个数的控制并节约系统资源. 单例模式有三个特点: 1.某个类只能有一个实例: 2.它要自行创建这个实例: 3.它只有 ...

  3. Android开发笔记(八十八)同步与加锁

    同步synchronized 同步方法 synchronized可用来给方法或者代码块加锁,当它修饰一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.这就意味着,当两个并发线程同时访 ...

  4. Android开发笔记(八十六)几个特殊的类

    接口interface interface是一些功能的集合,但它只定义了对象必须实现的成员,而不包含成员的实现代码,成员的具体代码由实现接口的类提供.Android对接口的使用场景主要有三类:事件监听 ...

  5. Android开发笔记(八十)运行状态检查

    大家都知道刻舟求剑的寓言故事,说的是事物是发展变化着的,如果拘泥于原来的情况,那随着情况的改变,就不会得到预期的结果.同样,影响app运行的因素,并不只是外部环境(如硬件.系统.权限等等),还包括ap ...

  6. Android开发笔记(七十九)资源与权限校验

    硬件资源 因为移动设备的硬件配置各不相同,为了防止使用了不存在的设备资源,所以要对设备的硬件情况进行检查.一般情况下,前置摄像头.部分传感器在低端手机上是没有的,像SD卡也可能因为用户没插卡使得找不到 ...

  7. Android开发笔记(七十五)内存泄漏的处理

    内存泄漏的原因 一直以来以为只有C/C++才存在内存泄漏的问题,没想到拥有内存回收机制的Java也可能出现内存泄漏.C/C++存在指针的概念,程序中需要使用指针变量时,就从内存中开辟一块区域,并把该区 ...

  8. Android开发笔记(七十四)布局文件优化

    include/merge 布局优化中常常用到include/merge标签,include的含义类似C代码中的include,意思是直接把指定布局片段包含进当前的布局文件.include适用于多个布 ...

  9. Android开发笔记(六十八)工程库打包

    写好一个Android模块,比如说一个自定义控件或某个功能的sdk,然后开放出来给别人使用,就得通过某种方式把源码提供给对方.常见的打包方式有: 一.直接给源码,由开发者把代码加入到自己的工程中 该方 ...

  10. Android开发笔记(六十六)自定义对话框

    AlertDialog Android中最常用的对话框是AlertDialog,它可以完成常见的交互操作,如提示.确认.选择等等,然后就是进度对话框ProgressDialog(参见< Andr ...

最新文章

  1. php表单ajax,PHP表单到Ajax类型
  2. 接口冲突的一种解决方法
  3. 用jenkins搭建android自动打包环境
  4. FreeSql与SqlSugar性能测试对比
  5. docker 搜寻 包 版本_ubuntu16.04下安装docker容器
  6. js之 foreach, map, every, some
  7. Win7系统C盘空间不足
  8. 九、Linux系统安装和常见故障排除
  9. Hitool网口烧写失败问题
  10. PIR控制器调节器并网逆变器电流谐波抑制策略
  11. 联想云计算机终端,联想智能云教室系统 V1.3.20.1109_C201105 最新官网版本
  12. matlabR2020A mingw安装
  13. AlphaTensor横空出世!打破矩阵乘法计算速度50年纪录,DeepMind新研究再刷Nature封面,详细算法已开源...
  14. 来世你还能和你的父母重逢吗?
  15. mx550和锐炬xe显卡差距大 锐炬xe显卡和mx550区别哪个好
  16. LTE 怎么从信令里提取 IMSI
  17. gensim.corpora中Dictionaryd的用法
  18. matlab命令行窗口显示长度设置_MATLAB中如何设置坐标轴的显示长度?
  19. vue进入浏览器大屏
  20. 2015年 行人检测总结1

热门文章

  1. Knowledge Integration Networks for Action Recognition AAAI 2020
  2. Java基础(一)——基本语法
  3. Linux无root权限安装opencv3.4.0以及一些问题
  4. HDU 3584 三维树状数组
  5. 【四】Jmeter:逻辑控制器
  6. c++语言用文件输入数值,C++ 基本的输入输出
  7. 项目解析jsx文件_神奇了!这个 Go 语言项目让前端构建快了近 100 倍
  8. mysql数据索引失效_MySQL索引失效的几种情况
  9. 推荐一款Linux服务器连接工具FinalShell
  10. Chrome浏览器最新版驱动包下载