当前位置: 代码迷 >> Android >> Android Application Thread CPU GC Operatiing and OOM Question 0603-顺手笔记
  详细解决方案

Android Application Thread CPU GC Operatiing and OOM Question 0603-顺手笔记

热度:607   发布时间:2016-04-28 00:35:18.0
Android Application Thread CPU GC Operatiing and OOM Question 0603-随手笔记

在之前app写完测试的时候,跑完整个老化阶段包括数据收发都没问题,键入 adb shell top -m 5  发现我的 app pid 占用的 

CPU是最多的,其实我想说写一个app是不难,你又没有全面的分析app的内存占用?避免一些OOM之类的问题,和其他可

能带来的一些偶发性问题,这些估计很多小伙伴都没考虑,没事,今天就给大伙说说这方面的东西,虽说不是什么高难度的

知识点,但最重要的是养成这种习惯,才能在后续的开发中减少不必要的时间浪费,下面我就带大家怎么发现并且解决问

题,一步一步分析


首先看看 我们的app cpu 占用情况:


我们可以看到 com.digissin.twelve 这个进程是一直排在第一位的,这个就是我们测试的进程,下面我带小伙伴们怎么发现问

题,并且及时纠正


首先我们要分析,为什么CPU 占用会那么高?是不是在主线程或者子线程做了耗时操作,网络操作,new 的实例对象过多?

带着这个疑问,我们看看DDMS并且分析下:



查看 com.digissin.twelve.RSUDPProtocol&PostBytesThread 134 行代码:



死循环读取状态导致的,但又不能去掉这个死循环,因为app需要这个死循环来给服务端进行通信,只要非意外情况,app是

一直和后台保持通信的!当有数据传过来,isPause 会被设成true,代码流程就会走到if里面,一旦发完一条数据报,

isPause false while 就用进入了空死循环,不干任何事情,且频率很快的循环执行,如果我们在这个死循环里面调用sleep()

虽然能成功,但是很显然它是与app需求背道而驰的,所以必须排除,因为一旦进入sleep() 线程就不干活了,来自主线成的

协议分发的数据报发送就没任何意义了!所以这个方法就不可取了


所以我很快想到了一个办法,就是当isPause false 的时候,我们就不需要子线程工作,那很简单,我只需要让他休眠,一旦

有来自协议分发过来的数据报,我们就wakeup 让子线程继续工作,那就非 wait() 和 notify() 莫属了 首先区分 Thread 和 

Object 的 这两个东西里面的 wait() 和 notify() ,源码分析太笼统了,我给大家举例子分析

在Thread 里直接调用这2两个函数是不会起作用的,我们需要创建一个Object对象来管理子线程的暂停和继续,意思就是说

子线程相当于一个普通员工,被new 出来的Object对象相当于一个管理者,员工要做什么需要管理者来通知和告知,即使员

工知道自己下一步该干什么想干什么,都需要管理者的允许才行!员工也没法自己独立出来,就是不能自己做自己的事情,

否则整个管理模式会乱套,所以我们必须创建Object对象来对子线程做这个暂停和继续的控制着


所以我给这个内部类线程加 synchronized 字段,并且添加实例化静态方法,来创建这个Object(PostBytesThread)实例对象

别且给出暂停和继续函数:

        private static PostBytesThread mThreadInstance = null;                  public synchronized static PostBytesThread getThreadInstance() {              if (mThreadInstance == null) {              	mThreadInstance = new PostBytesThread();              }              return mThreadInstance;          }    	    	public synchronized boolean isPause() {			return isPause;		}		public synchronized void setPause(boolean isPause) {			this.isPause = isPause;		}		public byte[] getPost_bytes() {			return post_bytes;		}		public void setPost_bytes(byte[] post_bytes) {			this.post_bytes = post_bytes;		}		public synchronized void onThreadPause(){			try {				Log.e(TAG, TAG+" onThreadPause() ----");				this.wait();			} catch (InterruptedException e) {				Log.i(TAG, e.toString());			}		}		public synchronized void onThreadResume(){			Log.e(TAG, TAG+" onThreadResume() ----");			this.notify();		}    	@Override    	public void run() {    	       if(udpSocket == null){    	        	Log.i(TAG, TAG+" udpSocket is null");    	        	return;    	        }        		while(true){        			Log.i(TAG, TAG+" isPause() state:"+isPause());        			if(isPause()){        				try {        					sendPacket.setData(getPost_bytes());        					sendPacket.setLength(getPost_bytes().length);        					sendPacket.setAddress(serverAddress);        					sendPacket.setPort(DEFAULT_POTR);        					udpSocket.send(sendPacket);        					Thread.sleep(1000);        					setPause(false);        				} catch (InterruptedException e) {        					Log.i(TAG, "Exception:"+e.toString());        				} catch (IOException e) {        					Log.i(TAG, "Exception:"+e.toString());        				}        			}else{        				onThreadPause();        			}				}    	}    }

