├── README.md └── Tools ├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jiangc │ │ └── tools │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jiangc │ │ │ └── tools │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jiangc │ └── tools │ └── ExampleUnitTest.java ├── build.gradle ├── fileobserver ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jiangc │ │ └── receiver │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── err.h │ │ └── fileObserver.c │ ├── java │ │ └── com │ │ │ └── jiangc │ │ │ └── receiver │ │ │ └── FileObserverJni.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── jiangc │ └── receiver │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | 2 | #### 说明 3 | Android JNI 监控指定目录下的文件以及子目录及子目录下的文件,支持传参控制监控事件,主要使用linux inotify和epoll实现 4 | 5 | #### 注意事项 6 | demo中未申请存储权限,使用时需要手动打开或者申请存储权限 7 | 8 | #### 使用方式 9 | 10 | 11 | ```java 12 | 主要API: 13 | /** 14 | * Create a new file observer for a certain file or directory And start it. 15 | * @param path The file or directory to monitor 16 | * @param mask The event or events (added together) to watch for 17 | */ 18 | public FileObserverJni(String path, int mask, ILifecycle callback) //推荐使用 19 | 20 | /** 21 | * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). 22 | */ 23 | public FileObserverJni(String path) 24 | 25 | 26 | 例子: 27 | String path = Environment.getExternalStorageDirectory().getAbsolutePath(); 28 | Log.e(TAG, "onCreate: " + path); 29 | 30 | FileObserverJni fileObserverJni = new FileObserverJni(path, FileObserverJni.ALL_EVENTS, new FileObserverJni.ILifecycle() { 31 | @Override 32 | public void onInit(int errno) { 33 | if (0 == errno) { 34 | Log.e(TAG, "onInit: 初始化成功"); 35 | } else { 36 | Log.e(TAG, "onInit: 初始化失败: " + FileObserverJni.error2String(errno)); 37 | } 38 | } 39 | 40 | @Override 41 | public void onExit(int errno) { 42 | if (0 == errno) { 43 | Log.e(TAG, "onExit: 正常退出"); 44 | } else { 45 | Log.e(TAG, "onExit: 异常退出: " + errno); 46 | } 47 | } 48 | }); 49 | fileObserverJni.setmCallback(new FileObserverJni.Callback() { 50 | @Override 51 | public void FileObserverEvent(String path, int mask) { 52 | // 在这里监听事件 53 | switch (mask) { 54 | case FileObserverJni.CREATE: 55 | Log.e(TAG, "FileObserverEvent: 文件创建:" + path); 56 | break; 57 | case FileObserverJni.DELETE: 58 | Log.e(TAG, "FileObserverEvent: 文件删除:" + path); 59 | break; 60 | case FileObserverJni.MODIFY: 61 | Log.e(TAG, "FileObserverEvent: 文件修改:" + path); 62 | break; 63 | } 64 | } 65 | }); 66 | 67 | ``` 68 | 69 | 70 | 71 | #### 修改记录: 72 | 73 | ##### 2019/5/9 74 | 1.修改默认监听创建删除事件为可传入mask参数 75 | 2.修改使用接口,仿FileObserver 76 | 3.修改回调方法为统一方法回调 77 | 78 | 79 | 80 | ##### 适配发现 81 | 1.锤子坚果pro事件有问题,除了删除,其他都不好用 82 | 83 | 84 | 85 | ##### 2022/08/28 86 | 87 | 1. 修改项目为Androidx 88 | 2. 修改项目Android.mk 为CMakeLists.txt 89 | 3. 修正没有回调Java接口的bug 90 | 91 | ##### 2022/09/18 92 | 93 | 1. 增加初始化及退出回调 94 | 2. 增加错误码转换接口 95 | 3. 禁用Android Q 分区存储 96 | 4. 增加使用示例 97 | -------------------------------------------------------------------------------- /Tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /Tools/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Tools/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 32 7 | 8 | defaultConfig { 9 | applicationId "com.jiangc.tools" 10 | minSdk 21 11 | targetSdk 32 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled true 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | debug { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | } 32 | 33 | dependencies { 34 | 35 | implementation 'androidx.appcompat:appcompat:1.3.0' 36 | implementation 'com.google.android.material:material:1.4.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 38 | testImplementation 'junit:junit:4.13.2' 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 41 | implementation project(path: ':fileobserver') 42 | } 43 | -------------------------------------------------------------------------------- /Tools/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Tools/app/src/androidTest/java/com/jiangc/tools/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.tools; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jiangc.tools", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tools/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tools/app/src/main/java/com/jiangc/tools/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.tools; 2 | 3 | import android.os.Environment; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.jiangc.receiver.FileObserverJni; 10 | 11 | 12 | /** 13 | * 文件监听demo实现,目前只支持创建和删除事件,默认递归监听子目录,未实现新建目录下文件的监听 14 | */ 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private static final String TAG = "MainActivity"; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | // String path = Environment.getExternalStorageDirectory().getAbsolutePath(); 24 | String path = Environment.getExternalStorageDirectory().getAbsolutePath(); 25 | Log.e(TAG, "onCreate: " + path); 26 | 27 | FileObserverJni fileObserverJni = new FileObserverJni(path, FileObserverJni.ALL_EVENTS, new FileObserverJni.ILifecycle() { 28 | @Override 29 | public void onInit(int errno) { 30 | if (0 == errno) { 31 | Log.e(TAG, "onInit: 初始化成功"); 32 | } else { 33 | Log.e(TAG, "onInit: 初始化失败: " + FileObserverJni.error2String(errno)); 34 | } 35 | } 36 | 37 | @Override 38 | public void onExit(int errno) { 39 | if (0 == errno) { 40 | Log.e(TAG, "onExit: 正常退出"); 41 | } else { 42 | Log.e(TAG, "onExit: 异常退出: " + errno); 43 | } 44 | } 45 | }); 46 | fileObserverJni.setmCallback(new FileObserverJni.Callback() { 47 | @Override 48 | public void FileObserverEvent(String path, int mask) { 49 | // 在这里监听事件 50 | switch (mask) { 51 | case FileObserverJni.CREATE: 52 | Log.e(TAG, "FileObserverEvent: 文件创建:" + path); 53 | break; 54 | case FileObserverJni.DELETE: 55 | Log.e(TAG, "FileObserverEvent: 文件删除:" + path); 56 | break; 57 | case FileObserverJni.MODIFY: 58 | Log.e(TAG, "FileObserverEvent: 文件修改:" + path); 59 | break; 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Tools/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Tools 3 | 4 | -------------------------------------------------------------------------------- /Tools/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Tools/app/src/test/java/com/jiangc/tools/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.tools; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /Tools/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.2.2' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /Tools/fileobserver/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Tools/fileobserver/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdk 32 7 | 8 | defaultConfig { 9 | minSdk 21 10 | targetSdk 32 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles "proguard-rules.pro" 14 | externalNativeBuild { 15 | cmake { 16 | cppFlags '-std=c++11' 17 | } 18 | } 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled true // 开启混淆 24 | zipAlignEnabled true // Zipalign优化 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | debug{ 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | externalNativeBuild { 37 | cmake { 38 | path file('src/main/cpp/CMakeLists.txt') 39 | version '3.18.1' 40 | } 41 | } 42 | 43 | } 44 | 45 | dependencies { 46 | 47 | implementation 'androidx.appcompat:appcompat:1.3.0' 48 | implementation 'com.google.android.material:material:1.4.0' 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 52 | } -------------------------------------------------------------------------------- /Tools/fileobserver/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/androidTest/java/com/jiangc/receiver/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.receiver; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jiangc.fileobserver.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.18.1) 7 | # 包含头文件 8 | include_directories(breakpad/src breakpad/src/common/android/include) 9 | # Declares and names the project. 10 | 11 | project("fileobserver") 12 | 13 | # Creates and names a library, sets it as either STATIC 14 | # or SHARED, and provides the relative paths to its source code. 15 | # You can define multiple libraries, and CMake builds them for you. 16 | # Gradle automatically packages shared libraries with your APK. 17 | 18 | add_library( # Sets the name of the library. 19 | fileobserver 20 | 21 | # Sets the library as a shared library. 22 | SHARED 23 | 24 | # Provides a relative path to your source file(s). 25 | fileObserver.c) 26 | 27 | # Searches for a specified prebuilt library and stores the path as a 28 | # variable. Because CMake includes system libraries in the search path by 29 | # default, you only need to specify the name of the public NDK library 30 | # you want to add. CMake verifies that the library exists before 31 | # completing its build. 32 | 33 | find_library( # Sets the name of the path variable. 34 | log-lib 35 | 36 | # Specifies the name of the NDK library that 37 | # you want CMake to locate. 38 | log) 39 | 40 | # Specifies libraries CMake should link to your target library. You 41 | # can link multiple libraries, such as libraries you define in this 42 | # build script, prebuilt third-party libraries, or system libraries. 43 | 44 | target_link_libraries( # Specifies the target library. 45 | fileobserver 46 | 47 | # Links the target library to the log library 48 | # included in the NDK. 49 | ${log-lib}) 50 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/cpp/err.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by jiangchao on 2022-09-18. 3 | // 4 | 5 | #ifndef TOOLS_ERR_H 6 | #define TOOLS_ERR_H 7 | 8 | #endif //TOOLS_ERR_H 9 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/cpp/fileObserver.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define TAG "fileobserver" 25 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 26 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 27 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 28 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 29 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 30 | 31 | #define MAX_FILES 1000 32 | #define EPOLL_COUNT 1000 33 | #define MAXCOUNT 500 34 | 35 | jclass gl_class; /*类*/ 36 | JavaVM *gl_jvm; /*java虚拟机*/ 37 | jobject gl_object; /*引用类型*/ 38 | 39 | 40 | 41 | int RUN = 1; 42 | 43 | 44 | char *pathName[4096] = {NULL}; //save fd--->path name 45 | int inotifyWd[4096] = {-1}; //保存 inotify_add_watch 返回值,删除的时候需要 46 | 47 | char monitorPath[1024] = {0}; //用来保存监听的目录 48 | 49 | typedef struct { 50 | char path[4096]; 51 | int mask; 52 | } ST_MASK; 53 | 54 | ST_MASK stMask; 55 | 56 | 57 | static char *epoll_files[MAX_FILES]; 58 | 59 | static struct epoll_event mPendingEventItems[EPOLL_COUNT]; 60 | 61 | int mINotifyFd, mEpollFd, i; 62 | 63 | char inotifyBuf[MAXCOUNT]; 64 | 65 | char epollBuf[MAXCOUNT]; 66 | 67 | typedef struct t_name_fd { 68 | int fd; 69 | char name[30]; 70 | 71 | } T_name_fd; 72 | 73 | 74 | T_name_fd t_name_fd[100]; 75 | int count_name_fd; 76 | 77 | 78 | int getfdFromName(char *name) { 79 | int i; 80 | for (i = 0; i < MAX_FILES; i++) { 81 | if (!epoll_files[i]) 82 | continue; 83 | if (0 == strcmp(name, epoll_files[i])) { 84 | return i; 85 | } 86 | } 87 | return -1; 88 | } 89 | 90 | 91 | struct inotify_event *curInotifyEvent; 92 | char name[30]; 93 | int readCount = 0; 94 | int fd; 95 | char cFilePath[4096] = {0}; 96 | 97 | /** 98 | * 初始化错误回调 99 | */ 100 | void InitCallback(JNIEnv *env, jclass cls, int error, int type) { 101 | jmethodID jmethodid; 102 | if (type == 0) { 103 | jmethodid = (*env)->GetStaticMethodID(env, cls, "onInit", "(I)V"); 104 | } else { 105 | jmethodid = (*env)->GetStaticMethodID(env, cls, "onExit", "(I)V"); 106 | } 107 | if (jmethodid == NULL) { 108 | LOGE("event jmethodid == null"); 109 | return; 110 | } 111 | (*env)->CallStaticVoidMethod(env, cls, jmethodid, error); 112 | } 113 | 114 | int scan_dir(JNIEnv *env, jclass cls, const char *dir, int depth) // 定义目录扫描函数 115 | { 116 | DIR *dp; // 定义子目录流指针 117 | struct dirent *entry; // 定义dirent结构指针保存后续目录 118 | struct stat statbuf; // 定义statbuf结构保存文件属性 119 | struct epoll_event eventItem; //epoll event 120 | struct inotify_event inotifyEvent;//inotify event 121 | int lnotifyFD; 122 | int lwd; //inotify_add_watch 返回值 123 | char path[1024] = {0}; 124 | if ((dp = opendir(dir)) == NULL) // 打开目录,获取子目录流指针,判断操作是否成功 125 | { 126 | LOGE("can't open dir ------> %d\n", errno); 127 | InitCallback(env, cls, errno, 0); 128 | return -1; 129 | } 130 | chdir(dir); // 切换到当前目录 131 | while ((entry = readdir(dp)) != NULL) // 获取下一级目录信息,如果未否则循环 132 | { 133 | lstat(entry->d_name, &statbuf); // 获取下一级成员属性 134 | if (S_IFDIR & statbuf.st_mode) // 判断下一级成员是否是目录 135 | { 136 | if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) 137 | continue; 138 | //printf("%*s%s/\n", depth, "", entry->d_name); // 输出目录名称 139 | char *path1 = realpath("./", NULL); 140 | if (NULL != path1) { 141 | lnotifyFD = inotify_init(); 142 | sprintf(path, "%s/%s", path1, entry->d_name); //get absolute path 143 | lwd = inotify_add_watch(lnotifyFD, path, 144 | IN_DELETE | IN_CREATE);//监听xxx目录下的 delete、create事件 145 | if (-1 == lwd) { 146 | LOGE("-1 == LWD\n"); 147 | continue; 148 | } 149 | eventItem.events = EPOLLIN; 150 | eventItem.data.fd = lnotifyFD; 151 | epoll_ctl(mEpollFd, EPOLL_CTL_ADD, lnotifyFD, &eventItem); //add to epoll 152 | if (lnotifyFD < 4096) { 153 | char *p = malloc(strlen(path) + 1); 154 | memset(p, 0, strlen(path) + 1); 155 | if (NULL != p) { 156 | memcpy(p, path, strlen(path)); 157 | pathName[lnotifyFD] = p; 158 | inotifyWd[lnotifyFD] = lwd; 159 | } 160 | } 161 | free(path1); 162 | } 163 | scan_dir(env, cls, entry->d_name, depth + 4); // 递归调用自身,扫描下一级目录的内容 164 | } 165 | } 166 | chdir(".."); // 回到上级目录 167 | closedir(dp); // 关闭子目录流 168 | return 0; 169 | } 170 | 171 | char creatPath[1024] = {0}; 172 | char deletePath[1024] = {0}; 173 | int a = -1; 174 | 175 | void Event(JNIEnv *env, jclass cls, char *path, int mask) { 176 | jmethodID jmethodid; 177 | jmethodid = (*env)->GetStaticMethodID(env, cls, "FileObserverEvent", "(Ljava/lang/String;I)V"); 178 | if (jmethodid == NULL) { 179 | LOGE("event jmethodid == null"); 180 | return; 181 | } 182 | jstring str = (*env)->NewStringUTF(env, path); 183 | (*env)->CallStaticVoidMethod(env, cls, jmethodid, str, mask); 184 | (*env)->DeleteLocalRef(env, str); 185 | } 186 | 187 | /** 188 | *事件处理函数 189 | */ 190 | void ProcessEvent(JNIEnv *env, jclass cls, const char *pathName, const char *fileName, int mask) { 191 | memset(cFilePath, 0, sizeof(cFilePath)); 192 | sprintf(cFilePath, "%s/%s", pathName, fileName); 193 | Event(env, cls, cFilePath, mask); 194 | } 195 | 196 | 197 | /** 198 | * 199 | */ 200 | int *fileObserver_init(ST_MASK *stMask) { 201 | int i; 202 | struct epoll_event eventItem; //epoll event 203 | struct inotify_event inotifyEvent; //inotify event 204 | JNIEnv *env; 205 | jmethodID jmethodid; 206 | 207 | char path[4096]; 208 | int mask; 209 | 210 | i = 0; 211 | memset(&eventItem, 0, sizeof(struct epoll_event)); 212 | memset(&inotifyEvent, 0, sizeof(struct inotify_event)); 213 | env = NULL; 214 | jmethodid = 0; 215 | memset(path, 0, sizeof(path)); 216 | mask = 0; 217 | 218 | if (NULL == stMask) { 219 | return (int *) -1; 220 | } 221 | memcpy(path, stMask->path, sizeof(path)); 222 | mask = stMask->mask; 223 | 224 | if (gl_jvm == NULL) { 225 | LOGE("gl_jvm is NULL"); 226 | return (int *) -1; 227 | } 228 | 229 | (*gl_jvm)->AttachCurrentThread(gl_jvm, &env, NULL); 230 | 231 | //0. add sub dir inotify 232 | mEpollFd = epoll_create(1000); 233 | // 1. init inotify & epoll 234 | int homeINotifyFd = inotify_init(); 235 | char *p = malloc(strlen(path) + 1); 236 | if (NULL == p) { 237 | LOGE("malloc failed = NULL \n"); 238 | a = -1; 239 | InitCallback(env, gl_class, errno, 0); 240 | return &a; 241 | } 242 | memset(p, 0, strlen(path) + 1); 243 | memcpy(p, path, strlen(path) + 1); 244 | pathName[homeINotifyFd] = p; 245 | // 2.add inotify watch dir 246 | int lwd = inotify_add_watch(homeINotifyFd, pathName[homeINotifyFd], mask);//监听xxx目录下的 mask事件 247 | if (-1 == lwd) { 248 | LOGE(" inotify_add_watch -------> errno = %d\n", errno); 249 | InitCallback(env, gl_class, errno, 0); 250 | return &a; 251 | } 252 | 253 | inotifyWd[homeINotifyFd] = lwd; 254 | 255 | // 3. add inotify fd to epoll 256 | eventItem.events = EPOLLIN; 257 | eventItem.data.fd = homeINotifyFd; 258 | epoll_ctl(mEpollFd, EPOLL_CTL_ADD, homeINotifyFd, &eventItem); 259 | int ret = scan_dir(env, gl_class, path, 0); 260 | if (ret == 0) 261 | InitCallback(env, gl_class, 0, 0); 262 | while (RUN) { 263 | // 4.epoll检测文件的可读变化 264 | int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_COUNT, -1); 265 | for (i = 0; i < pollResult; i++) { 266 | readCount = 0; 267 | readCount = read(mPendingEventItems[i].data.fd, inotifyBuf, MAXCOUNT); 268 | 269 | if (readCount < sizeof(inotifyEvent)) { 270 | LOGE("error inotify event \n"); 271 | InitCallback(env, gl_class, errno, 1); 272 | return &a; 273 | } 274 | 275 | // cur 指针赋值 276 | curInotifyEvent = (struct inotify_event *) inotifyBuf; 277 | 278 | while (readCount >= sizeof(inotifyEvent)) { 279 | if (curInotifyEvent->len > 0) { 280 | if (curInotifyEvent->mask & IN_CREATE) { 281 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 282 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 283 | curInotifyEvent->name, IN_CREATE); 284 | } else { 285 | LOGE("IN_CREATE name[mPendingEventItems[i].data.fd] == NULL\n"); 286 | } 287 | } else if (curInotifyEvent->mask & IN_DELETE) { 288 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 289 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 290 | curInotifyEvent->name, IN_DELETE); 291 | } else { 292 | LOGE("IN_DELETE name[mPendingEventItems[i].data.fd] == NULL\n"); 293 | } 294 | } else if (curInotifyEvent->mask & IN_ACCESS) { 295 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 296 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 297 | curInotifyEvent->name, IN_ACCESS); 298 | } else { 299 | LOGE("IN_ACCESS name[mPendingEventItems[i].data.fd] == NULL\n"); 300 | } 301 | } else if (curInotifyEvent->mask & IN_ATTRIB) { 302 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 303 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 304 | curInotifyEvent->name, IN_ATTRIB); 305 | } else { 306 | LOGE("IN_ATTRIB name[mPendingEventItems[i].data.fd] == NULL\n"); 307 | } 308 | } else if (curInotifyEvent->mask & IN_CLOSE_WRITE) { 309 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 310 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 311 | curInotifyEvent->name, IN_CLOSE_WRITE); 312 | } else { 313 | LOGE("IN_CLOSE_WRITE name[mPendingEventItems[i].data.fd] == NULL\n"); 314 | } 315 | 316 | } else if (curInotifyEvent->mask & IN_CLOSE_NOWRITE) { 317 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 318 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 319 | curInotifyEvent->name, IN_CLOSE_NOWRITE); 320 | } else { 321 | LOGE("IN_CLOSE_NOWRITE name[mPendingEventItems[i].data.fd] == NULL\n"); 322 | } 323 | 324 | } else if (curInotifyEvent->mask & IN_DELETE_SELF) { 325 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 326 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 327 | curInotifyEvent->name, IN_DELETE_SELF); 328 | } else { 329 | LOGE("IN_DELETE_SELF name[mPendingEventItems[i].data.fd] == NULL\n"); 330 | } 331 | } else if (curInotifyEvent->mask & IN_MODIFY) { 332 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 333 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 334 | curInotifyEvent->name, IN_MODIFY); 335 | } else { 336 | LOGE("IN_MODIFY name[mPendingEventItems[i].data.fd] == NULL\n"); 337 | } 338 | 339 | } else if (curInotifyEvent->mask & IN_MOVE_SELF) { 340 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 341 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 342 | curInotifyEvent->name, IN_MOVE_SELF); 343 | } else { 344 | LOGE("IN_MOVE_SELF name[mPendingEventItems[i].data.fd] == NULL\n"); 345 | } 346 | } else if (curInotifyEvent->mask & IN_MOVED_FROM) { 347 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 348 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 349 | curInotifyEvent->name, IN_MOVED_FROM); 350 | } else { 351 | LOGE("IN_MOVED_FROM name[mPendingEventItems[i].data.fd] == NULL\n"); 352 | } 353 | 354 | } else if (curInotifyEvent->mask & IN_MOVED_TO) { 355 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 356 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 357 | curInotifyEvent->name, IN_MOVED_TO); 358 | } else { 359 | LOGE("IN_MOVED_TO name[mPendingEventItems[i].data.fd] == NULL\n"); 360 | } 361 | } else if (curInotifyEvent->mask & IN_OPEN) { 362 | if (pathName[mPendingEventItems[i].data.fd] != NULL) { 363 | ProcessEvent(env, gl_class, pathName[mPendingEventItems[i].data.fd], 364 | curInotifyEvent->name, IN_OPEN); 365 | } else { 366 | LOGE("IN_OPEN name[mPendingEventItems[i].data.fd] == NULL\n"); 367 | } 368 | } 369 | } 370 | curInotifyEvent--; 371 | readCount -= sizeof(inotifyEvent); 372 | } 373 | } 374 | } 375 | InitCallback(env, gl_class, 0, 1); 376 | (*gl_jvm)->DetachCurrentThread(gl_jvm); 377 | LOGE("退出"); 378 | return 0; 379 | } 380 | 381 | /** 382 | * 383 | *释放 384 | * 385 | */ 386 | int FileObserverDestroy() { 387 | int i = 0; 388 | for (i = 0; i < 4096; i++) //这里释放inotify的fd和申请的内存 389 | { 390 | if (pathName[i] != NULL) { 391 | RUN = 0; 392 | free(pathName[i]); 393 | inotify_rm_watch(i, inotifyWd[i]); 394 | } 395 | } 396 | 397 | return 0; 398 | } 399 | 400 | pthread_t thread_1 = -1; 401 | 402 | 403 | int FileObserverInit(const char *path, int mask) { 404 | memset(&stMask, 0, sizeof(ST_MASK)); 405 | memcpy(stMask.path, path, strlen(path)); 406 | stMask.mask = mask; 407 | if (-1 == thread_1) { 408 | pthread_create(&thread_1, NULL, (void *(*)(void *)) fileObserver_init, &stMask); 409 | } 410 | return 0; 411 | } 412 | 413 | #ifdef __cplusplus 414 | extern "C" { 415 | #endif 416 | /* 417 | * Class: com_jiangc_receiver_FileObserverJni 418 | * Method: FileObserverInit 419 | * Signature: (Ljava/lang/String;I)I 420 | */ 421 | JNIEXPORT jint JNICALL 422 | Java_com_jiangc_receiver_FileObserverJni_FileObserverInit(JNIEnv *env, jclass clazz, jstring path, 423 | jint mask) { 424 | const char *str = (*env)->GetStringUTFChars(env, path, 0); 425 | 426 | memset(monitorPath, 0, sizeof(monitorPath)); 427 | memcpy(monitorPath, str, strlen(str)); 428 | 429 | /*获取全局的JavaVM以及object*/ 430 | (*env)->GetJavaVM(env, &gl_jvm); 431 | if (NULL == gl_jvm) { 432 | LOGE("gl_jvm = NULL"); 433 | } 434 | gl_class = (*env)->NewGlobalRef(env, clazz); 435 | FileObserverInit(monitorPath, mask); 436 | (*env)->ReleaseStringUTFChars(env, path, str); 437 | return 0; 438 | } 439 | 440 | /* 441 | * Class: com_jiangc_fileobserver_FileObserverJni 442 | * Method: FileObserverDestroy 443 | * Signature: ()I 444 | */ 445 | JNIEXPORT jint JNICALL 446 | Java_com_jiangc_receiver_FileObserverJni_FileObserverDestroy(JNIEnv *env, jclass cls) { 447 | (*env)->DeleteGlobalRef(env, gl_class); //释放全局的object 448 | FileObserverDestroy(); 449 | return 0; 450 | } 451 | 452 | JNIEXPORT jstring JNICALL 453 | Java_com_jiangc_receiver_FileObserverJni_error2String(JNIEnv *env, jclass clazz, jint err) { 454 | jstring str = (*env)->NewStringUTF(env, strerror(err)); 455 | return str; 456 | } 457 | 458 | #ifdef __cplusplus 459 | } 460 | #endif 461 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/java/com/jiangc/receiver/FileObserverJni.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.receiver; 2 | 3 | import android.util.Log; 4 | 5 | import java.net.PasswordAuthentication; 6 | 7 | public class FileObserverJni { 8 | private static final String TAG = "FileObserverJni"; 9 | 10 | //Event 11 | /** 12 | * Event type: Data was read from a file 13 | */ 14 | public static final int ACCESS = 0x00000001; 15 | /** 16 | * Event type: Data was written to a file 17 | */ 18 | public static final int MODIFY = 0x00000002; 19 | /** 20 | * Event type: Metadata (permissions, owner, timestamp) was changed explicitly 21 | */ 22 | public static final int ATTRIB = 0x00000004; 23 | /** 24 | * Event type: Someone had a file or directory open for writing, and closed it 25 | */ 26 | public static final int CLOSE_WRITE = 0x00000008; 27 | /** 28 | * Event type: Someone had a file or directory open read-only, and closed it 29 | */ 30 | public static final int CLOSE_NOWRITE = 0x00000010; 31 | /** 32 | * Event type: A file or directory was opened 33 | */ 34 | public static final int OPEN = 0x00000020; 35 | /** 36 | * Event type: A file or subdirectory was moved from the monitored directory 37 | */ 38 | public static final int MOVED_FROM = 0x00000040; 39 | /** 40 | * Event type: A file or subdirectory was moved to the monitored directory 41 | */ 42 | public static final int MOVED_TO = 0x00000080; 43 | /** 44 | * Event type: A new file or subdirectory was created under the monitored directory 45 | */ 46 | public static final int CREATE = 0x00000100; 47 | /** 48 | * Event type: A file was deleted from the monitored directory 49 | */ 50 | public static final int DELETE = 0x00000200; 51 | /** 52 | * Event type: The monitored file or directory was deleted; monitoring effectively stops 53 | */ 54 | public static final int DELETE_SELF = 0x00000400; 55 | /** 56 | * Event type: The monitored file or directory was moved; monitoring continues 57 | */ 58 | public static final int MOVE_SELF = 0x00000800; 59 | 60 | /** 61 | * Event mask: All valid event types, combined 62 | */ 63 | public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE 64 | | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE 65 | | DELETE_SELF | MOVE_SELF; 66 | 67 | 68 | private static Callback mCallback = null; 69 | private static ILifecycle mInitCallback = null; 70 | 71 | /** 72 | * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). 73 | */ 74 | public FileObserverJni(String path) { 75 | this(path, ALL_EVENTS); 76 | } 77 | 78 | /** 79 | * Create a new file observer for a certain file or directory And start it. 80 | * 81 | * @param path The file or directory to monitor 82 | * @param mask The event or events (added together) to watch for 83 | */ 84 | @Deprecated 85 | public FileObserverJni(String path, int mask) { 86 | this(path, mask, null); 87 | } 88 | 89 | public FileObserverJni(String path, int mask, ILifecycle callback) { 90 | FileObserverInit(path, mask); 91 | FileObserverJni.mInitCallback = callback; 92 | } 93 | 94 | public interface Callback { 95 | /** 96 | * 总的事件出口,前面的都弃用 97 | * 98 | * @param path 99 | * @param mask 100 | */ 101 | void FileObserverEvent(String path, int mask); 102 | } 103 | 104 | /** 105 | * 初始化回调 106 | */ 107 | public interface ILifecycle { 108 | /** 109 | * 初始化失败时会回调该接口,具体错误原因可使用error2String查看 110 | * 111 | * @param errno 错误码 112 | */ 113 | void onInit(int errno); 114 | 115 | void onExit(int errno); 116 | } 117 | 118 | 119 | /** 120 | * 总的事件入口,供native调用 121 | * 122 | * @param path 123 | * @param mask 124 | */ 125 | private static void FileObserverEvent(String path, int mask) { 126 | if (mCallback != null) { 127 | mCallback.FileObserverEvent(path, mask); 128 | } 129 | } 130 | 131 | 132 | public void setmCallback(Callback mCallback) { 133 | FileObserverJni.mCallback = mCallback; 134 | } 135 | 136 | static { 137 | try { 138 | System.loadLibrary("fileobserver"); 139 | } catch (Exception e) { 140 | Log.e(TAG, "static initializer: 加载fileObserver库失败"); 141 | } 142 | } 143 | 144 | /** 145 | * 初始化要监听的目录,并启动线程 146 | * 147 | * @param path 148 | * @param mask 149 | * @return 150 | */ 151 | public static native int FileObserverInit(String path, int mask); 152 | 153 | 154 | /** 155 | * 释放资源 156 | * 157 | * @return 158 | */ 159 | public static native int FileObserverDestroy(); 160 | 161 | /** 162 | * 错误码转字符串 163 | * 164 | * @param errno callback中的错误码 165 | * @return 错误原因 166 | */ 167 | public static native String error2String(int errno); 168 | 169 | 170 | /** 171 | * native回调 172 | * 173 | * @param errno 错误码 0初始化成功,其他:异常,查看错误码 174 | */ 175 | public static void onInit(int errno) { 176 | if (null != mInitCallback) { 177 | mInitCallback.onInit(errno); 178 | } 179 | } 180 | 181 | /** 182 | * native回调 183 | * 184 | * @param errno 错误码 0正常退出,其他:异常 185 | */ 186 | public static void onExit(int errno) { 187 | if (null != mInitCallback) { 188 | mInitCallback.onExit(errno); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FileObserver 3 | 4 | -------------------------------------------------------------------------------- /Tools/fileobserver/src/test/java/com/jiangc/receiver/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangc.receiver; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /Tools/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /Tools/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangchaochao/fileObserver/f96bec41230293ad4815ee49900366785b828b72/Tools/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Tools/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 28 21:14:05 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /Tools/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /Tools/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /Tools/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':fileobserver' 2 | --------------------------------------------------------------------------------