Android下Activity结束(finish())之后没有释放内存问题的解决方法初探


很多人(应该是所有用过DDMS或者是ANDROID自带的任务管理器)玩android细心一点
的就会发现,当打开一个app,进入主页面,跳到登陆画面,登陆之后返回到菜单,
再点击菜单上的任意一个页面,点击了,退回主菜单,看一下ddms,这时的heap used
大约在3.5m左右。

这时候如果再点击一个页面,退回来,再点击同一个,再退回来,
然后无数次(理论上的,呵呵,10次即OK Android下Activity结束(finish())之后没有释放内存问题的解决方法初探)。一般来说(除了
极个别的app),退回来就是结束当前activity, 也就是使得activity
的生命周期走到最后的一刻destory,之后理论上这个activity被销毁,
与之相关所有的内存被GC,内存还会在3.5m。

结果是,仅仅10次重复打开关闭同一个activity,内存使用变成5.2m了!
而且这多出来的1.7m怎么也去不掉!狂点cause gc? 不理你!我被抛弃了!
情急之下,我的重要的恋人----java顷刻之间让我感觉那么的陌生,她最
重要,也是和C相比最美丽的地方之一----垃圾回收立即烟消云散,荡然无存。

。。。。。。。
。。。。。。。

痛苦的思索之后,冷静下来,从java的GC机制开始分析。。。。。 

我们说 GC(Garbage Collector)的真正目的是什么?显而易见。
回收没有利用价值的被占用的内存。那么怎样的
内存区域不能被使用或者访问了呢?没有被其他对象
引用到的对象,或者说是引用数为0的对象。 

从以上的分析,可以猜想,对象没被释放,因为有
其他对象在引用它。所以,我们把activity结束掉后
必须释放那些在activity中new出来或者是从其他地方
比如findViewById() getResourceById()这种地方获得
的activity字段。 解决方案显而易见:

