我们先获取系统服务,我们通过getSystemService传入什么参数呢?下面就先说下各个参数代表的是什么吧:
参数名 | 获取了什么系统服务 |
---|---|
WINDOW_SERVICE | 窗口管理器 |
LAYOUT_INFLATER_SERVICE | 布局管理 |
ACTIVITY_SERVICE | 活动管理 |
POWER_SERVICE | 电源管理 |
ALARM_SERVICE | 闹钟管理 |
NOTIFICATION_SERVICE | 通知栏管理 |
KEYGUARD_SERVICE | 屏幕保护管理 |
LOCATION_SERVICE | 位置管理 |
SEARCH_SERVICE | 搜索管理 |
VIBRATOR_SERVICE | 手机震动 |
CONNECTIVITY_SERVICE | 网络连接管理 |
WIFI_SERVICE | wifi管理 |
WIFI_P2P_SERVICE | 音频管理 |
MEDIA_ROUTER_SERVICE | 大屏幕播放管理 |
TELEPHONY_SERVICE | 电话管理 |
SubscriptionManager.from(this); | 消息驱动 |
INPUT_METHOD_SERVICE | 软键盘的输入控制 |
UI_MODE_SERVICE | 模式管理 |
DOWNLOAD_SERVICE | 下载管理 |
BATTERY_SERVICE | 电池管理 |
JOB_SCHEDULER_SERVICE | 后台服务 |
NETWORK_STATS_SERVICE | 应用流量统计 |
详细的可以看我这篇博客:传送门
所以我们就要传入ACTIVITY_SERVICE这个参数,这样我们就可以通过ActivityManager中的getMemoryClass方法获取到该APP在该部手机上能获取到的最大的内存空间,单位是M,或者可以通过getLargeMemoryClass方法获取到设置了最大堆的应用的能获得到的最大的内存空间。但是这两个获取到的值一般都是相同的,因为一般的应用都不支持最大堆的申明,而且也不这么去做。
private void calculate() {
StringBuilder str = new StringBuilder();
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memClass = activityManager.getMemoryClass();//以m为单位
int LargememClass = activityManager.getLargeMemoryClass();//以m为单位
str.append("memClass:"+memClass+"\n");
str.append("LargememClass:"+LargememClass+"\n");
mTv.setText(str.toString());
}
这么声明最大堆呢?
在AndroidManifest中application标签下定义下面的属性:
android:largeHeap="true"
我们先运行我们之前写的程序,然后再来查看一下虚拟机中运行的进程吧,我们要用adb shell这个命令进入Android底层Linux系统,然后再用ps命令查看系统里面的进程,当然有可能有人输入adb shell会返回不是内部或外部命令,也不是可运行程序和批处理文件,这就是你的环境没有配好,我有一篇博客是讲怎么解决的,这里就不啰嗦了(传送门:戳这里)。正常的我们就会获得下面这些数据,我们从中找到属于程序包名的一行数据。
然后我们来看下这个应用的进程相关信息,输入下面这个命令
dumpsys meminfo com.gin.xjh.testnc
说了怎么看系统的内存分配了,我们就来说下系统的回收方式:
GC(垃圾回收器)只有在Heap(堆)空间不够的情况下才会发出垃圾回收的命令。之后再释放空间,所以这就照成了一个很不好的情况,因为他释放空间是会让所有的线程暂停的,如果你的垃圾比较多的话,在GC触发的时候,所有的线程都会被暂停,这样就会让应用卡顿等。
每个APP分配的最大内存限制,随不同设备而不同。使用内存最大的是图片。那为什么要堆内存进行限制呢?因为Android手机是多任务系统,有很多APP在运行,所以肯定要对内存进行限制。
APP在切换的时候是使用了LRU算法的(LRU算法:最近使用的排在最前面,最少可能被清理掉)
当真正要开始清理的时候,系统会发出onTrimMemort()回调,但是这个回调并不是在清理的时候回调,而是在系统内存发生变化的时候,系统会发出onTrimMemort给各个应用,然后你的APP收到了这个以后,如果系统的内存已经很少了,你就要开始把你APP中不用到的一些占用内存的东西进行释放,这样的话你的APP占用的内存就会相对的小一点,系统在查看后台APP的时候把你的APP清除的可能性就会小一点。
1、代码显示
这里我们就可以利用上面我们获取最大内存的代码来实现内存的查看,用代码来看呢就要在你需要看内存信息的时候调用这段代码。后面这种方法获取到的最大内存大小应该是和之前那种方法获取到的应该是一样的。
private void calculate() {
StringBuilder str = new StringBuilder();
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memClass = activityManager.getMemoryClass();//以m为单位
int LargememClass = activityManager.getLargeMemoryClass();//以m为单位
str.append("memClass:"+memClass+"\n");
str.append("LargememClass:"+LargememClass+"\n");
Float maxMemory = Runtime.getRuntime().maxMemory()*1.0f/(1024*1024);//以字节为单位
Float totalMemory = Runtime.getRuntime().totalMemory()*1.0f/(1024*1024);//以字节为单位
Float freeMemory = Runtime.getRuntime().freeMemory()*1.0f/(1024*1024);//以字节为单位
str.append("maxMemory:"+maxMemory+"\n"+"totalMemory:"+totalMemory+"\n"+"freeMemory:"+freeMemory+"\n");
mTv.setText(str.toString());
}
当然我们也可以通过Android Studio的Android Profiler来动态的查看我们APP的内存使用情况:
当然我们也可以用DDMS来查看内存信息
DDMS打开方式:Tools->Android->Android Device Monitor。
打开DDMS以后我们在Devices中找到我们现在程序运行的模拟器或者真机,找到我们程序对应的包名,然后点击heap,再点Cause GC就可以查看详细信息了,我们查看程序是否内存泄漏主要是看data object以及class object两个的值,假如这两个值随着程序的运行数值趋于平稳就说明你的程序应该没有发生内存泄漏,如果一直在进行增长的话就是可能会发生内存泄漏。
1、数据结构优化
我们先来人工造成内存抖动来看一下现象,按照上面的定义我们要先申请很多空间,然后弃之不用,然后再申请,这个实现应该很简单的。
private void doChurn() {
Log.d("xjhLog","doChurn start:");
for(int i=0;iString[] strMatrix = new String[length];
for(int j=0;jString.valueOf(ran.nextDouble());
}
Log.d("xjhLog","doChurn rowStr:"+i);
}
Log.d("xjhLog","doChurn end.");
}
通过Android Studio的Android Profiler,我们可以看到内存的使用情况。
2、对象复用
3、避免内存泄漏
内存泄漏:由于代码的瑕疵,导致这块内存虽然是停止不用的,但是依然还是被其他东西引用着,使得GC没法对它进行回收。
OOM:内存溢出
OOM的必然性:Android手机对于每个APP可用的内存是有一定限制的。
OOM的可解决性:Android手机的生产厂家对于手机内存的设置是有过考量的,一般来说只要是有对内存的优化是不会出现OOM问题的。
OOM问题绝大部分发生在图片上。
(1)强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
如下:
Object o=new Object(); // 强引用
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:
o=null; // 帮助垃圾收集器回收此对象
显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
举例:
public void test(){
Object o=new Object();
// 省略其他操作
}
在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。
但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
(2)软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
当内存不足时,等价于:
If(JVM.内存不足()) {
str = null; // 转换为软引用
System.gc(); // 垃圾回收器进行回收
}
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}
else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
这样就很好的解决了实际的问题。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
其实对应的还有弱引用和虚引用,这个对于内存的优化没有什么用处就不在这里说了。
我们其实可以发现上面这几点大部分和Bitmap图片操作有关系,接下来我们来说下怎么进行图片压缩,主要有三种方法一是把图片缩小,二是把图片的每个像素压缩(一个像素由4个字节压缩到2个字节),最后一种就是显示部分完整图片。
缩小图片
private void changePicOpti() {
if (file == null) {//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;//只获得文件的宽和高
BitmapFactory.decodeStream(new FileInputStream(file), null, o);
int width_tmp = o.outWidth;
int height_tmp = o.outHeight;
int scale = 2;
while (true) {
if (width_tmp / scale < SCREEN_WIDTH && height_tmp / scale < SCREEN_HEIGHT) {//SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度
break;
}
scale += 2;
}
scale /= 2;//inSampleSize=1会将原始图片放大2倍
o.inJustDecodeBounds = false;
o.inSampleSize = scale;
FileInputStream fin = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
压缩像素
private void changeRGB() {
if (file == null) {//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inPreferredConfig = Bitmap.Config.RGB_565;
FileInputStream fin = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
显示部分高清图片
private void partLoad() {
if (file == null) {//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;//只获得文件的宽和高
FileInputStream fin = new FileInputStream(file);
BitmapFactory.decodeStream(fin, null, o);
int width_tmp = o.outWidth;
int height_tmp = o.outHeight;
fin = new FileInputStream(file);
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(fin, false);
BitmapFactory.Options options = new BitmapFactory.Options();
//SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度,nextPx垂直方向变动的,nextPy水平方向的变动,默认中心点
int x = width_tmp / 2 - SCREEN_WIDTH / 2 + nextPx;
int y = height_tmp / 2 - SCREEN_HEIGHT / 2 + nextPy;
//保证在图片范围以内
if (x < 0) {
x = 0;
} else if (x > width_tmp - SCREEN_WIDTH) {
x = width_tmp - SCREEN_WIDTH;
}
if (y < 0) {
y = 0;
} else if (y > height_tmp - SCREEN_HEIGHT) {
y = height_tmp - SCREEN_HEIGHT;
}
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(x, y, x + SCREEN_WIDTH, y + SCREEN_HEIGHT), options);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}