android 判断内存卡怎么通过log判断是内存异常

8932人阅读
android(9)
Android内存泄漏查找和解决
内存泄漏的概念
一个内存泄漏的例子
Java中”失效”的private修饰符
回头看内存泄漏例子泄漏的重点
强引用与弱引用
解决内部类的内存泄漏
Context造成的泄漏
使用LeakCanary工具查找内存泄漏
一.内存泄漏概念
1.什么是内存泄漏?
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。即所谓的内存泄漏。
其实说白了就是该内存空间使用完毕之后未回收
2.内存泄漏会导致的问题
内存泄露就是系统回收不了那些分配出去但是又不使用的内存, 随着程序的运行,可以使用的内存就会越来越少,机子就会越来越卡,直到内存数据溢出,然后程序就会挂掉,再跟着操作系统也可能无响应。
(在我们平时写应用的过程中,可能会无意的写了一些存在内存泄漏的代码,如果没有专业的工具,对内存泄漏的原理也不熟悉,要查内存泄漏出现在哪里是比较困难的)接下来先看一个内存泄漏的例子
二.内存泄漏的例子
这个例子存在的问题应该很容易能看出来,使用了handler延迟一定时间执行Runnable代码块,而在Activity结束的时候又没有释放执行的代码块,导致了内存泄漏。那么只要在Activity结束onDestroy的时候,释放延迟执行的代码块不就可以了,确实是,那么再看一看下面的例子。
这段代码是实际开发中存在内存泄漏的实例,稍微进行简化得到的。内存泄漏的关键点在哪里,怎么去解决,先留着这个问题,看下面一节的内容:”失效”的private修饰符。
三.Java中”失效”的private修饰符
相信大家都用过内部类,Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类。一个简单的内部类实现可以如下
class OuterClass {
class InnerClass{
下面回头看上面写的例子:
这其实是一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的private成员变量或者方法,这是可以的。
这是为什么,不是private修饰的成员只能被成员所述的类才能访问么?难道private真的失效了么?
其实是编译器帮我们做了一些我们看不到的工作,下面我们通过反编译把这些看不到的工作都扒出来看看
1.下面这一份是通过 dex2jar + jad 进行反编译得到的近似源码的java类
可以看到这份反编译出来的代码,比我们编写的源码,要多了一些东西,在内部类MyRunnable里面多了一个MainActivity的成员变量,并且,在构造函数里面获得了外部类的引用。
2.再看看下面这一份文件,这是通过 apktool 反编译出来的 smali指令语言
在这里MainActivity分成了两个文件,分别是MainActivity.smali和MainActivity$MyRunnable.smali。下面贴出的两份文件比较长,简单浏览一遍即可,详细看下面的解析,了解这份文件跟源码的对应关系。
MainActivity:
.class public Lcom/gexne/car/leaktest/MainA
.super Landroid/app/A
.source "MainActivity.java"
# annotations
.annotation system Ldalvik/annotation/MemberC
Lcom/gexne/car/leaktest/MainActivity$MyR
.end annotation
# instance fields
.field private handler:Landroid/os/H
.field private test:Ljava/lang/S
# direct methods
.method public constructor &init&()V
invoke-direct {p0}, Landroid/app/A-&&init&()V
const-string v0, "TEST_STR"
iput-object v0, p0, Lcom/gexne/car/leaktest/MainA-&test:Ljava/lang/S
new-instance v0, Landroid/os/H
invoke-direct {v0}, Landroid/os/H-&&init&()V
iput-object v0, p0, Lcom/gexne/car/leaktest/MainA-&handler:Landroid/os/H
return-void
.end method
.method static synthetic access$000(Lcom/gexne/car/leaktest/MainA)Ljava/lang/S
.param p0, "x0"
# Lcom/gexne/car/leaktest/MainA
iget-object v0, p0, Lcom/gexne/car/leaktest/MainA-&test:Ljava/lang/S
return-object v0
.end method
# virtual methods
.method protected onCreate(Landroid/os/B)V
.param p1, "savedInstanceState"
# Landroid/os/B
invoke-super {p0, p1}, Landroid/app/A-&onCreate(Landroid/os/B)V
const/high16 v0, 0x7f040000
invoke-virtual {p0, v0}, Lcom/gexne/car/leaktest/MainA-&setContentView(I)V
iget-object v0, p0, Lcom/gexne/car/leaktest/MainA-&handler:Landroid/os/H
new-instance v1, Lcom/gexne/car/leaktest/MainActivity$MyR
invoke-direct {v1, p0}, Lcom/gexne/car/leaktest/MainActivity$MyR-&&init&(Lcom/gexne/car/leaktest/MainA)V
const-wide/16 v2, 0x2710
invoke-virtual {v0, v1, v2, v3}, Landroid/os/H-&postDelayed(Ljava/lang/RJ)Z
invoke-virtual {p0}, Lcom/gexne/car/leaktest/MainA-&finish()V
return-void
.end method
在上面MainActivity.smali文件中,可以看到.field代表的是成员变量,.method代表的是方法,2个成员变量分别是Handler和String,方法则有3个分别是构造函数、onCreate()、access$000()。
嗯?在MainActivity中我们并没有定义access$000()这种方法,它是一个静态方法,接收一个MainActivity实例作为参数,并且返回MainActivity的test成员变量,所以,它出现的目的就是为了得到MainActivity的私有属性。
MainActivity$MyRunnable.smali:
.class Lcom/gexne/car/leaktest/MainActivity$MyRunnable;
.super Ljava/lang/Object;
.source "MainActivity.java"
.implements Ljava/lang/Runnable;
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/gexne/car/leaktest/MainActivity;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = "MyRunnable"
.end annotation
.field final synthetic this$0:Lcom/gexne/car/leaktest/MainActivity;
.method constructor &init&(Lcom/gexne/car/leaktest/MainActivity;)V
.param p1, "this$0"
iput-object p1, p0, Lcom/gexne/car/leaktest/MainActivity$MyRunnable;-&this$0:Lcom/gexne/car/leaktest/MainActivity;
invoke-direct {p0}, Ljava/lang/Object;-&&init&()V
return-void
.end method
.method public run()V
const-string v0, "test"
iget-object v1, p0, Lcom/gexne/car/leaktest/MainActivity$MyRunnable;-&this$0:Lcom/gexne/car/leaktest/MainActivity;
invoke-static {v1}, Lcom/gexne/car/leaktest/MainActivity;-&access$000(Lcom/gexne/car/leaktest/MainActivity;)Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;-&d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
MyRunnable.smali文件中用同样的方法观察,发现多了一个成员变量MainActivity,方法分别是构造函数、run(),根据smali指令的含义可以看到构造函数是接收了一个MainActivity作为参数的,而run()方法中获取外部类中的test变量,则是调用access$000()方法获取。如果想了解smali指令语言可以自行google,这里不详细讲解。通过上面两个文件,重新还原一下源码。
这段代码基本上还原了编译器编译后指令的执行方式。内部类调用外部类,是通过一个外部类的引用进行调用的(上面红色框框的两段代码是在还原的基础上加入的,用于解释内部类调用外部类的方式,调用方式1是我们常用的,而到的编译器编译后,实际调用方式是2),而外部类的private属性则通过编译器生成的我们看不见的静态方法,通过传入外部类实例引用获取出来。
通过还原,我们了解了非静态内部类跟外部类交互时的工作方式,以及非静态内部类为什么会持有外部类的引用。
四.通过dumpsys查看内存使用情况
继续回头看第一个内存泄漏的例子,稍微进行修改
对于这段代码,它会造成内存泄漏,那么对于外部类Activity来说,它能够被释放吗?
我们通过dumpsys来查看,了解怎么查看应用的内存使用情况,怎么看一个Activity有没有被顺利释放掉,而这个Activity能不能被回收。
1.先创建一个空Activity,如下代码所示,并安装到设备中
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
2.通过adb shell dumpsys meminfo &packageName&来查看内存使用状况
在没有打开应用的情况下,该命令返回的数据是这样的:
3.打开这个应用的MainActivity,再通过命令查看:
可以看到打印出来很多的信息,而对于我们查看Activity内存泄漏来说,只需要关注Activities和Views两个信息即可,在应用中存在的Activity对象有一个,存在的View对象有13个。
4.这时候我们退出这个Activity,在用命令查看一下:
可以看到,Activity对象和View对象都在极短的时间内被回收掉了。再次打开,退出,多次尝试,发现情况都是一样的。我们可以通过这种方式来简单判断一个Activity是否存在内存泄漏,最后是否能够被回收。
5.再运行刚才的泄漏的例子,用命令查看一下:
当我们连续打开退出同一个页面,然后使用命令查看时,发现Activity存在13个,而View则存在了234个,而且没有很快被回收,依次判断应该是存在内存泄漏了。
等待10多秒,再次查看,发现Activity和View的数量都变成了0。
所以,结论是能够被回收,只要Runnable代码块执行完毕,释放了Activity的引用,Activity就能被回收。
上面的例子,是Handler临时性内存泄漏,只要Handler post的代码块执行完毕,被引用的Activity就能够释放。
除了临时性内存泄漏,还有危害更大,直到程序结束才能被释放的内存泄漏。例如:
对于第一个例子,比较容易看出来,MyRunnable内部类持有了Activity的引用,而它自身一直不释放,导致Activity也一直无法释放,使用dumpsys meminfo查看可以验证,多次打开后退Activities的数量只会增加不会减少,直到手动结束整个应用。
而第二个例子也不难看出,只是引用链稍微长了点,TelephonyManager注册了内部类PhoneStateListener,持有了这个内部类的引用,PhoneStateListener持有了ViewHolder的引用,ViewHolder同时也是一个内部类,持有了ViewAdapter的引用,而ViewAdapter则持有了Activity的引用,最后TelephonyManager又没有做反注册的操作,导致了内存泄漏。
很多时候我们写代码,都忽略了释放工作,特别是写Java写多了,都觉得这些资源会自动释放,不用写释放方法,不用操心去做释放工作,然后内存泄漏就这样出现了。
参考资料:
五.强引用与弱引用
看完上面的例子,了解到非静态内部类因为持有外部类的引用,很可能会造成泄漏。为什么持有了外部类的引用会导致外部类不能被回收?
在解决内存泄漏之前,先了解Java的引用方式。Java有四种引用方式,分别是强引用、弱引用、虚引用、软引用。这里只介绍强引用以及弱引用,更详细的资料可以自行查找。
1.强引用(Strong Reference),就是我们经常使用的引用,写法如下
StringBuffer buffer = new StringBuffer();
上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。强引用最重要的就是它能够让引用变得强(Strong),这就决定了它和垃圾回收器的交互。具体来说,如果一个对象可以从GC Roots通过强引用到达时,那么这个对象将不会被GC回收。
2.弱引用(Weak Reference),弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下
WeakReference&Widget& weakWidget = new WeakReference&Widget&(widget);
使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null,所以对于弱引用要记得做判空处理后再使用,否则很容易出现NPE异常。
六.解决内部类的内存泄漏
通过上面介绍的内容,我们了解到内存泄漏产生的原因是对象在生命周期结束时被另一个对象通过强引用持有而无法释放造成的
怎么解决这个问题,思路就是避免使用非静态内部类,定义内部类时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。
这种解决方法,对于临时性内存泄漏适用,其中包括但不限于自定义动画的更新回调,网络请求数据后更新页面的回调等,更具体一点的例子有当我们在页面触发了网络请求加载时,希望它把数据加载完毕,当加载完毕时如果页面还在活动状态则更新显示内容。其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,所以当我们使用时要非静态内部类时要格外注意。
在Android Studio里面,当你定义一个内部类Handler的时候,会出现贴心提示,This Handler class should be static or leaks might occur,提醒你把Handler改成静态类。
解决了上面的内存泄漏问题,再看看下面这个例子:
这个例子改写成静态内部类+弱引用,并不能完全解决内存泄漏的问题。
为什么?只需要加上一句Log即可验证。
多次进入退出页面,看一下打印出来的Log
结果显而易见,Log越来越多了,虽然Activity最后能够回收,但只是因为弱引用很弱,GC能够在内存不足的时候回收它,但并没有完全解决泄漏问题。
使用dumsys meminfo同样可以验证,每一次打开Activity并退出,等GC回收掉Activity后,发现Local Binder的数量并没有减少,而且比上一次多了1。
对于注册到服务中的回调(包括系统服务,自定义服务),使用静态内部类+弱引用的方式只能部分解决内存泄漏问题,这种问题需要释放资源时进行反注册才能根本解决,因为这种服务会长期存在系统中,注册了的callback对象会一直存在于服务中,每次callback来了都会执行callback中的代码块,只不过执行到弱引用部分由于弱引用获取到的对象为null而不会执行下一步操作。例如Broadcast,例如systemServer.listen等。
七.Context造成的泄漏
了解完内部类的泄漏以及修复方法,再来看一下另一种泄漏,由context造成的泄漏。
这也是一个开发中的例子,稍作修改得到。
可以看到,蓝色框框内是一个标准的懒汉式单例。单例是我们比较简单常用的一种设计模式,然而如果单例使用不当也会导致内存泄露。比如这个例子,DashBoardTypeface需要持有一个Context作为成员变量,并且使用该Context创建字体资源。
instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,当我们退出Activity,默认情况下,系统会销毁当前Activity,然后当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。
解决的方法就是不持有Activity的引用,而是持有Application的Context引用。
在任何使用到Context的地方,都要多加注意,例如我们常见的Dialog,Menu,悬浮窗,这些控件都需要传入Context作为参数的,如果要使用Activity作为Context参数,那么一定要保证控件的生命周期跟Activity的生命周期同步。窗体泄漏也是内存泄漏的一种,就是我们常见的leak window,这种错误就是依赖Activity的控件生命周期跟Activity不同步造成的。
一般来说,对于非控件类型的对象需要Context参数,最好优先考虑全局ApplicationContext,来避免内存泄漏。
参考资料:
八.使用LeakCanary工具查找内存泄漏
LeakCanary是什么?它是一个傻瓜化并且可视化的内存泄露分析工具。
它的特点是简单,易于发现问题,人人都可参与,只要配置完成,简单的黑盒测试通过手工点击就能够看到详细的泄漏路径。
下面来看一下如何集成:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
创建Application并加入LeakCanary代码:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
这样已经完成最简单的集成,可以开始进行测试了。
在进行尝试之前再看一段代码:
思考完这段代码的问题后,我们来尝试一下使用LeakCanary寻找问题。如上面的配置,配置好应用,安装后可以看到,应用多了一个入口,如图所示。
这个入口就是当应用在使用过程中发生内存泄漏,可以从这个入口看到详细的泄漏位置。
从LeakCanary给出来的分析能轻易找到内存泄漏出现在responseHandler里面,跟刚才思考分析的答案是否一致呢?如果一致那你对内存泄漏的知识已经掌握不少了。
上面这种是最简单的默认配置,只对Activity进行了检测。但需要检测的对象肯定不只有Activity,例如Fragment、Service、Broadcast。这需要做更多的配置,在Application中留下RefWatcher的引用,使用它来检测其他对象。
public class MyApplication extends Application {
private static RefWatcher sRefW
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
public static RefWatcher getRefWatcher() {
return sRefW
在有生命周期的对象的onDestroy()中进行监控,例如Service。
public class CoreService extends Service {
public void onDestroy() {
super.onDestroy();
MyApplication.getRefWatcher().watch(this);
监控需要设置在对象(很快)被释放的时候,如Activity和Fragment的onDestroy方法。
一个错误示例,比如监控一个Activity,放在onCreate就会大错特错了,那么你每次都会收到Activity的泄露通知。
更详细的资料可以到LeakCanary的github仓库中查看。
关于内存泄漏的知识,如何定位内存泄漏,如何修复,已经讲解完了。
最后做一个总结:
非静态内部类的静态实例
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
资源对象未关闭
资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。
注册对象未反注册
未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。
Handler临时性内存泄露
Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。
由于AsyncTask内部也是Handler机制,同样存在内存泄漏的风险。
此种内存泄露,一般是临时性的。
不要维持到Activity的长久引用,对activity的引用应该和activity本身有相同的生命周期。
尽量使用context-application代替context-activity
Activity中尽量不要使用非静态内部类,可以使用静态内部类和WeakReference代替。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:46325次
排名:千里之外
原创:11篇
评论:56条
(1)(1)(2)(1)(2)(1)(3)(1)
(window.slotbydup = window.slotbydup || []).push({
id: '4740890',
container: s,
size: '250,250',
display: 'inlay-fix'博客分类:
网上找的一个很强大的实现方法,原网页的链接找不到了,没法转载,特此声明一下。
原作者貌似还设了几个小陷阱,大概是不希望我们不劳而获,能多理解一下代码。很多人拿了源码去用,都说日志并没写出来,自己debug了下,果然有些地方是要做改动的,这里把改好的source贴上来,不保证全部正确了,因为我只跑了最基本的写日志,copy日志等功能。
import java.io.BufferedR
import java.io.F
import java.io.FileInputS
import java.io.FileNotFoundE
import java.io.FileOutputS
import java.io.IOE
import java.io.InputS
import java.io.InputStreamR
import java.io.OutputStreamW
import java.text.ParseE
import java.text.SimpleDateF
import java.util.ArrayL
import java.util.A
import java.util.C
import java.util.C
import java.util.D
import java.util.L
import android.app.AlarmM
import android.app.N
import android.app.PendingI
import android.app.S
import android.content.BroadcastR
import android.content.C
import android.content.I
import android.content.IntentF
import android.os.E
import android.os.IB
import android.os.PowerM
import android.os.PowerManager.WakeL
import android.util.L
import com.XXX.helloandroid.R;
* 日志服务,日志默认会存储在SDcar里,如果没有SDcard会存储在内存中的安装目录下面。
* 1.本服务默认在SDcard中每天生成一个日志文件,
* 2.如果有SDCard的话会将之前内存中的文件拷贝到SDCard中
* 3.如果没有SDCard,在安装目录下只保存当前在写日志
* 4.SDcard的装载卸载动作会在步骤2,3中切换
* 5.SDcard中的日志文件只保存7天
* 6.监视App运行的LogCat日志(sdcard mounted):sdcard/MyApp/log/yyyy-MM-dd HHmmss.log(默认)
* 7.监视App运行的LogCat日志(sdcard unmounted):/data/data/包名/files/log/yyyy-MM-dd HHmmss.log
* 8.本service的运行日志:/data/data/包名/files/log/Log.log
* @author Administrator
public class LogService extends Service {
private static final String TAG = "LogService";
private static final int MEMORY_LOG_FILE_MAX_SIZE = 10 * 1024 * 1024;
//内存中日志文件最大值,10M
private static final int MEMORY_LOG_FILE_MONITOR_INTERVAL = 10 * 60 * 1000;
//内存中的日志文件大小监控时间间隔,10分钟
private static final int SDCARD_LOG_FILE_SAVE_DAYS = 7;
//sd卡中日志文件的最多保存天数
private String LOG_PATH_MEMORY_DIR;
//日志文件在内存中的路径(日志文件在安装目录中的路径)
private String LOG_PATH_SDCARD_DIR;
//日志文件在sdcard中的路径
@SuppressWarnings("unused")
private String LOG_SERVICE_LOG_PATH; //本服务产生的日志,记录日志服务开启失败信息
private final int SDCARD_TYPE = 0;
//当前的日志记录类型为存储在SD卡下面
private final int MEMORY_TYPE = 1;
//当前的日志记录类型为存储在内存中
private int CURR_LOG_TYPE = SDCARD_TYPE; //当前的日志记录类型
private String CURR_INSTALL_LOG_NAME; //如果当前的日志写在内存中,记录当前的日志文件名称
private String logServiceLogName = "Log.log";//本服务输出的日志文件名称
private SimpleDateFormat myLogSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private OutputStreamW
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HHmmss");//日志名称格式
private WakeLock wakeL
private SDStateMonitorReceiver sdStateR //SDcard状态监测
private LogTaskReceiver logTaskR
/* 是否正在监测日志文件大小;
* 如果当前日志记录在SDcard中则为false
* 如果当前日志记录在内存中则为true*/
private boolean logSizeMoniting =
private static String MONITOR_LOG_SIZE_ACTION = "MONITOR_LOG_SIZE";
//日志文件监测action
private static String SWITCH_LOG_FILE_ACTION = "SWITCH_LOG_FILE_ACTION"; //切换日志文件action
public IBinder onBind(Intent intent) {
// This is the new method that instead of the old onStart method on the pre-2.0 platform.
@Override //开始服务,执行更新widget组件的操作
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "===================onStartCommand===========================");
Notification notification = new Notification(R.drawable.logo,
"wf log service is running",
System.currentTimeMillis());
PendingIntent pi=PendingIntent.getService(this, 0, intent, 0);
notification.setLatestEventInfo(this, "WF Log Service",
"wf log service is running!", pi);
//让该service前台运行,避免手机休眠时系统自动杀掉该服务
//如果 id 为 0 ,那么状态栏的 notification 将不会显示。
startForeground(startId, notification);
return Service.START_REDELIVER_INTENT;
public void onCreate() {
super.onCreate();
register();
deploySwitchLogFileTask();
new LogCollectorThread().start();
private void init(){
LOG_PATH_MEMORY_DIR = getFilesDir().getAbsolutePath() + File.separator + "log";
LOG_SERVICE_LOG_PATH = LOG_PATH_MEMORY_DIR + File.separator + logServiceLogN
LOG_PATH_SDCARD_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
+ "MyApp" + File.separator + "log";
createLogDir();
writer = new OutputStreamWriter(new FileOutputStream(
LOG_SERVICE_LOG_PATH, true));
} catch (FileNotFoundException e) {
Log.e(TAG, e.getMessage(), e);
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
CURR_LOG_TYPE = getCurrLogType();
Log.i(TAG, "LogService onCreate");
private void register(){
IntentFilter sdCarMonitorFilter = new IntentFilter();
sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
sdCarMonitorFilter.addDataScheme("file");
sdStateReceiver = new SDStateMonitorReceiver();
registerReceiver(sdStateReceiver, sdCarMonitorFilter);
IntentFilter logTaskFilter = new IntentFilter();
logTaskFilter.addAction(MONITOR_LOG_SIZE_ACTION);
logTaskFilter.addAction(SWITCH_LOG_FILE_ACTION);
logTaskReceiver = new LogTaskReceiver();
registerReceiver(logTaskReceiver,logTaskFilter);
* 获取当前应存储在内存中还是存储在SDCard中
public int getCurrLogType(){
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return MEMORY_TYPE;
return SDCARD_TYPE;
* 部署日志切换任务,每天凌晨切换日志文件
private void deploySwitchLogFileTask() {
Intent intent = new Intent(SWITCH_LOG_FILE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// 部署任务
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, sender);
recordLogServiceLog("deployNextTask succ,next task time is:"+myLogSdf.format(calendar.getTime()));
* 日志收集
* 1.清除日志缓存
* 2.杀死应用程序已开启的Logcat进程防止多个进程写入一个日志文件
* 3.开启日志收集进程
* 4.处理日志文件
移动 OR 删除
class LogCollectorThread extends Thread {
public LogCollectorThread(){
super("LogCollectorThread");
Log.d(TAG, "LogCollectorThread is create");
public void run() {
wakeLock.acquire(); //唤醒手机
clearLogCache();
List&String& orgProcessList = getAllProcess();
List&ProcessInfo& processInfoList = getProcessInfoList(orgProcessList);
killLogcatProc(processInfoList);
createLogCollector();
Thread.sleep(1000);//休眠,创建文件,然后处理文件,不然该文件还没创建,会影响文件删除
handleLog();
wakeLock.release(); //释放
} catch (Exception e) {
e.printStackTrace();
recordLogServiceLog(Log.getStackTraceString(e));
* 每次记录日志之前先清除日志的缓存, 不然会在两个日志文件中记录重复的日志
private void clearLogCache() {
Process proc =
List&String& commandList = new ArrayList&String&();
commandList.add("logcat");
commandList.add("-c");
proc = Runtime.getRuntime().exec(
commandList.toArray(new String[commandList.size()]));
StreamConsumer errorGobbler = new StreamConsumer(proc
.getErrorStream());
StreamConsumer outputGobbler = new StreamConsumer(proc
.getInputStream());
errorGobbler.start();
outputGobbler.start();
if (proc.waitFor() != 0) {
Log.e(TAG, " clearLogCache proc.waitFor() != 0");
recordLogServiceLog("clearLogCache clearLogCache proc.waitFor() != 0");
} catch (Exception e) {
Log.e(TAG, "clearLogCache failed", e);
recordLogServiceLog("clearLogCache failed");
} finally {
proc.destroy();
} catch (Exception e) {
Log.e(TAG, "clearLogCache failed", e);
recordLogServiceLog("clearLogCache failed");
* 关闭由本程序开启的logcat进程:
* 根据用户名称杀死进程(如果是本程序进程开启的Logcat收集进程那么两者的USER一致)
* 如果不关闭会有多个进程读取logcat日志缓存信息写入日志文件
* @param allProcList
private void killLogcatProc(List&ProcessInfo& allProcList) {
if(process != null){
process.destroy();
String packName = this.getPackageName();
String myUser = getAppUser(packName, allProcList);
recordLogServiceLog("app user is:"+myUser);
recordLogServiceLog("============= START TYPING PROC LIST INFO ==============");
//只打印本App和logcat的进程信息,其他进程信息不打印
for (ProcessInfo processInfo : allProcList) {
if (myUser.equals( processInfo.user ) || "logcat".equals( processInfo.name )) {
recordLogServiceLog(processInfo.toString());
recordLogServiceLog("============= END TYPING PROC LIST INFO ==============");
for (ProcessInfo processInfo : allProcList) {
if (processInfo.name.toLowerCase().equals("logcat")
&& processInfo.user.equals(myUser)) {
android.os.Process.killProcess(Integer
.parseInt(processInfo.pid));
recordLogServiceLog("kill another logcat process success,the process info is:"
+ processInfo);
* 获取本程序的用户名称
* @param packName
* @param allProcList
private String getAppUser(String packName, List&ProcessInfo& allProcList) {
for (ProcessInfo processInfo : allProcList) {
if (processInfo.name.equals(packName)) {
return processInfo.
* 根据ps命令得到的内容获取PID,User,name等信息
* @param orgProcessList
private List&ProcessInfo& getProcessInfoList(List&String& orgProcessList) {
List&ProcessInfo& procInfoList = new ArrayList&ProcessInfo&();
for (int i = 1; i & orgProcessList.size(); i++) {
String processInfo = orgProcessList.get(i);
String[] proStr = processInfo.split(" ");
// USER PID PPID VSIZE RSS WCHAN PC NAME
// root 1 0 416 300 c00d4b28 0000cd5c S /init
List&String& orgInfo = new ArrayList&String&();
for (String str : proStr) {
if (!"".equals(str)) {
orgInfo.add(str);
if (orgInfo.size() == 9) {
ProcessInfo pInfo = new ProcessInfo();
pInfo.user = orgInfo.get(0);
pInfo.pid = orgInfo.get(1);
pInfo.ppid = orgInfo.get(2);
pInfo.name = orgInfo.get(8);
procInfoList.add(pInfo);
return procInfoL
* 运行PS命令得到进程信息
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 416 300 c00d4b28 0000cd5c S /init
private List&String& getAllProcess() {
List&String& orgProcList = new ArrayList&String&();
Process proc =
proc = Runtime.getRuntime().exec("ps");
StreamConsumer errorConsumer = new StreamConsumer(proc
.getErrorStream());
StreamConsumer outputConsumer = new StreamConsumer(proc
.getInputStream(), orgProcList);
errorConsumer.start();
outputConsumer.start();
if (proc.waitFor() != 0) {
Log.e(TAG, "getAllProcess proc.waitFor() != 0");
recordLogServiceLog("getAllProcess proc.waitFor() != 0");
} catch (Exception e) {
Log.e(TAG, "getAllProcess failed", e);
recordLogServiceLog("getAllProcess failed");
} finally {
proc.destroy();
} catch (Exception e) {
Log.e(TAG, "getAllProcess failed", e);
recordLogServiceLog("getAllProcess failed");
return orgProcL
* 开始收集日志信息
public void createLogCollector() {
String logFileName = sdf.format(new Date()) + ".log";// 日志文件名称
List&String& commandList = new ArrayList&String&();
commandList.add("logcat");
commandList.add("-f");
commandList.add(LOG_PATH_INSTALL_DIR + File.separator + logFileName);
commandList.add(getLogPath());
commandList.add("-v");
commandList.add("time");
commandList.add("*:I");
//commandList.add("*:E");// 过滤所有的错误信息
// 过滤指定TAG的信息
// commandList.add("MyAPP:V");
// commandList.add("*:S");
process = Runtime.getRuntime().exec(
commandList.toArray(new String[commandList.size()]));
recordLogServiceLog("start collecting the log,and log name is:"+logFileName);
// process.waitFor();
} catch (Exception e) {
Log.e(TAG, "CollectorThread == &" + e.getMessage(), e);
recordLogServiceLog("CollectorThread == &" + e.getMessage());
* 根据当前的存储位置得到日志的绝对存储路径
public String getLogPath(){
createLogDir();
String logFileName = sdf.format(new Date()) + ".log";// 日志文件名称
if(CURR_LOG_TYPE == MEMORY_TYPE){
CURR_INSTALL_LOG_NAME = logFileN
Log.d(TAG, "Log stored in memory, the path is:"+LOG_PATH_MEMORY_DIR + File.separator + logFileName);
return LOG_PATH_MEMORY_DIR + File.separator + logFileN
CURR_INSTALL_LOG_NAME =
Log.d(TAG, "Log stored in SDcard, the path is:"+LOG_PATH_SDCARD_DIR + File.separator + logFileName);
return LOG_PATH_SDCARD_DIR + File.separator + logFileN
* 处理日志文件
* 1.如果日志文件存储位置切换到内存中,删除除了正在写的日志文件
并且部署日志大小监控任务,控制日志大小不超过规定值
* 2.如果日志文件存储位置切换到SDCard中,删除7天之前的日志,移
动所有存储在内存中的日志到SDCard中,并将之前部署的日志大小
public void handleLog(){
if(CURR_LOG_TYPE == MEMORY_TYPE){
deployLogSizeMonitorTask();
deleteMemoryExpiredLog();
moveLogfile();
cancelLogSizeMonitorTask();
deleteSDcardExpiredLog();
* 部署日志大小监控任务
private void deployLogSizeMonitorTask() {
if(logSizeMoniting){ //如果当前正在监控着,则不需要继续部署
logSizeMoniting =
Intent intent = new Intent(MONITOR_LOG_SIZE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(), MEMORY_LOG_FILE_MONITOR_INTERVAL, sender);
Log.d(TAG, "deployLogSizeMonitorTask() succ !");
Calendar calendar = Calendar.getInstance();
recordLogServiceLog("deployLogSizeMonitorTask() succ ,start time is " + calendar.getTime().toLocaleString());
* 取消部署日志大小监控任务
private void cancelLogSizeMonitorTask() {
logSizeMoniting =
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(MONITOR_LOG_SIZE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
am.cancel(sender);
Log.d(TAG, "canelLogSizeMonitorTask() succ");
* 检查日志文件大小是否超过了规定大小
* 如果超过了重新开启一个日志收集进程
private void checkLogSize(){
if(CURR_INSTALL_LOG_NAME != null && !"".equals(CURR_INSTALL_LOG_NAME)){
String path = LOG_PATH_MEMORY_DIR + File.separator + CURR_INSTALL_LOG_NAME;
File file = new File(path);
if(!file.exists()){
Log.d(TAG, "checkLog() ==& The size of the log is too big?");
if(file.length() &= MEMORY_LOG_FILE_MAX_SIZE){
Log.d(TAG, "The log's size is too big!");
new LogCollectorThread().start();
* 创建日志目录
private void createLogDir() {
File file = new File(LOG_PATH_MEMORY_DIR);
boolean mkOk;
if (!file.isDirectory()) {
mkOk = file.mkdirs();
if (!mkOk) {
mkOk = file.mkdirs();
file = new File(LOG_SERVICE_LOG_PATH);
if (!file.exists()) {
mkOk = file.createNewFile();
if (!mkOk) {
file.createNewFile();
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
file = new File(LOG_PATH_SDCARD_DIR);
if (!file.isDirectory()) {
mkOk = file.mkdirs();
if (!mkOk) {
recordLogServiceLog("move file failed,dir is not created succ");
* 将日志文件转移到SD卡下面
private void moveLogfile() {
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
recordLogServiceLog("move file failed, sd card does not mount");
File file = new File(LOG_PATH_SDCARD_DIR);
if (!file.isDirectory()) {
boolean mkOk = file.mkdirs();
if (!mkOk) {
recordLogServiceLog("move file failed,dir is not created succ");
file = new File(LOG_PATH_MEMORY_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
for (File logFile : allFiles) {
String fileName = logFile.getName();
//只copy App的LocCat日志文件【yyyy-MM-dd HHmmss.log】到sdcard,本LogService的运行日志【Log.log】不做copy对象
if (logServiceLogName.equals(fileName)) {
//String createDateInfo = getFileNameWithoutExtension(fileName);
boolean isSucc = copy(logFile, new File(LOG_PATH_SDCARD_DIR
+ File.separator + fileName));
if (isSucc) {
logFile.delete();
recordLogServiceLog("move file success,log name is:"+fileName);
* 删除SDCard下过期的日志
private void deleteSDcardExpiredLog() {
File file = new File(LOG_PATH_SDCARD_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
for (File logFile : allFiles) {
String fileName = logFile.getName();
if (logServiceLogName.equals(fileName)) {
String createDateInfo = getFileNameWithoutExtension(fileName);
if (canDeleteSDLog(createDateInfo)) {
logFile.delete();
Log.d(TAG, "delete expired log success,the log path is:"
+ logFile.getAbsolutePath());
* 判断sdcard上的日志文件是否可以删除
* @param createDateStr
public boolean canDeleteSDLog(String createDateStr) {
boolean canDel =
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1 * SDCARD_LOG_FILE_SAVE_DAYS);//删除7天之前日志
Date expiredDate = calendar.getTime();
Date createDate = sdf.parse(createDateStr);
canDel = createDate.before(expiredDate);
} catch (ParseException e) {
Log.e(TAG, e.getMessage(), e);
return canD
* 删除内存中的过期日志,删除规则:
* 除了当前的日志和离当前时间最近的日志保存其他的都删除
private void deleteMemoryExpiredLog(){
File file = new File(LOG_PATH_MEMORY_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
Arrays.sort(allFiles, new FileComparator());
for (int i=0;i&allFiles.length-2;i++) { //"-2"保存最近的两个日志文件
File _file =
allFiles[i];
if (logServiceLogName.equals(_file.getName()) ||
_file.getName().equals(CURR_INSTALL_LOG_NAME)) {
_file.delete();
Log.d(TAG, "delete expired log success,the log path is:"+_file.getAbsolutePath());
* 拷贝文件
* @param source
* @param target
private boolean copy(File source, File target) {
FileInputStream in =
FileOutputStream out =
if(!target.exists()){
boolean createSucc = target.createNewFile();
if(!createSucc){
in = new FileInputStream(source);
out = new FileOutputStream(target);
byte[] buffer = new byte[8*1024];
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
recordLogServiceLog("copy file fail");
} finally{
if(in != null){
in.close();
if(out != null){
out.close();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
recordLogServiceLog("copy file fail");
* 记录日志服务的基本信息 防止日志服务有错,在LogCat日志中无法查找
* 此日志名称为Log.log
* @param msg
private void recordLogServiceLog(String msg) {
if (writer != null) {
Date time = new Date();
writer.write(myLogSdf.format(time) + " : " + msg);
writer.write("\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
* 去除文件的扩展类型(.log)
* @param fileName
private String getFileNameWithoutExtension(String fileName){
return fileName.substring(0, fileName.indexOf("."));
class ProcessInfo {
public String toString() {
String str = "user=" + user + " pid=" + pid + " ppid=" + ppid
+ " name=" +
class StreamConsumer extends Thread {
List&String&
StreamConsumer(InputStream is) {
StreamConsumer(InputStream is, List&String& list) {
this.list =
public void run() {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line =
while ((line = br.readLine()) != null) {
if (list != null) {
list.add(line);
} catch (IOException ioe) {
ioe.printStackTrace();
* 监控SD卡状态
* @author Administrator
class SDStateMonitorReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_MEDIA_UNMOUNTED.equals(intent.getAction())){ //存储卡被卸载
if(CURR_LOG_TYPE == SDCARD_TYPE){
Log.d(TAG, "SDcar is UNMOUNTED");
CURR_LOG_TYPE = MEMORY_TYPE;
new LogCollectorThread().start();
//存储卡被挂载
if(CURR_LOG_TYPE == MEMORY_TYPE){
Log.d(TAG, "SDcar is MOUNTED");
CURR_LOG_TYPE = SDCARD_TYPE;
new LogCollectorThread().start();
* 日志任务接收
* 切换日志,监控日志大小
* @author Administrator
class LogTaskReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(SWITCH_LOG_FILE_ACTION.equals(action)){
new LogCollectorThread().start();
}else if(MONITOR_LOG_SIZE_ACTION.equals(action)){
checkLogSize();
class FileComparator implements Comparator&File&{
public int compare(File file1, File file2) {
if(logServiceLogName.equals(file1.getName())){
return -1;
}else if(logServiceLogName.equals(file2.getName())){
String createInfo1 = getFileNameWithoutExtension(file1.getName());
String createInfo2 = getFileNameWithoutExtension(file2.getName());
Date create1 = sdf.parse(createInfo1);
Date create2 = sdf.parse(createInfo2);
if(create1.before(create2)){
return -1;
} catch (ParseException e) {
public void onDestroy() {
//对于通过startForeground启动的service,需要通过stopForeground来取消前台运行状态
stopForeground(true);
super.onDestroy();
recordLogServiceLog("LogService onDestroy");
if (writer != null) {
writer.close();
} catch (IOException e) {
e.printStackTrace();
if (process != null) {
process.destroy();
unregisterReceiver(sdStateReceiver);
unregisterReceiver(logTaskReceiver);
浏览 11057
我也遇到类似问题了,请问下有办法修改吗?debug一下看看,没有代码我也不好说,还有看看manifest里面读写文件的权限加了没
你好,博主,你的这段代码我运行出错10-23 10:28:19.590: W/System.err(17852): java.io.IOException: read failed: EBADF (Bad file number)10-23 10:28:19.590: W/System.err(17852):
at libcore.io.IoBridge.read(IoBridge.java:435)10-23 10:28:19.590: W/System.err(17852):
at java.io.FileInputStream.read(FileInputStream.java:185)10-23 10:28:19.594: W/System.err(17852):
at java.io.InputStreamReader.read(InputStreamReader.java:244)10-23 10:28:19.594: W/System.err(17852):
at java.io.BufferedReader.fillBuf(BufferedReader.java:130)10-23 10:28:19.594: W/System.err(17852):
at java.io.BufferedReader.readLine(BufferedReader.java:354)10-23 10:28:19.594: W/System.err(17852):
at com.example.demo.LogService$StreamConsumer.run(LogService.java:817)10-23 10:28:19.594: W/System.err(17852): Caused by: libcore.io.ErrnoException: read failed: EBADF (Bad file number)10-23 10:28:19.594: W/System.err(17852):
at libcore.io.Posix.readBytes(Native Method)10-23 10:28:19.597: W/System.err(17852):
at libcore.io.Posix.read(Posix.java:119)10-23 10:28:19.597: W/System.err(17852):
at libcore.io.BlockGuardOs.read(BlockGuardOs.java:149)10-23 10:28:19.597: W/System.err(17852):
at libcore.io.IoBridge.read(IoBridge.java:425)10-23 10:28:19.597: W/System.err(17852):
... 5 moredebug一下看看,没有代码我也不好说,还有看看manifest里面读写文件的权限加了没
浏览: 262917 次
为了感谢楼主 我特意注册了一个账号!,谢谢!很实用!真棒
多谢。搞定~
应该是官网写错了。应该是144x144.
Create or replace function test ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 activitylog.xml 异常 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信