public void onDestory(){
     textView1 = null;
     dataList = null;
     ...... 
     //etc.
super.onDestory();

不出所料,测试结果,反复打开关闭一个页面消耗了1.3m的内存,比
之前1.7m的情况好了30%左右。可是,这依然没有满足需求。

那么,还有什么地方能提高内存的回收率呢??

我们再回到前面提到的GC的性质和功能。如果GC不释放一部分内存,那么这一部分
的内存 肯定是还被其他的对象引用。
但是如何去找到关闭了activity而仍然被引用的对象呢?
这里就是问题的关键了。
一般性会写安卓的朋友们都会想起一个方法,就是前面提到的 findViewById()

说起这个方法,我们一般都想到把findViewById()的对象赋给自定义Activity的类字段。
比如说,textView = findViewById(R.id.tv1);
在ondestory里把 textView = null加上,在destory的时候activity将不再引用textView
所指的对象实体了。然而, findViewById()是根据id创建一个view,还是返回在onCreate 里已经创建的view?

茫茫大海,没有指南针,我们只能迷失在苍苍大海之中。

哎?这就不对了,你说郑和下西洋环游地球最终返回中国,这是一个奇迹。可是,在
没有通讯仪器的古代,加上单打独斗独闯七大洲,没有方向概念克不行啊。。。

所以,为了您的安全,请带好指南针。
话题回来,我们这里有一个类似的东东,那就是在程序的海洋中旅行的人们耳熟能详的,
居家旅行必备的(名字就不用说了,这个大家都应该知道)
http://developer.android.com/reference

那么这个doc告诉了我们什么呢?findViewById的定义。
Finds a view that was identified by the id attribute from the XML that was processed in  onCreate(Bundle). 

很明显,findViewById返回在onCreate已经创建了的view,并且指定的ID与通过setContentView中指定的
xml文件匹配。那么,前面所说的onDestory()里面的置null 方法的作用就很有限了。

我们来看一下setContentView的文档。
Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity.

看到inflate这个词,应该想到另外一种findView之外构建activity页面的方法了。不难理解,inflate就是
建立一个新的android.view.View对象。结合 setContentView的定义,activity也是通过 setContentView并
传入一个xml来构建整个activity的view对象以及子对象的!

就是说,在onCreate的setContentview 里,有一个contentview = viewInflater.Inflate();或者类似的东东。

所以,我们应该在ondestory把由 setContentview创建的contentview置为null,就一劳永逸了。
但是,这句话我们看不到,因为在Activity父类里。
同样,我们也不能在ondestory把contentview = null; 因为是父类的私有字段!

经过一番思考,想出了一种方法,很多人应该也能感觉到,那就是 setContentview(null)
通过这样的方法,改变父类Activity的contentview对象的引用,让oncreate的 setContentview创建的
那个view对象失去引用,被GC掉。
然而,编译报错,NullPointerException。于是写了一个空的viewxml,命名view_null。
编译成功,运行正常。结果是,10个打开关闭一共消耗了0.6M的内存。
基于ffmpeg 进行视频转换 flv\mp4\3gp\wmv等 package cn.fourtwoone.main; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * @author Eoge E-mail:18802012501@139.com * @version 创建时间:2017年6月23日 上午9:13:12 * 类说明 */ public class FfmpegManager { private static ConcurrentMap<String, ConcurrentMap<String, Object>> handlerMap = new ConcurrentHashMap<String, ConcurrentMap<String, Object>>(20); private static int checkContentType(String path) { String type = path.substring(path.lastIndexOf(".") + 1, path.length()).toLowerCase(); // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) if (type.equals("avi")) { return 0; } else if (type.equals("mpg")) { return 0; } else if (type.equals("wmv")) { return 0; } else if (type.equals("3gp")) { return 0; } else if (type.equals("mov")) { return 0; } else if (type.equals("mp4")) { return 0; } else if (type.equals("asf")) { return 0; } else if (type.equals("asx")) { return 0; } else if (type.equals("flv")) { return 0; } // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式. else if (type.equals("wmv9")) { return 1; } else if (type.equals("rm")) { return 1; } else if (type.equals("rmvb")) { return 1; } return 9; } private static boolean checkfile(String path) { File file = new File(path); if (!file.isFile()) { return false; } return true; } protected String getComm4Map(Map<String, Object> paramMap) { // -i:输入流地址或者文件绝对地址 StringBuilder comm = new StringBuilder(); if(paramMap.containsKey("fp")) { comm.append(paramMap.get("fp")).append(" -i"); }else comm.append("ffmpeg -i "); // 是否有必输项:输入地址,输出地址,应用名 if (paramMap.containsKey("input") && paramMap.containsKey("output") && paramMap.containsKey("name")) { comm.append(paramMap.get("input")).append(" "); // -f :转换格式,默认flv comm.append(" -f ").append(paramMap.containsKey("fmt") ? paramMap.get("fmt") : "flv").append(" "); // -r :帧率,默认25 comm.append("-r ").append(paramMap.containsKey("fps") ? paramMap.get("fps") : "30").append(" "); // -s 分辨率 默认是原分辨率 comm.append("-s ").append(paramMap.containsKey("rs") ? paramMap.get("rs") : "").append(" "); // -an 禁用音频 comm.append("-an ").append( paramMap.containsKey("disableAudio") && ((Boolean) paramMap.get("disableAudio")) ? "-an" : "") .append(" "); // 输出地址 comm.append(paramMap.get("output")); // 发布的应用名 comm.append(paramMap.get("name")); // 一个视频源,可以有多个输出,第二个输出为拷贝源视频输出,不改变视频的各项参数 comm.append(" ").append(" -vcodec copy -f flv -an rtmp://192.168.30.21/live/test2"); System.out.println(comm.toString()); return comm.toString(); } else { throw new RuntimeException("输入流地址不能为空!"); } } public String push(Map<String, Object> paramMap) throws IOException { // 从map里面取数据,组装成命令 String comm = getComm4Map(paramMap); ConcurrentMap<String, Object> resultMap = null; // 执行命令行 final Process proc = Runtime.getRuntime().exec(comm); System.out.println("执行命令----start commond"); OutHandler errorGobbler = new OutHandler(proc.getErrorStream(), "Error"); OutHandler outputGobbler = new OutHandler(proc.getInputStream(), "Info"); errorGobbler.start(); outputGobbler.start(); // 返回参数 resultMap = new ConcurrentHashMap<String, Object>(); resultMap.put("info", outputGobbler); resultMap.put("error", errorGobbler); resultMap.put("process", proc); String key = paramMap.hashCode()+""; handlerMap.put(key, resultMap); return key; } public void removePush(String pushId) { if (handlerMap.containsKey(pushId)) { ConcurrentMap<String, Object> map = handlerMap.get(pushId); //关闭两个线程 ((OutHandler)map.get("error")).destroy(); ((OutHandler)map.get("info")).destroy(); System.out.println("停止命令-----end commond"); //关闭命令主进程 ((Process)map.get("process")).destroy(); handlerMap.remove(pushId); } } public class OutHandler extends Thread { // 控制线程状态 volatile boolean status = true; BufferedReader br = null; String type = null; public OutHandler(InputStream is, String type) { br = new BufferedReader(new InputStreamReader(is)); this.type = type; } /** * 重写线程销毁方法,安全的关闭线程 */ @Override public void destroy() { status = false; } /** * 执行输出线程 */ @Override public void run() { String msg = null; try { while (status) { if ((msg = br.readLine()) != null) { System.out.println(type + "消息:" + msg); } } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { FfmpegManager pusher = new FfmpegManager(); Map<String, Object> map=new HashMap<String, Object>(); map.put("fp", "D:/Program Files/ffmpeg/bin/ffmpeg"); map.put("name", "test3"); map.put("input", "rtsp://admin:admin@192.168.2.236:37779/cam/realmonitor?channel=1&subtype=0"); map.put("output", "rtmp://192.168.30.21/live/"); map.put("fmt", "mp4"); map.put("fps", "25"); map.put("rs", "640x360"); map.put("disableAudio", true); try { // 推送后会获得该处理器的id,通过该id可关闭推送流 String id = pusher.push(map); Thread.sleep(100000); // 关闭推送流 pusher.removePush(id); } catch (Exception ee) { ee.printStackTrace(); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值