android文件上传oom
① android app上传文件速度太慢怎么处理
一、释放XP自带保留20%的带宽,让你的网速更快! 1、运行组策略编辑器程序(gpedit.msc)。在“‘本地计算机’策略”中,逐级“计算机配置”→“管理模板”→“网络(互联网)”→“QoS数据包调度程序”分支。在屏幕右边会出现“QoS数据包调度程序”策略。接着单击右边子项目的“限制可保留带宽”。这时,左边会显示“限制可保留带宽”的清楚描述。从这里我们可了解到“限制可保留带宽”的有些基本情形。了解之后我们就可以对“限制可保留带宽”进行设置了。单击“限制可保留带宽”下“显示”旁边的“属性”(或选取子项目“限制可保留带宽”,再点击右键→“属性”也可),出现“限制可保留带宽”对话框,先点击“说明”,再进一步了解“限制可保留带宽”确定系统(System)可保留的连接带宽的百分比情形。 之后我们就可以对另外20%带宽进设置了。点击“设置”。“设置”为我们提供了三个选取(未配置、已启用、已禁用),选取“已启用”,接着再将带宽限制旁边的%设置为0%即可,之后按确定退出。 2、单击“开始”→“连接到”→“显示全部连接”。 选中你所建立的连接,用鼠标右键单击属性,在出现的连接属性中单击网络(互联网),在显示的网络(互联网)对话框中,检查“此连接用下列项目”中“QoS数据包调度程序”是不是已打了勾,没问题就按确定退出。 3、最后重新开启系统(System)便完成对另外20%的频宽利用了 二、清理 ①清空Internet临时文件夹 他人查看“Internet临时文件夹”下的图、Flash等文件便能大体知道你曾到过的网站。要清理它们,可依次单击IE菜单栏中的“工具”→“Internet选项”,打开(OPEN)“Internet选项”对话框,在“常规”标签中点击“删掉文件”按钮,在弹出的“删掉文件”窗口中勾选“删掉全部脱机内容”,最后点击“确定”。 三、关掉上网设备猫和路由,十多分钟后再开机 四、还有网络(互联网)网站问题
② android post 提交数据过大 会导致oom 求解决方案
你的图有点小。看不清。。。。你用的是广泛流行的OKHttp?还是Xutils。别用volley那个只适合显示小部分数据。既然要上传数据还是大数据就换个框架吧,认真的
③ android uil oom 怎么处理
对于OOM,其实最重要的是注意不要Memory Leak。而Memory Leak是会有多个方面会引起的,比如Drawable, RemoteViews, Receiver, Cursor,InputStream, MediaPlayer等,此外,如果使用JNI也会因为C或C++的代码导致Memory Leak。
除了Memory Leak,大数据量的操作也会导致OOM,比如之前其他回答提到的Bitmap,还有ListAdapter,如果在getView时处理不当,也很容易导致OOM,所以在ListAdapter时应该尽量使用convertView。
最后,可以用android.os.StrictMode以及Eclipse的MAT工具来进行OOM和Memory Leak的检测。
④ android何时会发生oom怎么解决oom
首先,OOM就是内存溢出,即Out Of Memory。也就是说内存占有量超过了VM所分配的最大。
怎么解决OOM,通常OOM都发生在需要用到大量内存的情况下(创建或解析Bitmap,分配特大的数组等),在这样的一种情况下,就可能出现OOM,据我现在了解到,多数OOM都是因为Bitmap太大。所以,这里我就专门针对如何解决Bitmap的OOM。其实最核发的就是只加载可见范围内的Bitmap,试想这样一种情况,在GridView或ListView中,数据量有5000,每一屏只显示20个元素,那么不可见的,我们是不需要保存Bitmap在内在中的。所以我们就是只把那么可见的Bitmap保留在内存中,那些不可见的,就释放掉。当元素滑出来时,再去加载Bitmap。
这里我有两种方式,都可以避免OOM。
⑤ android实现文件上传的功能
我是这样做的
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(intent, "请选择一个要上传的文件"), 1);
然后选择文件后调用
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
Uri uri = data.getData();
String url= uri.toString();
}
}
获得路径,根据路径调用
public String convertCodeAndGetText(String str_filepath) {// 转码\
try {
File file1 = new File(str_filepath);
file_name = file1.getName();
FileInputStream in = new FileInputStream(file1);
byte[] buffer = new byte[(int) file1.length() + 100];
int length = in.read(buffer);
load = Base64.encodeToString(buffer, 0, length,
Base64.DEFAULT);
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return load;
}
对文件进行编码
⑥ 如何解决上传多张图片时遇到的oom问题
一、OOM问题出现的场景和原因
一个好的app总少不了精美的图片,所以Android开发中图片的加载总是避免不了的,而在加载图片过程中,如果处理不当则会出现OOM的问题。那么如何彻底解决这个问题呢?本文将具体介绍这方面的知识。
首先我们来总结一下,在加载图片过程中出现的OOM的场景无非就这么几种:
1、 加载的图片过大
2、 一次加载的图片过多
3、 以上两种情况兼有
那么为什么在以上场景下会出现OOM问题呢?实际上在API文档中有着明确的说明,出现OMM的主要原因有两点:
1、移动设备会限制每个app所能够使用的内存,最小为16M,有的设备分配的会更多,如24、32M、64M等等不一,总之会有限制,不会让你无限制的使用。
2、在andorid中图片加载到内存中是以位图的方式存储的,在android2.3之后默认情况下使用ARGB_8888,这种方式下每个像素要使用4各字节来存储。所以加载图片是会占用大量的内存。
场景和原因我们都分析完了,下面我们来看看如何解决这些问题。
二、解决大图加载问题
首先先来解决大图加载的问题,一般在实际应用中展示图片时,因屏幕尺寸及布局显示的原因,我们没有必要加载原始大图,只需要按照比例采样缩放即可。这样即节省内存又能保证图片不失真,具体实施步骤如下:
1、在不加载图片内容的基础上,去解码图片得到图片的尺寸信息
这里需要用的BitmapFactory的decode系列方法和BitmapFactory.Options。当使用decode系列方法加载图片时,一定要将Options的inJustDecodeBounds属性设置为true。
BitmapFactory.Options ptions = new BitmapFactory.Options(); options.inJustDecodeBounds=true; BitmapFactory.decodeFile(path, options);2、根据获取的图片的尺寸和要展示在界面的尺寸计算缩放比例。public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height reqHeight || width reqWidth) { if (width height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; }3、根据计算的比例缩放图片。//计算图片的缩放比例 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; Bitmap bitmap= BitmapFactory.decodeFile(path, options);
根据缩放比例,会比原始大图节省很多内存,效果图如下:
三、批量加载大图
下面我们看看如何批量加载大图,首先第一步还是我们上面所讲到的,要根据界面展示图片控件的大小来确定图片的缩放比例。在此我们使用gridview加载本地图片为例,具体步骤如下:
1、通过系统提供的contentprovider加载外部存储器中的所有图片地址private void loadPhotoPaths(){ Cursor cursor= getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while(cursor.moveToNext()){ String path = cursor.getString(cursor.getColumnIndex(MediaColumns.DATA)); paths.add(path); } cursor.close(); }2、自定义adapter,在adapter的getview方法中加载图片@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder=null; if(convertView==null){ convertView = LayoutInflater.from(this.mContext).inflate(R.layout.grid_item_layout, null); holder = new ViewHolder(); holder.photo=(ImageView)convertView.findViewById(R.id.photo); convertView.setTag(holder); }else{ holder=(ViewHolder)convertView.getTag(); } final String path = this.paths.get(position); holder.photo.setImageBitmap(imageLoader.getBitmapFromCache(path)); return convertView; }
通过以上关键两个步骤后,我们发现程序运行后,用户体验特别差,半天没有反应,很明显这是因为我们在主线程中加载大量的图片,这是不合适的。在这里我们要将图片的加载工作放到子线程中进行,改造自定义的ImageLoader工具类,为其添加一个线程池对象,用来管理用于下载图片的子线程。
private ExecutorService executor; private ImageLoader(Context mContxt) { super(); executor = Executors.newFixedThreadPool(3); } //加载图片的异步方法,含有回调监听 public void loadImage(final ImageView view, final String path, final int reqWidth, final int reqHeight, final onBitmapLoadedListener callback){ final Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: Bitmap bitmap = (Bitmap)msg.obj; callback.displayImage(view, bitmap); break; default: break; } } }; executor.execute(new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmapInBackground(path, reqWidth, reqHeight); putBitmapInMemey(path, bitmap); Message msg = mHandler.obtainMessage(1); msg.obj = bitmap; mHandler.sendMessage(msg); } }); }
通过改造后用户体验明显好多了,效果图如下:
虽然效果有所提升,但是在加载过程中还存在两个比较严重的问题:
1、 图片错位显示
2、 当我们滑动速度过快的时候,图片加载速度过慢
经过分析原因不难找出,主要是因为我们时候holder缓存了grid的item进行重用和线程池中的加载任务过多所造成的,只需要对程序稍作修改,具体如下:
Adapter中:
holder.photo.setImageResource(R.drawable.ic_launcher); holder.photo.setTag(path); imageLoader.loadImage(holder.photo, path, DensityUtil.dip2px(80), DensityUtil.dip2px(80), new onBitmapLoadedListener() { @Override public void displayImage(ImageView view, Bitmap bitmap) { String imagePath= view.getTag().toString(); if(imagePath.equals(path)){ view.setImageBitmap(bitmap); } } });
ImageLoader中:
executor.execute(new Runnable() { @Override public void run() { String key = view.getTag().toString(); if (key.equals(path)) { Bitmap bitmap = loadBitmapInBackground(path, reqWidth, reqHeight); putBitmapInMemey(path, bitmap); Message msg = mHandler.obtainMessage(1); msg.obj = bitmap; mHandler.sendMessage(msg); } } });
为了获得更好的用户体验,我们还可以继续优化,即对图片进行缓存,缓存我们可以分为两个部分内存缓存磁盘缓存,本文例子加载的是本地图片所有只进行了内存缓存。对ImageLoader对象继续修改,添加LruCache对象用于缓存图片。
private ImageLoader(Context mContxt) { super(); executor = Executors.newFixedThreadPool(3); //将应用的八分之一作为图片缓存 ActivityManager am=(ActivityManager)mContxt.getSystemService(Context.ACTIVITY_SERVICE); int maxSize = am.getMemoryClass()*1024*1024/8; mCache = new LruCachestring, bitmap=""(maxSize){ @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight(); } }; } //存图片到缓存 public void putBitmapInMemey(String path,Bitmap bitmap){ if(path==null) return; if(bitmap==null) return; if(getBitmapFromCache(path)==null){ this.mCache.put(path, bitmap); } } public Bitmap getBitmapFromCache(String path){ return mCache.get(path); }/string,
在loadImage方法中异步加载图片前先从内存中取,具体代码请下载案例。
四、总结
总结一下解决加载图片出现OOM的问题主要有以下方法:
1、 不要加载原始大图,根据显示控件进行比例缩放后加载其缩略图。
2、 不要在主线程中加载图片,主要在listview和gridview中使用异步加载图片是要注意处理图片错位和无用线程的问题。
3、 使用缓存,根据实际情况确定是否使用双缓存和缓存大小。
小伙伴们看懂了嘛?想要自己测试的,可以点击下载工程运行测试哦!
⑦ android开发内存优化之如何有效避免oom
减小对象的内存占用
内存对象的重复利用
避免对象的内存泄露
内存使用策略优化
设计风格很大程度上会影响到程序的内存与性能,相对来说,如果大量使用类似Material Design的风格,不仅安装包可以变小,还可以减少内存的占用,渲染性能与加载性能都会有一定的提升。
内存优化并不就是说程序占用的内存越少就越好,如果因为想要保持更低的内存占用,而频繁触发执行gc操作,在某种程度上反而会导致应用性能整体有所下降,这里需要综合考虑做一定的权衡。
Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义。
⑧ android 图片转BASE64上传提示java.lang.OutOfMemoryError
我最近也碰到了这个问题,但是网上没有找到相关有效的直接解决方法。
后来看到了一篇解释base64编码原理的文章,研究了一番后解决了。
一般碰到这个问题的,都涉及到"大文件上传"的问题,"大文件上传"过程中除了base64编码时可能OOM,其实还有其他问题,虽然提问中没有提出,可能是因为这个问题还没有解决,所以还没有遇到其它问题,我就围绕着"大文件上传"来解决这个问题吧。
(提问时间在下看的清楚)
————————————————————
做项目的过程中碰到一个需求:
在java客户端,使用http通信,把客户端的本地文件通过http发送上传到服务器;
请求格式是xml(不管是json还是xml都是字符串,所以这个无所谓),中间包含[文件流字符串];
之前的做法是,把文件流通过base64编码转换为base64Byte,然后和其它字符串信息放到一起,post的时候通过HttpURLConnection的write方法写入到服务器中去,这个上传的过程就完成了。
——————————
但是碰到一个问题,当文件体积较大时,从文件流转换成base64Byte后,体积会很大,可能会导致OOM;
(以二进制流的方式保存,体积最小;以byte数组的方式保存,体积会相对变大一些;以String形式保存,体积最大;)
出错原因是:
FileInputStream fis = new FileInputStream(file); //这一步打开了一个对准file准备进行读取的文件指针,但是还没有开始读写,file的相关数据没有从本地加载到内存中来;所以即使file的体积有10G那么大,这一步也是不会OOM的
//把文件流转换为字节数组
byte[] fileBytes;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] byteBuf = new byte[1024];
int count;
while((count=fis.read(buf))!=-1)
{
baos.write(buf,0,count); //实际上,如果文件体积比较大的话,不用转码,在这一步就可能OOM了
}
fileBytes= baos.toByteArray();
byte[] base64Bytes = Base64.encodeBase64(fileBytes); //在这一步也可能OOM
(文件转换为byte[]时,是有可能OOM的;而转换为base64Bytes后,体积会增大1/3,所以有可能前一步没有OOM,却在这一步出现OOM;
为什么转码后体积会增大1/3,后面我会解释)
——————————
解决方法
既然file在本地没有加载到内存来的时候不会出现内存溢出的情况,我就想到了一个解决的方法:分段上传
(加大内存并不能从根本上解决内存溢出的问题,问题的根本原因不是内存不够大,而是代码有问题)
在本地的file通过HttpURLConnection的getOutputStream()进行write时,不是一次性全部写入,而是循环配合flush进行写入:
FileInputStream fis = new FileInputStream(file);
byte[] buf = new byte[1024];
int count;
while((count = fis.read(buf)) != -1)
{
os.write(Base64.encodeBase64(buf), 0, count);
os.flush();
}
(我从本地读1024字节,然后马上上传到服务器,清空本地缓存,然后再从本地读1024字节,这样循环读取,即使文件有20G,理论上也不有OOM问题出现,因为我从本地文件中读到的数据不会在内存中驻留)
——————————
解决问题的思路对了,但是出现了其他的细节问题
os.write(Base64.encodeBase64(buf), 0, count); //这一行代码报错了,出现了OOM
我搜集了一下资料,发现原因是:
HttpURLConnection的getOutputStream的实际对象是sun.net.<a href="http://www.http.PosterOutputStream" target="_blank">www.http.PosterOutputStream</a>,这个对象的flush方法代码是空的,write配合flush,并没有达到即时上传数据的效果。PosterOutputStream其实是自己在本地维护了一个缓冲区,你通过write写入的数据其实还是在这个本地的缓冲区里,只有当你getInputStream后,HttpURLConnection才会把这段缓冲区中的数据上传到服务器上。而flush达不到上传数据,清空本地缓存的效果。
——————————
(我是不能通过getInputStream来刷新缓冲流的,因为那就不是分段上传而是"分次"上传了)
那这就不是我的思路的问题了。再去搜索解决方法后,得知:
在创建HttpURLConnection对象的时候,要调用一个方法
hurlc.setChunkedStreamingMode(1024); //设置分块流模式 也就是分块上传 1024是getOutputStream维护的本地缓冲区的大小
调用该方法后,只要本地缓存区满了,HttpURLConnection就会自动把缓冲区里的数据发送到服务器,同时清空本地缓存(ps:HttpURLConnection的getOutputStream似乎是个抽象的工厂方法,在调用setChunkedStreamingMode方法后,我发现getOutputStream获取到的实例对象从sun.net.<a href="http://www.http.PosterOutputStream" target="_blank">www.http.PosterOutputStream</a>变成了sun.net.<a href="http://www.protocol.http.HttpURLConnection$StreamingOutputStream" target="_blank">www.protocol.http.HttpURLConnection$StreamingOutputStream</a>)
——————————
果然,调用setChunkedStreamingMode方法后,os.write(Base64.encodeBase64(buf), 0, count);没有再出现OOM异常了
但是,又出现了一个新的问题
我发现
FileInputStream fis = new FileInputStream(file);
byte[] buf = new byte[1024];
int count;
while((count = fis.read(buf)) != -1)
{
os.write(Base64.encodeBase64(buf), 0, count);
os.flush();
}
这段分段编码写入的代码,其编码所得结果,与非分段编码所得结果是不一样的
通过分段编码上传的图片内容出现了错误
我通过下面代码测试:
//分段编码
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] buf1 = new byte[1024];
int count1;
while((count1 = file1.read(buf1)) != -1)
{
os1.write(Base64.encodeBase64(buf1), 0, count1);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
//非分段编码
ByteArrayOutputStream os2 = new ByteArrayOutputStream();
InputStream file2 = new FileInputStream(path);
byte[] buf2 = new byte[1024];
int count2;
while((count2 = file2.read(buf2)) != -1)
{
os2.write(buf2, 0, count2);
os2.flush();
}
file2.close();
System.out.println(new String(Base64.encodeBase64(os2.toByteArray())));
两者的结果:
/9j/4AAQSkZJR...wDtUAVs7eF...
/9j/4AAQSkZJR...wDt89ymnxJ...
前面一段还是相同的,转到后面,就开始南辕北辙了
——————————
原因我去网上找了一下但是没有找到直接答案,但是看到一篇解释base64编码原理的文章
原文链接:<a href="http://www.cnblogs.com/luguo3000/p/3940197.html" target="_blank">http://www.cnblogs.com/luguo3000/p/3940197.html</a>
假设有文件A(txt文件,包含文本内容"ABCDEFG"),转换为InputStream->byte[]后
它们的ASIIC码分别对应65、66、67、68、69、70、71
二进制表现形式为:
1000001 1000010 1000011 1000100 1000101 1000110 1000111
对高位补零后:
01000001 01000010 01000011 01000100 01000101 01000110 01000111
在内存中的实际表现:
而base64编码,使用的字符包括(A-Z、a-z、0-9、+、/、=)这些常规可读字符,使用base64编码的原因,用途,在于把一些乱码字符、不可读字符转换为常规可读字符;
(因为java底层的通信协议、或者说其它的通信协议,很多地方用到远程通信这一块的,对一些乱码字符不支持传输,所以需要把乱码字符转换成常规可读字符才能进行传输)
比如对于'矙'这个字符,部分传输协议的编码集就不认识它,所以无法直接传输,必须base64转码
'矙'的UTF-8编码值为30681,二进制表现形式为111011111011001->(0)111011111011001
需要两个字节来存储01110111 11011001
base64编码只有(A-Z、a-z、0-9、+、/、=)这些字符来表示。需要强调的是,在base64编码规范中,字符'A'不等于65、'B'也不是66...。base64字符与数值(二进制值)的对应关系如下:
也就是说,常规字符'A'=65=01000001;而base64字符'A'=0=00000000;
base64字符代表的二进制值是无法直接表示'矙'这个字符的,因为base64字符的值范围在0~63之间(二进制值在(00)000000~(00)111111之间)。
那如何通过(00)000000~(00)111111之间的数值来表示01110111 11011001呢?
这就是base64的编码算法了
一个base64字符的二进制值在(00)000000~(00)111111之间,也就是说它可以表示000000~111111之间的二进制数,一个base64字符的有效位为后6位。如何通过以6bit为单位的base64字符表示以8bit为单位的常规字节?
6和8的最小公倍数为24,即 每4个base64字符可以表示3个常规字节;
回到刚才的文件A,编码过程:
(初始文件A)->"ABCDEFG"
(转UTF-8码 int)->65 66 67 68 69 70 71
("ABCDEFG"的二进制表示;7字节)->1000001 1000010 1000011 1000100 1000101 1000110 1000111
(高位补零)->01000001 01000010 01000011 01000100 01000101 01000110 01000111
(连写)->
(按6bit为单位对所有bit进行分割;得到10字节)->010000 010100 001001 000011 010001 000100 010101 000110 010001 11
(按6bit*4=8bit*3的对应关系再分割;得到3组6*4字节)->(010000 010100 001001 000011) (010001 000100 010101 000110) (010001 11)
(高位补2个零;末尾的低位也补零)->(00010000 00010100 00001001 00000011) (00010001 00000100 00010101 00000110) (00010001 00110000)
(二进制值换算成十进制)->(16 20 9 3) (17 4 21 6) (17 48)
(按base64编码的值-字符对应表,得出上面的十进制值对应的base64字符)->(Q U J D) (R E V G) (R w)
(每组base64字符都要求是4个,空白的位置补'='字符)->(Q U J D) (R E V G) (R w = =)
(文件A的最终转码结果)->QUJDREVGRw==
这里以文本文件作为演示,因为文本文件机器可读人也可读;实际情况中,很多时候转码的目标文件并不是文本文件,那就不能以可读字符串形式表示了,会直接以二进制格式表示
体积增大的原因,是因为3字节=24bit=分割成4个6bit-,对4个6bit高位补零后,就得到4个字节
也就是说3个常规字节经base64编码后会生成4个base64字节,这就是文件经base64转码后体积会增加1/3的原因
——————————
base64编码原理解释了,再看刚才的分段编码
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] buf1 = new byte[1024];
int count1;
while((count1 = file1.read(buf1)) != -1)
{
os1.write(Base64.encodeBase64(buf1), 0, count1); //可以发现一个问题:Base64.encodeBase64(buf1)编码后,体积会增加1/3,所以这里的Base64.encodeBase64(buf1)编码转换后的实际长度和count1并不相等,所以实际写入到os1中的base64字符数只有Base64.encodeBase64(buf1)编码产生的字符数的3/4
os1.flush();
}
file1.close();
System.out.println(os1.toString());
修改后:
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] byteBuf = new byte[1024];
byte[] base64ByteBuf;
while(file1.read(byteBuf) != -1)
{
base64ByteBuf = Base64.encodeBase64(byteBuf);
os1.write(base64ByteBuf, 0, base64ByteBuf.length);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
——————————
修改后,发现分段编码的结果发生了变化,跟之前不一样了
但仍然不是正确的结果
原因在于,base64字符的基础单位是(6bit*4=4字节),而3个常规字节(8bit*3)才能刚好产生4个base64字节
根本原因在于,如果进行编码的常规字节数不是3的倍数,最后就会余下1或2个字节,而这1、2个字节编码的结果就会产生'='字符;
使用1024作为分段编码缓冲时,编码的结果是3+3+3+...+1
也就是每次都会余1字节
而没有使用分段编码时,当编码到第1024个字节时,"余下"的1字节会跟后面的字节形成连续,就不会产生'='字符
(对一段byte字符进行base64编码时,中间是绝不会产生'='字符的,因为只有在结尾才可能余下1或2个字节,所以对一段byte字符进行编码时,只有结尾才可能产生1或2个'='补全字符)
——————————
解决方法是,使用3的公倍数作为缓冲区大小
修改后:
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] byteBuf = new byte[3*1000];
byte[] base64ByteBuf;
while(file1.read(byteBuf) != -1)
{
base64ByteBuf = Base64.encodeBase64(byteBuf);
os1.write(base64ByteBuf, 0, base64ByteBuf.length);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
测试结果再次发生了改变
中间不再有'='字符了,因为中间每次都是3字节3字节的编码,没有余下多余的字节
对比之后发现,中间段的结果已经正常了
——————————
但是,发现,结尾处两个转码的结果有些许不同
原因在于,假设文件A的长度为3001个字节;
在第二次循环读取时,只读到1个有效字节,而byteBuf的剩余2999个字节都是无效字节,而此时编码时,却把多余的2999个无效字节也编码了进去
(如果是非分段转码,就不会出现这种情况)
解决方法:
ByteArrayOutputStream os1 = new ByteArrayOutputStream();
InputStream file1 = new FileInputStream(path);
byte[] byteBuf = new byte[3*1000];
byte[] base64ByteBuf;
int count1; //每次从文件中读取到的有效字节数
while((count1=file1.read(byteBuf)) != -1)
{
if(count1!=byteBuf.length) //如果有效字节数不为3*1000,则说明文件已经读到尾了,不够填充满byteBuf了
{
byte[] = Arrays.Of(byteBuf, count1); //从byteBuf中截取包含有效字节数的字节段
base64ByteBuf = Base64.encodeBase64(); //对有效字节段进行编码
}
else
{
base64ByteBuf = Base64.encodeBase64(byteBuf);
}
os1.write(base64ByteBuf, 0, base64ByteBuf.length);
os1.flush();
}
file1.close();
System.out.println(os1.toString());
至此,base64分段编码才算大功告成。大文件上传核心代码才算大功告成。
其实代码改起来非常简单,但是不知道原因不知道原理的话,是无法无中生有的
对我本人来说原本只是想随便答一下,但没想到答的过程中发现自己有很多坑没有发现。答的过程中把自己不懂的地方没有发现的坑也完善了。不说碰到一个知识点就要追根究底,但实际开发中,每一个自己能亲身碰到的实际问题都是锻炼自己的绝佳机会,这种近距离触碰问题、解决问题的机会是难得的。虽然开发中还有很多其它问题也很重要,但你没有亲手碰到过,是无法共鸣的。所以自己在开发中碰到了问题,还是建议大概弄清原因。
弄清原理后,即使以后出现这个问题的其它"变种",也能找到原因并自己解决,但仅仅粘贴复制无法做到这一点。
⑨ android 怎么避免oom
在 Java中,JavaVM拥有自动管理内存的功能,Java的GC能够进行垃圾回收,但是Android中如果ImageView使用过多的Bitmap的话,经常会报OOM(内存溢出)。 造成内存溢出及解决方案: 1.使用BitmapFactory.decodeStream替代createBitmap方法 原因是该方法直读取图片字节,调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap。 2.使用压缩读取技术 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageSdUri, options); final int height = options.outHeight; final int width = options.outWidth; options.inSampleSize = 1; int w = 320; int h = 480; h = w*height/width;//计算出宽高等比率 int a = options.outWidth/ w; int b = options.outHeight / h; options.inSampleSize = Math.max(a, b); options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(imageSdUri, options); 3.及时释放Bitamp Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才它设置为null.虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,但是测试结果显示它并没能立即释放内存。但是我它应该还是能大大的加速Bitmap的主要内存的释放。
⑩ Android Green插入10万条数据OOM
下面写个小程序测试一下。
private Runnable runnable = new Runnable() { @Override
public void run() {
List<Book> bookList = new ArrayList<>(); for (int i = 0; i < 5000; i++) {
Book book = new Book();
book.setUuid(UUID.randomUUID().toString());
book.setName("name"); //其他set方法略
bookList.add(book);
} try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mBookDao.insertOrReplaceInTx(bookList);
Log.d(TAG, "插入book数据:" + bookList.size());
}
};private void insert() {
Log.d(TAG, "线程池开始");
mBookDao.deleteAll(); long time = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 200; i++) {
executorService.submit(runnable);
}
executorService.shutdown(); for (; ; ) { if (executorService.isTerminated()) { break;
} try {
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.d(TAG, "线程池完成:" + (System.currentTimeMillis() - time) + "ms");
}
runnable任务模拟1秒从网络拉取5000条数据并插入DB,insert方法使用线程池执行runnable任务。
执行时间超过1000秒,查看内存占用超过180M。如果数据量更多,肯定会发生OOM,基本上可以定位是greenDAO的问题。现在需要在两个方面优化,一是寻找内存占用的原因,二是提高数据的插入速度。
查看内存堆
内存的占用随着insert的数据量越多而递增,从中间mp出java堆,得到hprof文件。注意这个文件不是标准格式,只能用AndroidStudio打开。
图1
右击文件导出标准的hprof文件,用更加强大的MAT分析。
图2
图3
看到IdentityScope占了一半内存,可以确定是greenDAO缓存了插入数据。
mBookDao.insertOrReplaceInTx(bookList);mBookDao.detachAll();
greenDAO的缓存功能是有用的,没必要关闭,改成在插入数据后,调用一次detachAll,将identityScope清空。
public void detachAll() { if (identityScope != null) {
identityScope.clear();
}
}
重建索引
对表插入大量数据,如果中间没有涉及到业务,可以先失效索引,待插入完成后重建索引。
String sql = "drop index index_isbn";
mDb.execSQL(sql);
sql = "drop index index_publisherid";
mDb.execSQL(sql);
sql = "drop index index_author";
mDb.execSQL(sql);
插入数据前,drop掉表中的索引。没有见到greenDAO有操作索引的方法,直接执行sql命令。
sql = "create index index_isbn on book(isbn)";
mDb.execSQL(sql);
sql = "create index index_publisherid on book(publisherid)";
mDb.execSQL(sql);
sql = "create index index_author on book(author)";
mDb.execSQL(sql);
插入数据完成后,重建索引。最后执行100w数据插入大约耗时450秒,比什么都不做快了两三倍。
异步操作
上一个步骤的耗时包含了模拟网络和数据库操作的时间,使用多线程将两个环节分离,可以减少总时间。
greenDAO提供了AsyncSession这个异步操作类,使用Session.startAsyncSession()获取实例,内部实现使用了线程池和阻塞队列,原理很简单不用多讲。
mAsyncSession.runInTx(new Runnable() { @Override
public void run() {
mBookDao.insertOrReplaceInTx(bookList);
mBookDao.deleteAll();
}
});
获取数据后,提交给AsyncSession异步插入数据库。要注意在合适地方使用waitForCompletion,等待AsyncSession完成已有任务。如果获取数据速度很快,而操作数据库很慢,会导致过多数据缓存在AsyncSession的内部阻塞队列。
最后测试一下100w数据插入数据库,耗时不到150秒,又快了几倍。
作者:展翅而飞
链接:https://www.jianshu.com/p/6589c6d3f551
来源:简书
着作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。