调用方式,回调接口收到来自主线程的协议消息数据包分发,并开始工作,当然只是为了方便大家观看,其实start()方法不用发在这里,因为这个同步对象只有在子线程消亡才会被回收,所以相当于每次都多判断了一次这个同步对象的实例情况了

    public void setPostBytesData(byte[] data){    	PostBytesThread.getThreadInstance().start();    	PostBytesThread.getThreadInstance().onThreadResume();    	PostBytesThread.getThreadInstance().setPause(true);    	PostBytesThread.getThreadInstance().setPost_bytes(data);    	boolean isPause = PostBytesThread.getThreadInstance().isPause();    	Log.d("PostBytesThread", "PostBytesThread  isPause() state:"+isPause);    }

处理完这段代码后我们继续查看 cpu的占用情况:


 

可以看到com.digissin.twelve的CPU占用大幅降低了,从而达到了我们的目的,在解决这个问题的同时,我也给大家说一个

常犯的错误,并且以代码和注释的形式给大家看清楚

创建不必要的新实例:

在一些进度条更新或者上传下载数据等情况,我们通常需要对UI进行跟新之类的,这就涉及子线程跟Handler的交互,需要

我们不停地向Handler发送Message 对象,这时候就易犯这个错误,如下:

	@Override	public void run() {		while(true){			try {				SettingLocationTime();			} catch (InterruptedException e) {				e.printStackTrace();			}		}	}		private void SettingLocationTime() throws InterruptedException{		if(handler!=null){			SendMessage(post_data);			time = setting_time>0?setting_time:default_time;//			Log.i(TAG, TAG+" SettingLocationTime() time:"+time);			Thread.sleep(time*1000);		}	}	/**	 * 这个函数会在run while(true)里面一直跑	 * Message\Bundle会被不停的创建新实例对象	 * 所以这是个极低的错误!也是致命的!	 * */	private void SendMessage(byte[]data){		byte[]_data=ByteParseBeanTools.PostProtocolByte(				ByteProtocolSessionType.LOCATION_STATE_SEND, data);		Message msg = new Message();  // 不必要的 Message 新实例对象		msg.what=MainSessionUtil.SEND_POST_BYETS_DATA;		Bundle bundle = new Bundle(); // 不必要的 Bundle 新实例对象		bundle.putByteArray(MainSessionUtil.BYTES_DATA_KEY, _data);		msg.setData(bundle);		handler.sendMessage(msg);	}

解决方案:

	@Override	public void run() {		while(true){			try {				SettingLocationTime();			} catch (InterruptedException e) {				e.printStackTrace();			}		}	}		private void SettingLocationTime() throws InterruptedException{		if(handler!=null){			SendMessage(post_data);			time = setting_time>0?setting_time:default_time;//			Log.i(TAG, TAG+" SettingLocationTime() time:"+time);			Thread.sleep(time*1000);		}	}	/**	 * 可以把Bundle放在class被加载的地方,实例化这个对象	 * 装载完一次数据之后,下次调用之前执行clear()函数即可,此时的bundle对象就相当于一个铁碗	 * 每次装不同的水而已,就避免了每次开辟新的内存空间来存放Bundle对象	 * Message 对象就更简单了,因为我这类回调了一个Handler对象过来,我们可以直接	 * 调用Handler对象的obtainMessage()函数,这个函数当Handler被创建时,不管你用不用,它都在那里	 * 随Handler消亡而消亡,不需要实例化,不需要创建,可以直接取出来用,这又避免了每次开辟新的内存空间	 * 来装载Message对象,obtainMessage() 函数 来自 MessagePool	 * **/	private void SendMessage(byte[]data){		bundle.clear();// 倒掉碗里的老水(清空之前的缓存),装新来的水(填充来自回调函数的新数据)		byte[]_data=ByteParseBeanTools.PostProtocolByte(				ByteProtocolSessionType.LOCATION_STATE_SEND, data);		Message msg = handler.obtainMessage();  // 来自 MessagePool		msg.what=MainSessionUtil.SEND_POST_BYETS_DATA;		bundle.putByteArray(MainSessionUtil.BYTES_DATA_KEY, _data);// 装新的水(填充新的数据源)		msg.setData(bundle);		handler.sendMessage(msg);	}

这样CPU占用问题就能大幅降低,从而问题也能得到解决!


1楼realrealrealrealreal前天 15:46
已鉴定完毕- -
Re: jspping前天 15:49
回复realrealrealrealrealn大神!膜拜!o(∩_∩)o
  相关解决方案