├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
├── release
│ ├── app-release.apk
│ └── output.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── snail
│ │ └── labaffinity
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── snail
│ │ │ └── labaffinity
│ │ │ ├── activity
│ │ │ ├── BaseActivity.java
│ │ │ ├── FpsTestActivity.java
│ │ │ ├── LauncherTestActivity.java
│ │ │ ├── LeakTestActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SplashActivity.java
│ │ │ └── TrafficTestActivity.java
│ │ │ ├── adapter
│ │ │ ├── MyFragment.java
│ │ │ └── MyFragmentPagerAdapter.java
│ │ │ ├── app
│ │ │ ├── LabApplication.java
│ │ │ └── MyButton.java
│ │ │ ├── service
│ │ │ └── BackGroundService.java
│ │ │ └── utils
│ │ │ ├── AppProfile.java
│ │ │ ├── LogUtils.java
│ │ │ └── ToastUtil.java
│ └── res
│ │ ├── anim
│ │ ├── activity_no_anim.xml
│ │ ├── activity_slide_left_in.xml
│ │ ├── activity_slide_left_out.xml
│ │ ├── activity_slide_right_in.xml
│ │ ├── activity_slide_right_out.xml
│ │ ├── anim_fade_in.xml
│ │ ├── anim_fade_out.xml
│ │ ├── anim_float_button_fade_in.xml
│ │ ├── anim_float_button_fade_out.xml
│ │ ├── anim_nug_fade_out.xml
│ │ ├── anim_nug_in.xml
│ │ ├── anim_nug_tran_down.xml
│ │ ├── anim_set_bounce.xml
│ │ ├── anim_webview_load_in.xml
│ │ ├── popwindow_fade_in.xml
│ │ ├── popwindow_fade_out.xml
│ │ ├── popwindow_push_bottom_in.xml
│ │ ├── popwindow_push_bottom_out.xml
│ │ ├── popwindow_stretch_show.xml
│ │ ├── slide_left_in.xml
│ │ ├── slide_left_out.xml
│ │ ├── slide_right_in.xml
│ │ └── slide_right_out.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_second.xml
│ │ └── content_main.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── snail
│ └── labaffinity
│ └── ExampleUnitTest.java
├── build.gradle
├── collie
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── snail
│ └── collie
│ ├── Collie.kt
│ ├── CollieListener.kt
│ ├── Config.kt
│ ├── battery
│ ├── BatteryInfo.kt
│ ├── BatteryLevelReceiver.kt
│ └── BatteryStatsTracker.kt
│ ├── core
│ ├── ActivityStack.kt
│ ├── CollieHandlerThread.kt
│ ├── ITracker.kt
│ ├── LooperMonitor.kt
│ ├── ProcessUtil.kt
│ ├── ReflectFiled.kt
│ ├── ReflectMethod.kt
│ ├── ReflectUtils.kt
│ └── SimpleActivityLifecycleCallbacks.kt
│ ├── cpu
│ └── CpuInfoTracker.kt
│ ├── debug
│ ├── DebugHelper.kt
│ ├── FloatHelper.kt
│ ├── FloatingFpsView.kt
│ └── MeasureUtil.kt
│ ├── fps
│ ├── ANRMonitorRunnable.kt
│ ├── CollectItem.kt
│ ├── FpsThread.kt
│ ├── FpsTracker.java
│ └── ITrackFpsListener.kt
│ ├── mem
│ ├── AppMemory.kt
│ ├── FdLeakTrack.kt
│ ├── MemoryLeakTrack.kt
│ ├── SystemMemory.kt
│ └── TrackMemoryInfo.kt
│ ├── startup
│ └── LauncherTracker.kt
│ └── trafficstats
│ ├── ITrackTrafficStatsListener.kt
│ ├── TrafficStatsItem.kt
│ └── TrafficStatsTracker.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── keystore.jks
├── publish.gradle
├── release.properties
├── settings.gradle
└── sonar-project.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | /.idea
10 | /app/build
11 | /.gralde
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Collie
2 |
3 | 轻量级Android性能监测工具
4 |
5 | * FPS监测及卡顿监测: 利用Looper的printLoop来实现
6 | * 流量监测: Trafficstats
7 | * 耗电 :Battery BroadCast 似乎意义不是特别大
8 | * 内存占用:Debug
9 | * 内存泄漏:weakHashMap
10 | * 启动耗时:ContentProvier+onwindforcus
11 |
12 |
13 | 技术文档:[Android线上轻量级APM性能监测方案](https://juejin.im/post/6872151038305140744)
14 |
15 |
16 |
17 | ### 使用方法 mavenCenter
18 |
19 | app的build.gradle添加
20 |
21 | implementation 'io.github.happylishang:collie:1.1.8'
22 |
23 |
24 |
25 | Application中添加
26 |
27 | Collie.getInstance().init(this, new Config(true, true, true, true, true, true), new CollieListener() {
28 |
29 | @Override
30 | public void onTrafficStats(Activity activity, long value) {
31 | Log.v("Collie", "" + activity.getClass().getSimpleName() + " 流量消耗 " + value * 1.0f / (1024 * 1024) + "M");
32 |
33 | }
34 |
35 | @Override
36 | public void onBatteryCost(BatteryInfo batteryInfo) {
37 | Log.v("Collie", " 电量流量消耗 " +batteryInfo.cost);
38 |
39 | }
40 |
41 | @Override
42 | public void onAppColdLaunchCost(long duration ,String processName) {
43 | Log.v("Collie", "启动耗时 " + duration +" processName "+processName);
44 | }
45 |
46 | @Override
47 | public void onActivityLaunchCost(Activity activity, long duration,boolean finishNow) {
48 | Log.v("Collie", "activity启动耗时 " + activity + " " + duration + " finishNow "+finishNow);
49 | }
50 |
51 | @Override
52 | public void onLeakActivity(String activity, int count) {
53 | Log.v("Collie", "内存泄露 " + activity + " 数量 " + count);
54 | }
55 |
56 | @Override
57 | public void onCurrentMemoryCost(TrackMemoryInfo trackMemoryInfo) {
58 | Log.v("Collie", "内存 " + trackMemoryInfo.procName + " java内存 "
59 | + trackMemoryInfo.appMemory.dalvikPss + " native内存 " +
60 | trackMemoryInfo.appMemory.nativePss);
61 | }
62 |
63 | @Override
64 | public void onFpsTrack(Activity activity, long currentCostMils, long currentDropFrame, boolean isInFrameDraw, long averageFps) {
65 | if (currentDropFrame >= 2)
66 | Log.v("Collie", "Activity " + activity + " 掉帧 " + currentDropFrame + " 是否因为Choro 绘制掉帧 " + isInFrameDraw + " 1s 平均帧率" + averageFps);
67 | }
68 |
69 | @Override
70 | public void onANRAppear(Activity activity) {
71 | Log.v("Collie", "Activity " + activity + " ANR " );
72 |
73 | }
74 | });
75 | }
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'com.google.gms.google-services'
4 | apply plugin: 'com.google.firebase.crashlytics'
5 |
6 | android {
7 | compileSdkVersion 33
8 | viewBinding {
9 | enabled = true
10 | }
11 | defaultConfig {
12 | applicationId "com.snail.labaffinity"
13 | minSdkVersion 21
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.3"
17 | firebaseCrashlytics {
18 | nativeSymbolUploadEnabled true
19 | strippedNativeLibsDir "src/main/libs"
20 | unstrippedNativeLibsDir "src/main/libs"
21 | }
22 | }
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility 1.8
32 | targetCompatibility 1.8
33 | }
34 |
35 |
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 | implementation 'androidx.appcompat:appcompat:1.1.0'
41 | implementation 'com.google.android.material:material:1.1.0'
42 | implementation 'io.reactivex:rxjava:1.1.8'
43 | implementation 'io.reactivex:rxandroid:1.2.1'
44 | implementation 'com.github.campusappcn.AndRouter:router:1.2.8'
45 | implementation project(path: ':collie')
46 | // implementation 'io.github.happylishang:collie:1.1.8'
47 | // annotationProcessor 'com.github.campusappcn.AndRouter:compiler:1.2.8'
48 | implementation "androidx.core:core-ktx:1.8.0"
49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
50 |
51 |
52 | }
53 | repositories {
54 | mavenCentral()
55 | }
56 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "1012032823686",
4 | "firebase_url": "https://labaffinity-3f75c.firebaseio.com",
5 | "project_id": "labaffinity-3f75c",
6 | "storage_bucket": "labaffinity-3f75c.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:1012032823686:android:de46c1bd2db18425",
12 | "android_client_info": {
13 | "package_name": "com.snail.labaffinity"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "1012032823686-dc79a79gd0c5pksh6993k7a62dk6k0n7.apps.googleusercontent.com",
19 | "client_type": 1,
20 | "android_info": {
21 | "package_name": "com.snail.labaffinity",
22 | "certificate_hash": "cea4da8f6a541a140a6a64e08604bc07f87364f4"
23 | }
24 | },
25 | {
26 | "client_id": "1012032823686-b3gkava655j7ch0ppc12rjfk2g66tmcp.apps.googleusercontent.com",
27 | "client_type": 3
28 | }
29 | ],
30 | "api_key": [
31 | {
32 | "current_key": "AIzaSyCd7F0BkgeeZMxJpWT-NQQ2nrlns2K28mo"
33 | }
34 | ],
35 | "services": {
36 | "appinvite_service": {
37 | "other_platform_oauth_client": [
38 | {
39 | "client_id": "1012032823686-b3gkava655j7ch0ppc12rjfk2g66tmcp.apps.googleusercontent.com",
40 | "client_type": 3
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | ],
47 | "configuration_version": "1"
48 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/personal/software/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -dontwarn com.tencent.bugly.**
19 | -keep public class com.tencent.bugly.**{*;}
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happylishang/Collie/bfdc6782d568bfcefef01e846e81ccfd5a7e3470/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/snail/labaffinity/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
23 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.os.Bundle;
4 | import android.os.SystemClock;
5 |
6 | import androidx.annotation.LayoutRes;
7 | import androidx.annotation.Nullable;
8 | import androidx.appcompat.app.AppCompatActivity;
9 | import androidx.appcompat.widget.Toolbar;
10 |
11 | import com.snail.labaffinity.R;
12 | import com.snail.labaffinity.utils.LogUtils;
13 |
14 | /**
15 | * Author: hzlishang
16 | * Data: 16/10/12 上午9:57
17 | * Des:
18 | * version:
19 | */
20 | public class BaseActivity extends AppCompatActivity {
21 | @Override
22 | protected void onCreate(@Nullable Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | }
25 |
26 |
27 | @Override
28 | public void setContentView(@LayoutRes int layoutResID) {
29 | super.setContentView(layoutResID);
30 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
31 | setSupportActionBar(toolbar);
32 |
33 | }
34 |
35 |
36 | @Override
37 | protected void onStop() {
38 | super.onStop();
39 | LogUtils.v("onStop " + this + SystemClock.uptimeMillis());
40 |
41 | }
42 |
43 | @Override
44 | protected void onPause() {
45 | super.onPause();
46 | LogUtils.v("onPause " + this + SystemClock.uptimeMillis());
47 | }
48 |
49 | @Override
50 | protected void onDestroy() {
51 | super.onDestroy();
52 | LogUtils.v("onDestroy " + this + SystemClock.uptimeMillis());
53 | }
54 |
55 | @Override
56 | protected void onResume() {
57 | super.onResume();
58 | LogUtils.v("onResume " + this + SystemClock.uptimeMillis());
59 | }
60 |
61 | // 第一帧的显示点,基本是在这里
62 | @Override
63 | public void onWindowFocusChanged(boolean hasFocus) {
64 |
65 | LogUtils.v("onWindowFocusChanged " + this + SystemClock.uptimeMillis());
66 | super.onWindowFocusChanged(hasFocus);
67 | }
68 |
69 | protected void onPostResume() {
70 |
71 | super.onPostResume();
72 | LogUtils.v("onPostResumem" + this + SystemClock.uptimeMillis());
73 |
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/FpsTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.os.Bundle;
4 | import android.os.SystemClock;
5 | import android.view.ViewGroup;
6 | import android.view.ViewTreeObserver;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.LinearLayoutManager;
11 | import androidx.recyclerview.widget.RecyclerView;
12 |
13 | import com.snail.labaffinity.databinding.ActivitySecondBinding;
14 | import com.snail.labaffinity.utils.LogUtils;
15 |
16 |
17 | public class FpsTestActivity extends BaseActivity {
18 |
19 | private int count;
20 | private ActivitySecondBinding mActivitySecondBinding;
21 |
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 |
27 | mActivitySecondBinding = ActivitySecondBinding.inflate(getLayoutInflater());
28 | setContentView(mActivitySecondBinding.getRoot());
29 | LinearLayoutManager linearLayoutManager= new LinearLayoutManager(this);
30 | // linearLayoutManager.setItemPrefetchEnabled(false);
31 |
32 | mActivitySecondBinding.recy.setLayoutManager(linearLayoutManager);
33 | mActivitySecondBinding.recy.setAdapter(new RecyclerView.Adapter() {
34 | @NonNull
35 | @Override
36 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
37 | return new RecyclerView.ViewHolder(new TextView(FpsTestActivity.this)) {
38 | {
39 |
40 | }
41 | };
42 | }
43 |
44 | @Override
45 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
46 | // LogUtils.v("位置 " + position);
47 | if (position > 10)
48 | SystemClock.sleep(30);
49 | ((TextView) holder.itemView).setHeight(500);
50 | ((TextView) holder.itemView).setText("位置 " + position);
51 |
52 | // Log.d("lishang", Log.getStackTraceString(new Throwable()));
53 |
54 | }
55 |
56 | @Override
57 | public int getItemCount() {
58 | return 1000;
59 | }
60 | });
61 | }
62 |
63 |
64 | @Override
65 | protected void onResume() {
66 | super.onResume();
67 | LogUtils.v("onResume " +SystemClock.uptimeMillis());
68 | }
69 |
70 | // 第一帧的显示点,基本是在这里
71 | @Override
72 | public void onWindowFocusChanged(boolean hasFocus) {
73 | LogUtils.v("onWindowFocusChanged " + SystemClock.uptimeMillis());
74 | super.onWindowFocusChanged(hasFocus);
75 | }
76 |
77 | protected void onPostResume() {
78 |
79 | super.onPostResume();
80 | LogUtils.v("onPostResumem" +SystemClock.uptimeMillis());
81 |
82 | }
83 |
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/LauncherTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.os.Bundle;
4 | import android.os.SystemClock;
5 |
6 | import com.snail.labaffinity.app.MyButton;
7 | import com.snail.labaffinity.databinding.ActivitySecondBinding;
8 | import com.snail.labaffinity.utils.LogUtils;
9 |
10 |
11 | public class LauncherTestActivity extends BaseActivity {
12 |
13 | private int count;
14 | private ActivitySecondBinding mActivitySecondBinding;
15 |
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | MyButton textView = new MyButton(this);
21 | textView.setText("LauncherTestActivity");
22 | setContentView(textView);
23 | }
24 |
25 | @Override
26 | protected void onResume() {
27 | super.onResume();
28 |
29 | // Debug.stopMethodTracing();
30 |
31 |
32 | // SystemClock.sleep(2000);
33 | }
34 |
35 | @Override
36 | public void onWindowFocusChanged(boolean hasFocus) {
37 | super.onWindowFocusChanged(hasFocus);
38 | }
39 |
40 | @Override
41 | public void onAttachedToWindow() {
42 | // 绘制之前
43 | super.onAttachedToWindow();
44 | // SystemClock.sleep(1000);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/LeakTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.app.Activity;
4 | import android.app.ListActivity;
5 | import android.net.http.SslError;
6 | import android.os.Bundle;
7 | import android.webkit.SslErrorHandler;
8 | import android.webkit.WebChromeClient;
9 | import android.webkit.WebResourceRequest;
10 | import android.webkit.WebSettings;
11 | import android.webkit.WebView;
12 | import android.webkit.WebViewClient;
13 | import android.widget.TextView;
14 |
15 | import androidx.annotation.Nullable;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | public class LeakTestActivity extends BaseActivity {
21 |
22 | public static List sActivity=new ArrayList<>();
23 | @Override
24 | protected void onCreate(@Nullable Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | sActivity .add(this);
27 | TextView textView = new TextView(this);
28 | textView.setText("LeakTestActivity");
29 | setContentView(textView);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import static com.snail.labaffinity.app.LabApplication.sLaunchCost;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.os.SystemClock;
10 | import android.util.Log;
11 | import android.view.View;
12 |
13 | import com.snail.labaffinity.databinding.ActivityMainBinding;
14 | import com.snail.labaffinity.service.BackGroundService;
15 |
16 | public class MainActivity extends BaseActivity {
17 |
18 | private int count;
19 | ActivityMainBinding mResultProfileBinding;
20 | long start=SystemClock.uptimeMillis();
21 | @SuppressLint("SetTextI18n")
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 |
25 | super.onCreate(savedInstanceState);
26 |
27 | startService(new Intent(this, BackGroundService.class));
28 |
29 | mResultProfileBinding = ActivityMainBinding.inflate(getLayoutInflater());
30 | setContentView(mResultProfileBinding.getRoot());
31 | mResultProfileBinding.contentMain1.activityStart.setOnClickListener(new View.OnClickListener() {
32 | @Override
33 | public void onClick(View v) {
34 | // LabApplication.startTrace(MainActivity.this);
35 | Intent mIntent=new Intent( MainActivity.this, LauncherTestActivity.class);
36 | startActivity(mIntent);
37 | }
38 | });
39 |
40 | mResultProfileBinding.contentMain1.first.setOnClickListener(v -> {
41 | Intent mIntent=new Intent( MainActivity.this, LeakTestActivity.class);
42 | startActivity(mIntent);
43 |
44 | });
45 | mResultProfileBinding.contentMain1.second.setOnClickListener(new View.OnClickListener() {
46 | @Override
47 | public void onClick(View v) {
48 | Intent mIntent=new Intent( MainActivity.this, TrafficTestActivity.class);
49 | startActivity(mIntent);
50 |
51 | }
52 | });
53 | mResultProfileBinding.contentMain1.third.setOnClickListener(new View.OnClickListener() {
54 | @SuppressLint("InvalidAnalyticsName")
55 | @Override
56 | public void onClick(View v) {
57 |
58 | Intent mIntent=new Intent(MainActivity.this, FpsTestActivity.class);
59 | startActivity(mIntent);
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | protected void onResume() {
66 | super.onResume();
67 |
68 | }
69 |
70 | @Override
71 | public void onWindowFocusChanged(boolean hasFocus) {
72 | super.onWindowFocusChanged(hasFocus);
73 | }
74 |
75 | @Override
76 | protected void onPostResume() {
77 | super.onPostResume();
78 | new Handler().postDelayed((Runnable) () -> mResultProfileBinding.contentMain1.appStart.setText("冷启动耗时" + sLaunchCost), 300);
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.SystemClock;
6 |
7 | import com.snail.labaffinity.R;
8 | import com.snail.labaffinity.utils.LogUtils;
9 |
10 |
11 | public class SplashActivity extends BaseActivity {
12 |
13 | private int count;
14 |
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 | startActivity(new Intent(SplashActivity.this, MainActivity.class));
21 | LogUtils.v("onCreate " + SystemClock.uptimeMillis());
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/activity/TrafficTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.activity;
2 |
3 | import android.net.http.SslError;
4 | import android.os.Bundle;
5 | import android.webkit.SslErrorHandler;
6 | import android.webkit.WebChromeClient;
7 | import android.webkit.WebResourceRequest;
8 | import android.webkit.WebSettings;
9 | import android.webkit.WebView;
10 | import android.webkit.WebViewClient;
11 |
12 | import androidx.annotation.Nullable;
13 |
14 | public class TrafficTestActivity extends BaseActivity {
15 |
16 |
17 | @Override
18 | protected void onCreate(@Nullable Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | WebView webView = new WebView(this);
21 |
22 | WebSettings webSettings = webView.getSettings();
23 | webSettings.setJavaScriptEnabled(true);
24 | webSettings.setDomStorageEnabled(true);
25 | webView.setWebViewClient(new WebViewClient() {
26 | @Override
27 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
28 | return false;
29 | }
30 |
31 | @Override
32 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
33 | super.onReceivedSslError(view, handler, error);
34 | handler.proceed();
35 | }
36 | });
37 | webView.setWebChromeClient(new WebChromeClient(){
38 |
39 | @Override
40 | public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
41 | super.onReceivedTouchIconUrl(view, url, precomposed);
42 | }
43 | });
44 |
45 | setContentView(webView);
46 | // webView.loadUrl("https://www.mi.com/");
47 | webView.loadUrl("https://you.163.com/");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/adapter/MyFragment.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.adapter;
2 |
3 | import android.os.Bundle;
4 | import androidx.annotation.Nullable;
5 | import androidx.fragment.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.TextView;
10 |
11 | import com.snail.labaffinity.R;
12 |
13 | /**
14 | * Author: lishang
15 | * Data: 17/1/5 下午2:10
16 | * Des:
17 | * version:
18 | */
19 |
20 | public class MyFragment extends Fragment {
21 |
22 |
23 | public static Fragment newInstance(String msg) {
24 | Fragment fragment = new MyFragment();
25 | Bundle bundle = new Bundle();
26 | bundle.putString("msg", msg);
27 | fragment.setArguments(bundle);
28 | return fragment;
29 | }
30 |
31 | @Nullable
32 | @Override
33 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
34 | View root = inflater.inflate(R.layout.activity_main, container, false);
35 | TextView textView = (TextView) root.findViewById(R.id.first);
36 | textView.setText("Pos " + getArguments().getString("msg"));
37 | return root;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/adapter/MyFragmentPagerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.adapter;
2 |
3 | import androidx.fragment.app.Fragment;
4 | import androidx.fragment.app.FragmentManager;
5 | import androidx.fragment.app.FragmentPagerAdapter;
6 |
7 | /**
8 | * Author: lishang
9 | * Data: 17/1/5 下午2:10
10 | * Des:
11 | * version:
12 | */
13 |
14 | public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
15 |
16 | public MyFragmentPagerAdapter(FragmentManager fm) {
17 | super(fm);
18 | }
19 |
20 | @Override
21 | public Fragment getItem(int position) {
22 | return MyFragment.newInstance(String.valueOf(position));
23 | }
24 |
25 | @Override
26 | public int getCount() {
27 | return 10;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/app/LabApplication.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.app;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 | import android.os.Debug;
7 | import android.os.SystemClock;
8 | import android.util.Log;
9 |
10 | import com.snail.collie.Collie;
11 | import com.snail.collie.CollieListener;
12 | import com.snail.collie.Config;
13 | import com.snail.collie.battery.BatteryInfo;
14 | import com.snail.collie.mem.TrackMemoryInfo;
15 |
16 | import java.io.File;
17 |
18 | import cn.campusapp.router.Router;
19 |
20 | /**
21 | * Author: hzlishang
22 | * Data: 16/10/11 下午12:44
23 | * Des:
24 | * version:
25 | */
26 | public class LabApplication extends Application {
27 |
28 | public static long sLaunchCost;
29 | @Override
30 | public void onCreate() {
31 | super.onCreate();
32 | sApplication = this;
33 | Router.initBrowserRouter(this);
34 | Router.initActivityRouter(getApplicationContext());
35 | Collie.getInstance().init(this, new Config(false, true, true, true, true, true), new CollieListener() {
36 |
37 | @Override
38 | public void onActivityFocusableCost(Activity activity, long duration, boolean finishNow) {
39 | Log.v("Collie", "Activity 获取焦点" + activity + " "+ duration );
40 |
41 | }
42 |
43 | @Override
44 | public void onTrafficStats(Activity activity, long value) {
45 | Log.v("Collie", "" + activity.getClass().getSimpleName() + " 流量消耗 " + value * 1.0f / (1024 * 1024) + "M");
46 |
47 | }
48 |
49 | @Override
50 | public void onBatteryCost(BatteryInfo batteryInfo) {
51 | Log.v("Collie", " 电量流量消耗 " + batteryInfo.cost);
52 |
53 | }
54 |
55 | @Override
56 | public void onAppColdLaunchCost(long duration, String processName) {
57 | Log.v("Collie", "启动耗时 " + duration + " processName " + processName);
58 | sLaunchCost = duration;
59 | }
60 |
61 | @Override
62 | public void onActivityLaunchCost(Activity activity, long duration, boolean finishNow) {
63 | Log.v("Collie", "activity启动耗时 " + activity + " " + duration + " finishNow " + finishNow);
64 | }
65 |
66 | @Override
67 | public void onLeakActivity(String activity, int count) {
68 | Log.v("Collie", "内存泄露 " + activity + " 数量 " + count);
69 | }
70 |
71 | @Override
72 | public void onCurrentMemoryCost(TrackMemoryInfo trackMemoryInfo) {
73 | Log.v("Collie", "内存 " + trackMemoryInfo.procName + " java内存 "
74 | + trackMemoryInfo.appMemory.dalvikPss + " native内存 " +
75 | trackMemoryInfo.appMemory.nativePss);
76 | }
77 |
78 | @Override
79 | public void onFpsTrack(Activity activity, long currentCostMils, long currentDropFrame, boolean isInFrameDraw, long averageFps) {
80 | if (currentDropFrame >= 2)
81 | Log.v("Collie", "Activity " + activity + " 掉帧 " + currentDropFrame + " 是否因为Choro 绘制掉帧 " + isInFrameDraw + " 1s 平均帧率" + averageFps);
82 | }
83 |
84 | @Override
85 | public void onANRAppear(Activity activity) {
86 | Log.v("Collie", "Activity " + activity + " ANR ");
87 |
88 | }
89 |
90 | });
91 | }
92 |
93 | private static Application sApplication;
94 |
95 | public static Application getContext() {
96 | return sApplication;
97 | }
98 |
99 | public static void startTrace(Context context) {
100 | File file = new File(context.getExternalFilesDir("android"), SystemClock.uptimeMillis()+"methods.trace");
101 | Debug.startMethodTracing(file.getAbsolutePath(), 300 * 1024 * 1024);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/app/MyButton.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.app;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.os.SystemClock;
6 | import android.util.AttributeSet;
7 |
8 | import com.snail.labaffinity.utils.LogUtils;
9 |
10 | public class MyButton extends androidx.appcompat.widget.AppCompatTextView {
11 | public MyButton(Context context) {
12 | super(context);
13 | }
14 |
15 | public MyButton(Context context, AttributeSet attrs) {
16 | super(context, attrs);
17 | }
18 |
19 | public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
20 | super(context, attrs, defStyleAttr);
21 | }
22 |
23 | @Override
24 | protected void onDraw(Canvas canvas) {
25 | super.onDraw(canvas);
26 | SystemClock.sleep(300);
27 | LogUtils.v("onDraw");
28 | }
29 |
30 | @Override
31 | protected void onAttachedToWindow() {
32 | super.onAttachedToWindow();
33 | LogUtils.v("onAttachedToWindow");
34 | }
35 |
36 | @Override
37 | public void onWindowFocusChanged(boolean hasWindowFocus) {
38 | super.onWindowFocusChanged(hasWindowFocus);
39 | SystemClock.sleep(300);
40 | LogUtils.v("onWindowFocusChanged 慢");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/service/BackGroundService.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.service;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import androidx.annotation.Nullable;
7 |
8 | import com.snail.labaffinity.utils.LogUtils;
9 |
10 | /**
11 | * Author: hzlishang
12 | * Data: 16/7/5 下午2:17
13 | * Des:
14 | * version:
15 | */
16 | public class BackGroundService extends Service {
17 | @Nullable
18 | @Override
19 | public IBinder onBind(Intent intent) {
20 | return null;
21 | }
22 |
23 | @Override
24 | public int onStartCommand(Intent intent, int flags, int startId) {
25 | LogUtils.v("onStartCommand");
26 | return START_STICKY;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/utils/AppProfile.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.utils;
2 |
3 | import android.app.Application;
4 |
5 | import com.snail.labaffinity.app.LabApplication;
6 |
7 | /**
8 | * Author: hzlishang
9 | * Data: 16/10/11 下午12:45
10 | * Des:
11 | * version:
12 | */
13 | public class AppProfile {
14 |
15 | public static Application getAppContext() {
16 | return LabApplication.getContext();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/utils/LogUtils.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.utils;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Author: hzlishang
7 | * Data: 16/10/11 下午12:48
8 | * Des:
9 | * version:
10 | */
11 | public class LogUtils {
12 | public static void v(String msg) {
13 | Log.v("Collie", msg);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/snail/labaffinity/utils/ToastUtil.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity.utils;
2 |
3 | import android.widget.Toast;
4 |
5 | /**
6 | * Author: hzlishang
7 | * Data: 16/10/11 下午12:47
8 | * Des:
9 | * version:
10 | */
11 | public class ToastUtil {
12 |
13 | public static void show(String msg) {
14 | Toast.makeText(AppProfile.getAppContext(), msg, Toast.LENGTH_SHORT).show();
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_no_anim.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_slide_left_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_slide_left_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_slide_right_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_slide_right_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_float_button_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_float_button_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_nug_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_nug_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_nug_tran_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_set_bounce.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
16 |
17 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/anim_webview_load_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popwindow_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popwindow_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popwindow_push_bottom_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popwindow_push_bottom_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popwindow_stretch_show.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_left_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_left_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_right_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_right_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
27 |
28 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_second.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
19 |
20 |
24 |
25 |
31 |
37 |
42 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happylishang/Collie/bfdc6782d568bfcefef01e846e81ccfd5a7e3470/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #0000
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LabAffinity
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/test/java/com/snail/labaffinity/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.snail.labaffinity;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.6.0'
5 | repositories {
6 | jcenter()
7 | google()
8 | maven {
9 | url 'https://maven.fabric.io/public'
10 | }
11 |
12 | }
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:7.2.1'
15 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
16 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // Add this line
17 | classpath 'com.google.gms:google-services:4.3.10'
18 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2'
19 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
20 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.10.2"
21 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
22 | }
23 | }
24 |
25 | allprojects {
26 | repositories {
27 | jcenter()
28 | maven { url "https://jitpack.io" }
29 | google()
30 | maven {url 'https://maven.aliyun.com/repository/public'}
31 | }
32 | }
33 |
34 | task clean(type: Delete) {
35 | delete rootProject.buildDir
36 | }
37 |
--------------------------------------------------------------------------------
/collie/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/collie/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | apply from: "../publish.gradle"
5 | android {
6 | compileSdkVersion 30
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 | consumerProguardFiles "consumer-rules.pro"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: "libs", include: ["*.jar"])
24 | implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
25 | implementation 'androidx.appcompat:appcompat:1.1.0'
26 | implementation "androidx.core:core-ktx:1.8.0"
27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
28 | }
29 | repositories {
30 | mavenCentral()
31 | }
32 |
--------------------------------------------------------------------------------
/collie/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happylishang/Collie/bfdc6782d568bfcefef01e846e81ccfd5a7e3470/collie/consumer-rules.pro
--------------------------------------------------------------------------------
/collie/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
--------------------------------------------------------------------------------
/collie/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/Collie.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import com.snail.collie.core.ActivityStack.push
8 | import com.snail.collie.core.ActivityStack.markStart
9 | import com.snail.collie.core.ActivityStack.markStop
10 | import com.snail.collie.core.ActivityStack.pop
11 | import com.snail.collie.trafficstats.TrafficStatsTracker.trafficStatsListener
12 | import com.snail.collie.startup.LauncherTracker.iLaunchTrackListener
13 | import com.snail.collie.fps.ITrackFpsListener
14 | import com.snail.collie.mem.MemoryLeakTrack.ITrackMemoryListener
15 | import com.snail.collie.battery.BatteryStatsTracker.IBatteryListener
16 | import com.snail.collie.trafficstats.TrafficStatsTracker
17 | import com.snail.collie.trafficstats.ITrackTrafficStatsListener
18 | import com.snail.collie.mem.MemoryLeakTrack
19 | import com.snail.collie.fps.FpsTracker
20 | import com.snail.collie.debug.DebugHelper
21 | import com.snail.collie.battery.BatteryStatsTracker
22 | import com.snail.collie.startup.LauncherTracker
23 | import com.snail.collie.startup.LauncherTracker.ILaunchTrackListener
24 | import com.snail.collie.core.CollieHandlerThread
25 | import kotlin.jvm.Volatile
26 | import com.snail.collie.battery.BatteryInfo
27 | import com.snail.collie.mem.TrackMemoryInfo
28 | import java.util.ArrayList
29 | import java.util.HashSet
30 |
31 | class Collie private constructor() {
32 | private val mHandler: Handler = Handler(CollieHandlerThread.looper)
33 | private val mITrackListener: ITrackFpsListener
34 | private val mITrackMemoryLeakListener: ITrackMemoryListener
35 | private val mIBatteryListener: IBatteryListener
36 | private val mCollieListeners: MutableList = ArrayList()
37 | private val mActivityLifecycleCallbacks = HashSet()
38 | fun addActivityLifecycleCallbacks(callbacks: Application.ActivityLifecycleCallbacks) {
39 | mActivityLifecycleCallbacks.add(callbacks)
40 | }
41 |
42 | fun removeActivityLifecycleCallbacks(callbacks: Application.ActivityLifecycleCallbacks) {
43 | mActivityLifecycleCallbacks.remove(callbacks)
44 | }
45 |
46 | private val mActivityLifecycleCallback: Application.ActivityLifecycleCallbacks =
47 | object : Application.ActivityLifecycleCallbacks {
48 | override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
49 | push(activity)
50 | for (item in mActivityLifecycleCallbacks) {
51 | item.onActivityCreated(activity, bundle)
52 | }
53 | }
54 |
55 | override fun onActivityStarted(activity: Activity) {
56 | markStart()
57 | for (item in mActivityLifecycleCallbacks) {
58 | item.onActivityStarted(activity)
59 | }
60 | }
61 |
62 | override fun onActivityResumed(activity: Activity) {
63 | for (item in mActivityLifecycleCallbacks) {
64 | item.onActivityResumed(activity)
65 | }
66 | }
67 |
68 | override fun onActivityPaused(activity: Activity) {
69 | for (item in mActivityLifecycleCallbacks) {
70 | item.onActivityPaused(activity)
71 | }
72 | }
73 |
74 | override fun onActivityStopped(activity: Activity) {
75 | markStop()
76 | for (item in mActivityLifecycleCallbacks) {
77 | item.onActivityStopped(activity)
78 | }
79 | }
80 |
81 | override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {
82 | for (item in mActivityLifecycleCallbacks) {
83 | item.onActivitySaveInstanceState(activity, bundle)
84 | }
85 | }
86 |
87 | override fun onActivityDestroyed(activity: Activity) {
88 | for (item in mActivityLifecycleCallbacks) {
89 | item.onActivityDestroyed(activity)
90 | }
91 | pop(activity)
92 | }
93 | }
94 |
95 | fun init(
96 | application: Application,
97 | config: Config,
98 | listener: CollieListener
99 | ) {
100 | application.registerActivityLifecycleCallbacks(mActivityLifecycleCallback)
101 | mCollieListeners.add(listener)
102 | if (config.useTrafficTrack) {
103 | trafficStatsListener = object : ITrackTrafficStatsListener {
104 | override fun onTrafficStats(activity: Activity, value: Long) {
105 | for (collieListener in mCollieListeners) {
106 | collieListener.onTrafficStats(activity, value)
107 | }
108 | }
109 | }
110 | TrafficStatsTracker.startTrack(application)
111 | }
112 | if (config.useMemTrack) {
113 | MemoryLeakTrack.instance!!.startTrack(application)
114 | MemoryLeakTrack.instance!!.addOnMemoryLeakListener(mITrackMemoryLeakListener)
115 | }
116 | if (config.useFpsTrack) {
117 | FpsTracker.getInstance().setTrackerListener(mITrackListener)
118 | FpsTracker.getInstance().startTrack(application)
119 | }
120 | if (config.showDebugView) {
121 | DebugHelper.instance!!.startTrack(application)
122 | }
123 | if (config.useBatteryTrack) {
124 | BatteryStatsTracker.instance!!.addBatteryListener(mIBatteryListener)
125 | BatteryStatsTracker.instance!!.startTrack(application)
126 | }
127 | if (config.useStartUpTrack) {
128 | iLaunchTrackListener = object : ILaunchTrackListener {
129 | override fun onActivityFocusableCost(
130 | activity: Activity?,
131 | duration: Long,
132 | finishNow: Boolean
133 | ) {
134 | for (collieListener in mCollieListeners) {
135 | collieListener.onActivityFocusableCost(activity, duration, finishNow)
136 | }
137 | }
138 |
139 | override fun onAppColdLaunchCost(duration: Long, processName: String?) {
140 | for (collieListener in mCollieListeners) {
141 | collieListener.onAppColdLaunchCost(duration, processName)
142 | }
143 | }
144 |
145 | override fun onActivityLaunchCost(
146 | activity: Activity?,
147 | duration: Long,
148 | finishNow: Boolean
149 | ) {
150 | for (collieListener in mCollieListeners) {
151 | collieListener.onActivityLaunchCost(activity, duration, finishNow)
152 | }
153 | }
154 | }
155 | LauncherTracker.startTrack(application)
156 | }
157 | }
158 |
159 | fun stop(application: Application) {
160 | application.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallback)
161 | CollieHandlerThread.quitSafely()
162 | }
163 |
164 | companion object {
165 | @Volatile
166 | private var sInstance: Collie? = null
167 |
168 | @JvmStatic
169 | val instance: Collie
170 | get() {
171 | if (sInstance == null) {
172 | synchronized(Collie::class.java) {
173 | if (sInstance == null) {
174 | sInstance = Collie()
175 | }
176 | }
177 | }
178 | return sInstance!!
179 | }
180 | }
181 |
182 | init {
183 | mITrackListener = object : ITrackFpsListener {
184 | override fun onFpsTrack(
185 | activity: Activity,
186 | currentCostMils: Long,
187 | currentDropFrame: Long,
188 | isInFrameDraw: Boolean,
189 | averageFps: Long
190 | ) {
191 | val currentFps =
192 | if (currentCostMils == 0L) 60 else Math.min(60, 1000 / currentCostMils)
193 | mHandler.post {
194 | if (currentDropFrame > 1) DebugHelper.instance!!.update(
195 | """实时fps $currentFps
196 | 丢帧 $currentDropFrame
197 | 1s平均fps $averageFps
198 | 本次耗时 $currentCostMils"""
199 | )
200 | for (collieListener in mCollieListeners) {
201 | collieListener.onFpsTrack(
202 | activity,
203 | currentCostMils,
204 | currentDropFrame,
205 | isInFrameDraw,
206 | averageFps
207 | )
208 | }
209 | }
210 | }
211 |
212 | override fun onANRAppear(activity: Activity?) {
213 | for (collieListener in mCollieListeners) {
214 | collieListener.onANRAppear(activity)
215 | }
216 | }
217 | }
218 | mITrackMemoryLeakListener = object : ITrackMemoryListener {
219 | override fun onLeakActivity(activity: String?, count: Int) {
220 | // Log.v("Collie", "内存泄露 " + activity + " 数量 " + count);
221 | for (collieListener in mCollieListeners) {
222 | collieListener.onLeakActivity(activity, count)
223 | }
224 | }
225 |
226 | override fun onCurrentMemoryCost(trackMemoryInfo: TrackMemoryInfo?) {
227 | // Log.v("Collie", "内存 " + trackMemoryInfo.procName + " java内存 "
228 | // + trackMemoryInfo.appMemory.dalvikPss + " native内存 " +
229 | // trackMemoryInfo.appMemory.nativePss);
230 | for (collieListener in mCollieListeners) {
231 | collieListener.onCurrentMemoryCost(trackMemoryInfo)
232 | }
233 | }
234 | }
235 | mIBatteryListener = object : IBatteryListener {
236 | override fun onBatteryCost(batteryInfo: BatteryInfo?) {
237 | for (collieListener in mCollieListeners) {
238 | collieListener.onBatteryCost(batteryInfo)
239 | }
240 | }
241 | }
242 | }
243 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/CollieListener.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie
2 |
3 | import com.snail.collie.battery.BatteryStatsTracker.IBatteryListener
4 | import com.snail.collie.mem.MemoryLeakTrack.ITrackMemoryListener
5 | import com.snail.collie.fps.ITrackFpsListener
6 | import com.snail.collie.startup.LauncherTracker
7 | import com.snail.collie.trafficstats.ITrackTrafficStatsListener
8 |
9 | interface CollieListener : LauncherTracker.ILaunchTrackListener, ITrackFpsListener, ITrackMemoryListener, ITrackTrafficStatsListener, IBatteryListener
10 |
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/Config.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie
2 |
3 | class Config(val showDebugView: Boolean, //是否展示悬浮view
4 | val useFpsTrack: Boolean,//是否打开fps
5 | val useTrafficTrack: Boolean,//流量监控
6 | val useMemTrack: Boolean,//Activity泄露及内存情况
7 | val useBatteryTrack: Boolean, //电量
8 | val useStartUpTrack: Boolean)//启动
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/battery/BatteryInfo.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.battery
2 |
3 | /**
4 | * Author: snail
5 | * Data: 2021/12/22.
6 | * Des:
7 | * version:
8 | */
9 | class BatteryInfo {
10 |
11 | @JvmField
12 | var charging: Boolean = false
13 |
14 | @JvmField
15 | var activityName: String? = null
16 |
17 | @JvmField
18 | var cost: Float = 0f
19 |
20 | @JvmField
21 | var duration: Long = 0
22 |
23 | @JvmField
24 | var display: String? = null
25 |
26 | @JvmField
27 | var total: Int = 0
28 |
29 | @JvmField
30 | var voltage: Int = 0
31 |
32 | @JvmField
33 | var screenBrightness: Float = 0f
34 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/battery/BatteryLevelReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.battery
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.BatteryManager
7 |
8 | class BatteryLevelReceiver : BroadcastReceiver() {
9 |
10 | var currentBatteryLevel = 0
11 | private set
12 | var totalBatteryPercent = 0
13 | private set
14 | var isCharging = false
15 | private set
16 | var voltage = 0
17 | private set
18 |
19 | override fun onReceive(context: Context, intent: Intent) {
20 | //当前剩余电量
21 | currentBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
22 | //电量最大值
23 | totalBatteryPercent = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
24 | voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
25 | val status = intent.getIntExtra("status", 0)
26 | isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
27 | status == BatteryManager.BATTERY_STATUS_FULL
28 | }
29 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/battery/BatteryStatsTracker.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.battery
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.os.BatteryManager
8 | import android.os.Handler
9 | import android.os.SystemClock
10 | import android.provider.Settings
11 | import android.text.TextUtils
12 | import android.util.Log
13 | import com.snail.collie.core.ITracker
14 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks
15 | import com.snail.collie.debug.DebugHelper
16 | import com.snail.collie.core.ActivityStack.getTopActivity
17 | import com.snail.collie.core.ActivityStack.isInBackGround
18 | import com.snail.collie.core.CollieHandlerThread
19 | import java.util.*
20 |
21 | class BatteryStatsTracker private constructor() : ITracker {
22 |
23 | private var mHandler: Handler = Handler(CollieHandlerThread.looper)
24 | private var display: String? = null
25 | private var mStartPercent = 0
26 | private val mSimpleActivityLifecycleCallbacks: SimpleActivityLifecycleCallbacks =
27 | object : SimpleActivityLifecycleCallbacks() {
28 | override fun onActivityStarted(activity: Activity) {
29 | super.onActivityStarted(activity)
30 | val application = activity.application
31 | if (mStartPercent == 0 && getTopActivity() === activity) {
32 | mHandler.post {
33 | val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
34 | val batteryStatus = application.registerReceiver(null, filter)
35 | mStartPercent = batteryStatus!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
36 | }
37 | }
38 | }
39 |
40 | override fun onActivityStopped(activity: Activity) {
41 | super.onActivityStopped(activity)
42 | val application = activity.application
43 | if (isInBackGround()) {
44 | mHandler.post {
45 | if (mListeners.size > 0) {
46 | val batteryInfo = computeBatteryInfo(application)
47 | for (listener in mListeners) {
48 | listener.onBatteryCost(batteryInfo)
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 | override fun destroy(application: Application) {
57 | application.unregisterActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks)
58 | }
59 |
60 | override fun startTrack(application: Application) {
61 | application.registerActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks)
62 | val intentFilter = IntentFilter()
63 | intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
64 | }
65 |
66 | override fun pauseTrack(application: Application) {}
67 |
68 | // 似乎并没有必要按照Activity统计耗点,每个界面很难超过1%
69 | private fun computeBatteryInfo(application: Application): BatteryInfo {
70 | if (TextUtils.isEmpty(display)) {
71 | display =
72 | "" + application.resources.displayMetrics.widthPixels + "*" + application.resources.displayMetrics.heightPixels
73 | }
74 | val batteryInfo = BatteryInfo()
75 | try {
76 | val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
77 | val batteryStatus = application.registerReceiver(null, filter)
78 | val status = batteryStatus!!.getIntExtra("status", 0)
79 | val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
80 | status == BatteryManager.BATTERY_STATUS_FULL
81 | val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
82 | batteryInfo.charging = isCharging
83 | batteryInfo.cost = if (isCharging) 0f else mStartPercent - batteryStatus.getIntExtra(
84 | BatteryManager.EXTRA_LEVEL, -1
85 | ).toFloat()
86 | batteryInfo.duration += (SystemClock.uptimeMillis() - sStartUpTimeStamp) / 1000
87 | batteryInfo.screenBrightness = getSystemScreenBrightnessValue(application).toFloat()
88 | batteryInfo.display = display
89 | batteryInfo.total = scale
90 | Log.v(
91 | "Battery",
92 | "total " + batteryInfo.total + " 用时间 " + batteryInfo.duration / 1000 + " 耗电 " + batteryInfo.cost
93 | )
94 | } catch (e: Exception) {
95 | }
96 | return batteryInfo
97 | }
98 |
99 | fun getSystemScreenBrightnessValue(application: Application): Int {
100 | val contentResolver = application.contentResolver
101 | val defVal = 125
102 | return Settings.System.getInt(
103 | contentResolver,
104 | Settings.System.SCREEN_BRIGHTNESS, defVal
105 | )
106 | }
107 |
108 | private val mListeners: MutableList = ArrayList()
109 | fun addBatteryListener(listener: IBatteryListener) {
110 | mListeners.add(listener)
111 | }
112 |
113 | fun removeBatteryListener(listener: IBatteryListener) {
114 | mListeners.remove(listener)
115 | }
116 |
117 | interface IBatteryListener {
118 | fun onBatteryCost(batteryInfo: BatteryInfo?)
119 | }
120 |
121 | companion object {
122 | private var sInstance: BatteryStatsTracker? = null
123 | private val sStartUpTimeStamp = SystemClock.uptimeMillis()
124 |
125 | @JvmStatic
126 | val instance: BatteryStatsTracker?
127 | get() {
128 | if (sInstance == null) {
129 | synchronized(DebugHelper::class.java) {
130 | if (sInstance == null) {
131 | sInstance = BatteryStatsTracker()
132 | }
133 | }
134 | }
135 | return sInstance
136 | }
137 | }
138 |
139 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ActivityStack.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.app.Activity
4 |
5 | object ActivityStack {
6 |
7 | private val mActivities: MutableList = mutableListOf()
8 |
9 | @Volatile
10 | private var mCurrentSate = 0
11 |
12 | fun push(activity: Activity) {
13 | mActivities.add(0, activity)
14 | }
15 |
16 | fun getSize(): Int {
17 | return mActivities.size
18 | }
19 |
20 | fun pop(activity: Activity) {
21 | mActivities.remove(activity)
22 | }
23 |
24 | fun markStart() {
25 | mCurrentSate++
26 | }
27 |
28 | fun markStop() {
29 | mCurrentSate--
30 | }
31 |
32 | fun getTopActivity(): Activity? {
33 | return mActivities.first()
34 | }
35 |
36 | fun getBottomActivity(): Activity? {
37 | return mActivities.last()
38 | }
39 |
40 | fun isInBackGround(): Boolean {
41 | return mCurrentSate == 0
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/CollieHandlerThread.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.os.HandlerThread
4 |
5 | object CollieHandlerThread : HandlerThread("collie_thread") {
6 |
7 | init {
8 | start()
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ITracker.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.app.Application
4 |
5 | interface ITracker {
6 |
7 | fun destroy(application: Application)
8 |
9 | fun startTrack(application: Application)
10 |
11 | fun pauseTrack(application: Application)
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/LooperMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.os.Build
4 | import android.os.Looper
5 | import android.os.MessageQueue
6 | import android.os.SystemClock
7 | import android.util.Printer
8 | import androidx.annotation.CallSuper
9 | import java.lang.Exception
10 | import java.util.*
11 |
12 | object LooperMonitor : MessageQueue.IdleHandler {
13 |
14 | private var looper: Looper
15 | private var printer: LooperPrinter? = null
16 |
17 | init {
18 | Objects.requireNonNull(Looper.getMainLooper())
19 | looper = Looper.getMainLooper()
20 | resetPrinter()
21 | addIdleHandler(looper)
22 | }
23 |
24 | private fun addIdleHandler(looper: Looper) {
25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
26 | looper.queue.addIdleHandler(this)
27 | } else {
28 | try {
29 | val queue = ReflectUtils.get(looper?.javaClass, "mQueue", looper)
30 | queue?.addIdleHandler(this)
31 | } catch (e: Exception) {
32 | }
33 | }
34 | }
35 |
36 |
37 | private fun removeIdleHandler(looper: Looper) {
38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
39 | looper.queue.removeIdleHandler(this)
40 | } else {
41 | try {
42 | val queue = ReflectUtils.get(looper.javaClass, "mQueue", looper)
43 | queue?.removeIdleHandler(this)
44 | } catch (e: Exception) {
45 | }
46 | }
47 | }
48 |
49 | private const val CHECK_TIME = 60 * 1000L
50 | private var lastCheckPrinterTime: Long = 0
51 |
52 | override fun queueIdle(): Boolean {
53 | // 定期重置
54 | if (SystemClock.uptimeMillis() - lastCheckPrinterTime >= LooperMonitor.CHECK_TIME) {
55 | resetPrinter()
56 | lastCheckPrinterTime = SystemClock.uptimeMillis()
57 | }
58 | return true
59 | }
60 |
61 | private var isReflectLoggingError = false
62 |
63 | @Synchronized
64 | private fun resetPrinter() {
65 | var originPrinter: Printer? = null
66 | try {
67 | if (!isReflectLoggingError) {
68 | originPrinter = ReflectUtils.get(looper.javaClass, "mLogging", looper)
69 | if (originPrinter === printer && null != printer) {
70 | return
71 | }
72 | }
73 | } catch (e: Exception) {
74 | isReflectLoggingError = true
75 | }
76 | looper.setMessageLogging(LooperPrinter(originPrinter).also { printer = it })
77 | }
78 |
79 | class LooperPrinter(var originPrinter: Printer?) : Printer {
80 |
81 | override fun println(x: String?) {
82 |
83 | originPrinter?.let {
84 | if (it == this@LooperPrinter) {
85 | return
86 | }
87 | it.println(x)
88 | }
89 | x?.let {
90 | if (x[0] == '>' || x[0] == '<') {
91 | dispatch(x[0] == '>', x)
92 | }
93 | }
94 | }
95 | }
96 |
97 | abstract class LooperDispatchListener {
98 | var isHasDispatchStart = false
99 |
100 | open fun dispatchStart() {}
101 | open fun dispatchEnd() {}
102 |
103 | @CallSuper
104 | fun onDispatchStart(x: String?) {
105 | isHasDispatchStart = true
106 | dispatchStart()
107 | }
108 |
109 | @CallSuper
110 | fun onDispatchEnd(x: String?) {
111 | isHasDispatchStart = false
112 | dispatchEnd()
113 | }
114 | }
115 |
116 | private val listeners = mutableSetOf()
117 |
118 | @Synchronized
119 | private fun dispatch(isBegin: Boolean, log: String) {
120 | for (listener in listeners) {
121 | if (isBegin) {
122 | if (!listener.isHasDispatchStart) {
123 | listener.onDispatchStart(log)
124 | }
125 | } else {
126 | if (listener.isHasDispatchStart) {
127 | listener.onDispatchEnd(log)
128 | }
129 | }
130 | }
131 | }
132 |
133 | @Synchronized
134 | fun release() {
135 | if (printer != null) {
136 | listeners.clear()
137 | looper.setMessageLogging(printer?.originPrinter)
138 | removeIdleHandler(looper)
139 | printer = null
140 | }
141 | }
142 |
143 | @Synchronized
144 | fun registerListener(listener: LooperDispatchListener) {
145 | listeners.add(listener)
146 | }
147 |
148 | @Synchronized
149 | fun unregisterListener(listener: LooperDispatchListener?) {
150 | listeners.remove(listener)
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ProcessUtil.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.app.ActivityManager
4 | import android.content.Context
5 | import android.os.Process
6 | import android.text.TextUtils
7 |
8 | object ProcessUtil {
9 | var sProcName: String? = null
10 |
11 | @JvmStatic
12 | fun getProcessName(cxt: Context): String? {
13 | if (!TextUtils.isEmpty(sProcName)) {
14 | return sProcName
15 | }
16 | val pid = Process.myPid()
17 | val am: ActivityManager = cxt.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
18 | val runningApps: List = am.runningAppProcesses
19 | ?: return null
20 | for (procInfo in runningApps) {
21 | if (procInfo.pid == pid) {
22 | return procInfo.processName.also { sProcName = it }
23 | }
24 | }
25 | return null
26 | }
27 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ReflectFiled.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import kotlin.jvm.Synchronized
4 | import kotlin.Throws
5 | import java.lang.ClassCastException
6 | import java.lang.Exception
7 | import java.lang.IllegalArgumentException
8 | import java.lang.reflect.Field
9 |
10 | class ReflectFiled(clazz: Class<*>?, fieldName: String?) {
11 | private val mClazz: Class<*>
12 | private val mFieldName: String
13 | private var mInit = false
14 | private var mField: Field? = null
15 | @Synchronized
16 | private fun prepare() {
17 | if (mInit) {
18 | return
19 | }
20 | var clazz: Class<*>? = mClazz
21 | while (clazz != null) {
22 | try {
23 | val f = clazz.getDeclaredField(mFieldName)
24 | f.isAccessible = true
25 | mField = f
26 | break
27 | } catch (e: Exception) {
28 | }
29 | clazz = clazz.superclass
30 | }
31 | mInit = true
32 | }
33 |
34 | @Synchronized
35 | @Throws(
36 | NoSuchFieldException::class,
37 | IllegalAccessException::class,
38 | IllegalArgumentException::class
39 | )
40 | fun get(): Type? {
41 | return get(false)
42 | }
43 |
44 | @Synchronized
45 | @Throws(
46 | NoSuchFieldException::class,
47 | IllegalAccessException::class,
48 | IllegalArgumentException::class
49 | )
50 | operator fun get(ignoreFieldNoExist: Boolean): Type? {
51 | prepare()
52 | if (mField == null) {
53 | if (!ignoreFieldNoExist) {
54 | throw NoSuchFieldException()
55 | }
56 | return null
57 | }
58 | var fieldVal: Type? = null
59 | fieldVal = try {
60 | mField!![null] as Type
61 | } catch (e: ClassCastException) {
62 | throw IllegalArgumentException("unable to cast object")
63 | }
64 | return fieldVal
65 | }
66 |
67 | @Synchronized
68 | @Throws(
69 | NoSuchFieldException::class,
70 | IllegalAccessException::class,
71 | IllegalArgumentException::class
72 | )
73 | operator fun get(ignoreFieldNoExist: Boolean, instance: Any?): Type? {
74 | prepare()
75 | if (mField == null) {
76 | if (!ignoreFieldNoExist) {
77 | throw NoSuchFieldException()
78 | }
79 | return null
80 | }
81 | var fieldVal: Type? = null
82 | fieldVal = try {
83 | mField!![instance] as Type
84 | } catch (e: ClassCastException) {
85 | throw IllegalArgumentException("unable to cast object")
86 | }
87 | return fieldVal
88 | }
89 |
90 | @Synchronized
91 | @Throws(NoSuchFieldException::class, IllegalAccessException::class)
92 | operator fun get(instance: Any?): Type? {
93 | return get(false, instance)
94 | }
95 |
96 | @Synchronized
97 | fun getWithoutThrow(instance: Any?): Type? {
98 | var fieldVal: Type? = null
99 | try {
100 | fieldVal = get(true, instance)
101 | } catch (e: NoSuchFieldException) {
102 | } catch (e: IllegalAccessException) {
103 | } catch (e: IllegalArgumentException) {
104 | }
105 | return fieldVal
106 | }
107 |
108 | @get:Synchronized
109 | val withoutThrow: Type?
110 | get() {
111 | var fieldVal: Type? = null
112 | try {
113 | fieldVal = get(true)
114 | } catch (e: NoSuchFieldException) {
115 | } catch (e: IllegalAccessException) {
116 | } catch (e: IllegalArgumentException) {
117 | }
118 | return fieldVal
119 | }
120 |
121 | @Synchronized
122 | @Throws(
123 | NoSuchFieldException::class,
124 | IllegalAccessException::class,
125 | IllegalArgumentException::class
126 | )
127 | operator fun set(instance: Any?, `val`: Type): Boolean {
128 | return set(instance, `val`, false)
129 | }
130 |
131 | @Synchronized
132 | @Throws(
133 | NoSuchFieldException::class,
134 | IllegalAccessException::class,
135 | IllegalArgumentException::class
136 | )
137 | operator fun set(instance: Any?, `val`: Type, ignoreFieldNoExist: Boolean): Boolean {
138 | prepare()
139 | if (mField == null) {
140 | if (!ignoreFieldNoExist) {
141 | throw NoSuchFieldException("Method $mFieldName is not exists.")
142 | }
143 | return false
144 | }
145 | mField!![instance] = `val`
146 | return true
147 | }
148 |
149 | @Synchronized
150 | fun setWithoutThrow(instance: Any?, `val`: Type): Boolean {
151 | var result = false
152 | try {
153 | result = set(instance, `val`, true)
154 | } catch (e: NoSuchFieldException) {
155 | } catch (e: IllegalAccessException) {
156 | } catch (e: IllegalArgumentException) {
157 | }
158 | return result
159 | }
160 |
161 | @Synchronized
162 | @Throws(NoSuchFieldException::class, IllegalAccessException::class)
163 | fun set(`val`: Type): Boolean {
164 | return set(null, `val`, false)
165 | }
166 |
167 | @Synchronized
168 | fun setWithoutThrow(`val`: Type): Boolean {
169 | var result = false
170 | try {
171 | result = set(null, `val`, true)
172 | } catch (e: NoSuchFieldException) {
173 | } catch (e: IllegalAccessException) {
174 | } catch (e: IllegalArgumentException) {
175 | }
176 | return result
177 | }
178 |
179 | companion object {
180 | private const val TAG = "ReflectFiled"
181 | }
182 |
183 | init {
184 | require(!(clazz == null || fieldName == null || fieldName.length == 0)) { "Both of invoker and fieldName can not be null or nil." }
185 | mClazz = clazz
186 | mFieldName = fieldName
187 | }
188 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ReflectMethod.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import java.lang.reflect.InvocationTargetException
4 | import java.lang.reflect.Method
5 |
6 | class ReflectMethod(clazz: Class<*>?, methodName: String?, vararg parameterTypes: Class<*>) {
7 | private val mClazz: Class<*>
8 | private val mMethodName: String
9 | private var mInit = false
10 | private var mMethod: Method? = null
11 | private val mParameterTypes: Array>
12 | @Synchronized
13 | private fun prepare() {
14 | if (mInit) {
15 | return
16 | }
17 | var clazz: Class<*>? = mClazz
18 | while (clazz != null) {
19 | try {
20 | val method = clazz.getDeclaredMethod(mMethodName, *mParameterTypes)
21 | method.isAccessible = true
22 | mMethod = method
23 | break
24 | } catch (e: Exception) {
25 | }
26 | clazz = clazz.superclass
27 | }
28 | mInit = true
29 | }
30 |
31 | @Synchronized
32 | @Throws(
33 | NoSuchFieldException::class,
34 | IllegalAccessException::class,
35 | IllegalArgumentException::class,
36 | InvocationTargetException::class
37 | )
38 | operator fun invoke(instance: Any?, vararg args: Any?): T? {
39 | return invoke(instance, false, *args)
40 | }
41 |
42 | @Synchronized
43 | @Throws(
44 | NoSuchFieldException::class,
45 | IllegalAccessException::class,
46 | IllegalArgumentException::class,
47 | InvocationTargetException::class
48 | )
49 | operator fun invoke(instance: Any?, ignoreFieldNoExist: Boolean, vararg args: Any?): T? {
50 | prepare()
51 | if (mMethod == null) {
52 | if (!ignoreFieldNoExist) {
53 | throw NoSuchFieldException("Method $mMethodName is not exists.")
54 | }
55 | return null
56 | }
57 | return mMethod!!.invoke(instance, *args) as T
58 | }
59 |
60 | companion object {
61 | private const val TAG = "ReflectFiled"
62 | }
63 |
64 | init {
65 | require(!(clazz == null || methodName == null || methodName.isEmpty())) { "Both of invoker and fieldName can not be null or nil." }
66 | mClazz = clazz
67 | mMethodName = methodName
68 | mParameterTypes = parameterTypes as Array>
69 | }
70 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/ReflectUtils.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | object ReflectUtils {
4 | @Throws(Exception::class)
5 | operator fun get(clazz: Class<*>?, fieldName: String?): T? {
6 | return ReflectFiled(clazz, fieldName).get()
7 | }
8 |
9 | @Throws(Exception::class)
10 | operator fun get(clazz: Class<*>?, fieldName: String?, instance: Any?): T? {
11 | return ReflectFiled(clazz, fieldName)[instance]
12 | }
13 |
14 | @Throws(Exception::class)
15 | operator fun set(clazz: Class<*>?, fieldName: String?, `object`: Any?): Boolean {
16 | return ReflectFiled(clazz, fieldName).set(`object`)
17 | }
18 |
19 | @Throws(Exception::class)
20 | operator fun set(clazz: Class<*>?, fieldName: String?, instance: Any?, value: Any?): Boolean {
21 | return ReflectFiled(clazz, fieldName).set(instance, value)
22 | }
23 |
24 | @Throws(Exception::class)
25 | operator fun invoke(
26 | clazz: Class<*>?,
27 | methodName: String?,
28 | instance: Any?,
29 | vararg args: Any?
30 | ): T? {
31 | return ReflectMethod(clazz, methodName).invoke(instance, *args)
32 | }
33 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/core/SimpleActivityLifecycleCallbacks.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.core
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 |
7 | open class SimpleActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
8 | override fun onActivityCreated(p0: Activity, p1: Bundle?) {
9 | }
10 |
11 | override fun onActivityStarted(p0: Activity) {
12 | }
13 |
14 | override fun onActivityResumed(p0: Activity) {
15 | }
16 |
17 | override fun onActivityPaused(p0: Activity) {
18 | }
19 |
20 | override fun onActivityStopped(p0: Activity) {
21 | }
22 |
23 | override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
24 | }
25 |
26 | override fun onActivityDestroyed(p0: Activity) {
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/cpu/CpuInfoTracker.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.cpu
2 |
3 | import android.app.Application
4 | import com.snail.collie.core.ITracker
5 |
6 | class CpuInfoTracker : ITracker {
7 | // TODO: 2020/8/26 7.0以后 不方便读取,而且这个指标的线上意义不大,留给线下解决
8 | override fun destroy(application: Application) {}
9 | override fun startTrack(application: Application) {}
10 | override fun pauseTrack(application: Application) {}
11 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/debug/DebugHelper.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.debug
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Handler
6 | import com.snail.collie.Collie
7 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks
8 | import com.snail.collie.core.ActivityStack.getTopActivity
9 | import com.snail.collie.core.ActivityStack.isInBackGround
10 | import com.snail.collie.core.CollieHandlerThread
11 | import com.snail.collie.core.ITracker
12 |
13 | /**
14 | * 掉帧检测
15 | */
16 | class DebugHelper private constructor() : ITracker {
17 | private var mDebugCollieView: FloatingFpsView? = null
18 | private val mHandler: Handler = Handler(CollieHandlerThread.looper)
19 | private var mFloatHelper: FloatHelper? = null
20 | private val mSimpleActivityLifecycleCallbacks: SimpleActivityLifecycleCallbacks =
21 | object : SimpleActivityLifecycleCallbacks() {
22 | override fun onActivityStopped(activity: Activity) {
23 | super.onActivityStopped(activity)
24 | if (isInBackGround()) {
25 | hide()
26 | }
27 | }
28 |
29 | override fun onActivityResumed(activity: Activity) {
30 | super.onActivityResumed(activity)
31 | show(activity.application)
32 | }
33 | }
34 |
35 | fun show(context: Application) {
36 | if (mFloatHelper != null && mFloatHelper!!.isShowing) {
37 | return
38 | }
39 | if (mDebugCollieView == null) {
40 | mDebugCollieView = FloatingFpsView(context)
41 | mFloatHelper = FloatHelper(context)
42 | mFloatHelper!!.setAlignSide(false)
43 | .setInitPosition(
44 | context.resources.displayMetrics.widthPixels - MeasureUtil.getMeasuredWidth(
45 | mDebugCollieView!!, 0
46 | ), 200
47 | )
48 | }
49 | mHandler.post {
50 | mFloatHelper!!.setView(mDebugCollieView!!)
51 | .show(getTopActivity())
52 | }
53 | }
54 |
55 | fun hide() {
56 | if (mFloatHelper != null) {
57 | mFloatHelper!!.destroy()
58 | mHandler.removeCallbacksAndMessages(null)
59 | }
60 | }
61 |
62 | fun update(content: String?) {
63 | if (mFloatHelper != null && mFloatHelper!!.isShowing) {
64 | mHandler.post { mDebugCollieView!!.update(content) }
65 | }
66 | }
67 |
68 | override fun destroy(application: Application) {}
69 | override fun startTrack(application: Application) {
70 | Collie.instance.addActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks)
71 | }
72 |
73 | override fun pauseTrack(application: Application) {}
74 |
75 | companion object {
76 | @Volatile
77 | private var sInstance: DebugHelper? = null
78 | @JvmStatic
79 | val instance: DebugHelper?
80 | get() {
81 | if (sInstance == null) {
82 | synchronized(DebugHelper::class.java) {
83 | if (sInstance == null) {
84 | sInstance = DebugHelper()
85 | }
86 | }
87 | }
88 | return sInstance
89 | }
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/debug/FloatHelper.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.debug
2 |
3 | import android.app.Activity
4 | import android.app.AlertDialog
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.graphics.PixelFormat
8 | import android.net.Uri
9 | import android.os.Build
10 | import android.provider.Settings
11 | import android.view.Gravity
12 | import android.view.View
13 | import android.view.ViewGroup
14 | import android.view.WindowManager
15 | import android.widget.FrameLayout
16 | import java.lang.IllegalArgumentException
17 |
18 | /**
19 | * 悬浮窗,需权限
20 | *
21 | * @author hejiangjie on 2020/5/19
22 | */
23 | class FloatHelper(private val mContext: Context) {
24 | private var mWindow: SimpleFloatWindow? = null
25 | private var mChild: View? = null
26 | private var mInitWidth = WindowManager.LayoutParams.WRAP_CONTENT
27 | private var mInitHeight = WindowManager.LayoutParams.WRAP_CONTENT
28 | private var mGravity = Gravity.NO_GRAVITY
29 | private var mInitX = 0
30 | private var mInitY = 0
31 | private var mNeedReload = false
32 | private var mAlignSide = false
33 | private val isMove = false
34 | private val mPermissionDialog: AlertDialog? = null
35 | fun setAlignSide(alignSide: Boolean): FloatHelper {
36 | mAlignSide = alignSide
37 | return this
38 | }
39 |
40 | fun setSize(width: Int, height: Int): FloatHelper {
41 | mInitWidth = width
42 | mInitHeight = height
43 | mNeedReload = true
44 | return this
45 | }
46 |
47 | fun setInitPosition(x: Int, y: Int): FloatHelper {
48 | mGravity = Gravity.START or Gravity.TOP
49 | mInitX = x
50 | mInitY = y
51 | mNeedReload = true
52 | return this
53 | }
54 |
55 | fun setView(view: View): FloatHelper {
56 | if (mChild !== view) {
57 | mChild = view
58 | mNeedReload = true
59 | }
60 | return this
61 | }
62 |
63 | /**
64 | * 显示悬浮窗(需先保证已有权限)
65 | *
66 | * @return
67 | */
68 | fun show(activity: Activity?): Boolean {
69 | if (!hasOverlayPermission(mContext)) {
70 | showTips(activity)
71 | return false
72 | }
73 | if (isShowing) {
74 | return true
75 | }
76 | checkSetupWindow()
77 | if (mWindow != null) {
78 | mWindow!!.open()
79 | }
80 | return true
81 | }
82 |
83 | private fun showTips(activity: Activity?) {
84 | AlertDialog.Builder(activity)
85 | .setMessage("您需要打开悬浮窗权限") //可以直接设置这三种button
86 | .setPositiveButton("确定") { dialog, which ->
87 | requestOverlayPermission(activity, PERMISSIONS_REQUEST_OVERLAY)
88 | dialog.dismiss()
89 | }
90 | .setNegativeButton("取消") { dialog, which -> dialog.dismiss() }
91 | .create().show()
92 | }
93 |
94 | /**
95 | * 关闭悬浮窗
96 | */
97 | fun close() {
98 | if (mWindow != null) {
99 | mWindow!!.close()
100 | mWindow = null
101 | }
102 | }
103 |
104 | /**
105 | * 是否存在悬浮窗(显示/最小化)
106 | *
107 | * @return
108 | */
109 | val isShowing: Boolean
110 | get() = mWindow != null && mWindow!!.isOpen
111 |
112 | /**
113 | * 资源回收清理
114 | */
115 | fun destroy() {
116 | close()
117 | mChild = null
118 | }
119 |
120 | private fun createFloatWindow(context: Context): SimpleFloatWindow {
121 | return SimpleFloatWindow(context)
122 | }
123 |
124 | private fun checkSetupWindow() {
125 | if (mWindow == null || mNeedReload) {
126 | if (mWindow != null) {
127 | mWindow!!.close()
128 | }
129 | mWindow = createFloatWindow(mContext)
130 | mWindow!!.loadView(mChild)
131 | }
132 | }
133 |
134 | private inner class SimpleFloatWindow(context: Context) : FrameLayout(context) {
135 | private var mWindowManager: WindowManager? = null
136 | private var mLayoutParams: WindowManager.LayoutParams? = null
137 | private val mMinimized = false
138 | private val mLastFloatX = 0
139 | private val mLastFloatY = 0
140 | private val downX = 0f
141 | private val downY = 0f
142 | private val moveX = 0f
143 | private val moveY = 0f
144 | var isOpen: Boolean
145 | private set
146 | private val origDownX = 0f
147 | private val origDownY = 0f
148 | private fun initFloatWindowParams(context: Context) {
149 | mWindowManager =
150 | context.applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
151 | mLayoutParams = WindowManager.LayoutParams()
152 | mLayoutParams!!.packageName = context.packageName
153 | mLayoutParams!!.width = mInitWidth
154 | mLayoutParams!!.height = mInitHeight
155 | mLayoutParams!!.flags = (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
156 | or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
157 | or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
158 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
159 | mLayoutParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
160 | } else {
161 | mLayoutParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
162 | }
163 | mLayoutParams!!.format = PixelFormat.RGBA_8888
164 | mLayoutParams!!.gravity = mGravity
165 | mLayoutParams!!.x = mInitX
166 | mLayoutParams!!.y = mInitY
167 | }
168 |
169 | fun loadView(view: View?) {
170 | removeAllViews()
171 | this.addView(
172 | view,
173 | ViewGroup.LayoutParams(
174 | ViewGroup.LayoutParams.MATCH_PARENT,
175 | ViewGroup.LayoutParams.MATCH_PARENT
176 | )
177 | )
178 | }
179 |
180 | fun open() {
181 | try {
182 | mWindowManager!!.updateViewLayout(this, mLayoutParams)
183 | } catch (e: IllegalArgumentException) {
184 | mWindowManager!!.addView(this, mLayoutParams)
185 | }
186 | isOpen = true
187 | }
188 |
189 | fun close() {
190 | if (mWindowManager != null && isShowing) {
191 | mWindowManager!!.removeView(this)
192 | removeAllViews()
193 | }
194 | isOpen = false
195 | }
196 |
197 | init {
198 | initFloatWindowParams(context)
199 | mNeedReload = false
200 | isOpen = false
201 | }
202 | }
203 |
204 | companion object {
205 | private const val TAG = "FloatHelper"
206 | const val PERMISSIONS_REQUEST_OVERLAY = 1231
207 |
208 | /**
209 | * 判断悬浮窗动态权限
210 | *
211 | * @param context
212 | * @return
213 | */
214 | fun hasOverlayPermission(context: Context?): Boolean {
215 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
216 | Settings.canDrawOverlays(context)
217 | } else true
218 | }
219 |
220 | /**
221 | * 请求悬浮窗权限
222 | *
223 | * @param activity
224 | * @param reqCode
225 | * @return
226 | */
227 | fun requestOverlayPermission(activity: Activity?, reqCode: Int): Boolean {
228 | if (activity == null || hasOverlayPermission(activity)) {
229 | return false
230 | }
231 | val intent = Intent(
232 | Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
233 | Uri.parse("package:" + activity.packageName)
234 | )
235 | activity.startActivityForResult(intent, 1000)
236 | return true
237 | }
238 | }
239 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/debug/FloatingFpsView.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.debug
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.ViewGroup
6 | import android.widget.FrameLayout
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import java.util.ArrayList
11 | import kotlin.jvm.JvmOverloads
12 |
13 | class FloatingFpsView @JvmOverloads constructor(
14 | context: Context,
15 | attrs: AttributeSet? = null,
16 | defStyleAttr: Int = 0
17 | ) : FrameLayout(context, attrs, defStyleAttr) {
18 | private var mRecyclerView: RecyclerView? = null
19 | private val mStringList: MutableList = ArrayList()
20 | private var mAdapter: RecyclerView.Adapter<*> =
21 | object : RecyclerView.Adapter() {
22 | override fun onCreateViewHolder(
23 | parent: ViewGroup,
24 | viewType: Int
25 | ): RecyclerView.ViewHolder {
26 | val textView = TextView(getContext())
27 | textView.textSize = 12f
28 | textView.setPadding(8, 16, 0, 0)
29 | return object : RecyclerView.ViewHolder(textView) {
30 | override fun toString(): String {
31 | return super.toString()
32 | }
33 | }
34 | }
35 |
36 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
37 | (holder.itemView as TextView).text = mStringList[position]
38 | }
39 |
40 | override fun getItemCount(): Int {
41 | return mStringList.size
42 | }
43 | }
44 |
45 | private fun init() {
46 | setBackgroundColor(0x33000000)
47 | mRecyclerView = RecyclerView(context)
48 | mRecyclerView!!.layoutManager = LinearLayoutManager(context)
49 | mRecyclerView!!.adapter = mAdapter
50 | val params = LayoutParams(250, 500)
51 | addView(mRecyclerView, params)
52 | }
53 |
54 | fun update(content: String?) {
55 | mStringList.add(content)
56 | mAdapter.notifyItemInserted(mStringList.size - 1)
57 | mRecyclerView!!.scrollToPosition(mStringList.size - 1)
58 | }
59 |
60 | init {
61 | init()
62 | }
63 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/debug/MeasureUtil.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.debug
2 |
3 | import android.util.Pair
4 | import android.view.View
5 |
6 | /**
7 | * Created by zyl06 on 1/13/16.
8 | */
9 | object MeasureUtil {
10 | private const val MODE_SHIFT = 30
11 | private const val MODE_MASK = 0x3 shl MODE_SHIFT
12 | private const val UNSPECIFIED = 0 shl MODE_SHIFT
13 | const val EXACTLY = 1 shl MODE_SHIFT
14 | const val AT_MOST = 2 shl MODE_SHIFT
15 | const val WRAP_CONTENT = AT_MOST or MODE_MASK.inv()
16 | fun getMeasuredSize(view: View?): Pair? {
17 | if (view == null) return null
18 | view.measure(WRAP_CONTENT, WRAP_CONTENT)
19 | return Pair(view.measuredWidth, view.measuredHeight)
20 | }
21 |
22 | fun getMeasuredWidth(view: View, height: Int): Int {
23 | view.measure(
24 | WRAP_CONTENT,
25 | View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
26 | )
27 | return view.measuredWidth
28 | }
29 |
30 | fun getMeasuredHeight(view: View, width: Int): Int {
31 | view.measure(
32 | View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
33 | WRAP_CONTENT
34 | )
35 | return view.measuredHeight
36 | }
37 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/fps/ANRMonitorRunnable.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.fps
2 |
3 | import android.app.Activity
4 | import java.lang.ref.WeakReference
5 |
6 | /**
7 | * Author: snail
8 | * Data: 2021/10/9.
9 | * Des:
10 | * version:
11 | */
12 | //构造函数是否可以修改成类同名
13 |
14 | abstract class ANRMonitorRunnable(var invalid: Boolean, var activityRef: WeakReference) : Runnable
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/fps/CollectItem.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.fps
2 |
3 | import android.app.Activity
4 |
5 | /**
6 | * Author: snail
7 | * Data: 2021/10/9.
8 | * Des:
9 | * version:
10 | */
11 | class CollectItem constructor(
12 |
13 | @JvmField var activity: Activity? = null,
14 | @JvmField var sumCost: Long = 0,
15 | @JvmField var sumFrame: Int = 0
16 | )
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/fps/FpsThread.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.fps
2 |
3 | import java.util.concurrent.LinkedBlockingQueue
4 |
5 | /**
6 | * Author: snail
7 | * Data: 2021/10/9.
8 | * Des:
9 | * version:
10 | */
11 | class FpsThread(var linkedBlockingQueue: LinkedBlockingQueue?) : Thread() {
12 |
13 | override fun run() {
14 | super.run()
15 | while (true) {
16 | try {
17 | val runnable = linkedBlockingQueue?.take()
18 | runnable?.run()
19 | } catch (ignored: Exception) {
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/fps/FpsTracker.java:
--------------------------------------------------------------------------------
1 | package com.snail.collie.fps;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Build;
6 | import android.os.Handler;
7 | import android.os.SystemClock;
8 | import android.view.Choreographer;
9 |
10 | import androidx.annotation.NonNull;
11 |
12 | import com.snail.collie.Collie;
13 | import com.snail.collie.core.LooperMonitor;
14 | import com.snail.collie.core.ITracker;
15 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks;
16 | import com.snail.collie.core.ActivityStack;
17 | import com.snail.collie.core.CollieHandlerThread;
18 |
19 | import java.lang.ref.WeakReference;
20 | import java.lang.reflect.Field;
21 | import java.lang.reflect.Method;
22 | import java.util.concurrent.LinkedBlockingQueue;
23 |
24 | public class FpsTracker extends LooperMonitor.LooperDispatchListener implements ITracker {
25 |
26 | private ITrackFpsListener mITrackListener;
27 | private Handler mHandler;
28 | private Handler mANRHandler;
29 | private long mStartTime;
30 | private static FpsTracker sInstance = null;
31 | private CollectItem mCollectItem;
32 | private FpsThread mFpsThread;
33 | private LinkedBlockingQueue mLinkedBlockingQueue = new LinkedBlockingQueue<>();
34 |
35 | public void setTrackerListener(ITrackFpsListener listener) {
36 | mITrackListener = listener;
37 | }
38 |
39 | private FpsTracker() {
40 | mHandler = new Handler(CollieHandlerThread.INSTANCE.getLooper());
41 | mANRHandler = new Handler(CollieHandlerThread.INSTANCE.getLooper());
42 | mCollectItem = new CollectItem();
43 | mFpsThread = new FpsThread(mLinkedBlockingQueue);
44 | mFpsThread.start();
45 | }
46 |
47 | public static FpsTracker getInstance() {
48 | if (sInstance == null) {
49 | synchronized (FpsTracker.class) {
50 | if (sInstance == null) {
51 | sInstance = new FpsTracker();
52 | }
53 | }
54 | }
55 | return sInstance;
56 | }
57 |
58 | private Object callbackQueueLock;
59 | private Object[] callbackQueues;
60 | private Method addTraversalQueue;
61 | private Method addInputQueue;
62 | private Method addAnimationQueue;
63 | private Choreographer choreographer;
64 | public static final int CALLBACK_INPUT = 0;
65 | public static final int CALLBACK_ANIMATION = 1;
66 | public static final int CALLBACK_TRAVERSAL = 2;
67 | private static final String ADD_CALLBACK = "addCallbackLocked";
68 | private boolean mInDoFrame = false;
69 | private ANRMonitorRunnable mANRMonitorRunnable;
70 | private SimpleActivityLifecycleCallbacks mSimpleActivityLifecycleCallbacks = new SimpleActivityLifecycleCallbacks() {
71 |
72 |
73 | @Override
74 | public void onActivityPaused(@NonNull Activity activity) {
75 | super.onActivityPaused(activity);
76 | pauseTrack(activity.getApplication());
77 | }
78 |
79 | // 帧率不统计第一帧
80 | @Override
81 | public void onActivityResumed(@NonNull final Activity activity) {
82 | super.onActivityResumed(activity);
83 | resumeTrack();
84 | }
85 | };
86 |
87 | @Override
88 | public void dispatchStart() {
89 | super.dispatchStart();
90 | mStartTime = SystemClock.uptimeMillis();
91 | if (mANRMonitorRunnable == null) {
92 | mANRMonitorRunnable = new ANRMonitorRunnable(true, new WeakReference<>(ActivityStack.INSTANCE.getTopActivity())) {
93 | @Override
94 | public void run() {
95 | this.getActivityRef();
96 | if (this.getActivityRef().get() != null && !this.getInvalid()) {
97 | synchronized (FpsTracker.this) {
98 | if (mITrackListener != null) {
99 | mITrackListener.onANRAppear(this.getActivityRef().get());
100 | }
101 | }
102 | }
103 | }
104 | };
105 | } else {
106 | mANRMonitorRunnable.setActivityRef(new WeakReference<>(ActivityStack.INSTANCE.getTopActivity()));
107 | }
108 | mANRMonitorRunnable.setInvalid(false);
109 | mLinkedBlockingQueue.add(new Runnable() {
110 | @Override
111 | public void run() {
112 | mANRHandler.removeCallbacksAndMessages(null);
113 | mANRHandler.postDelayed(mANRMonitorRunnable, 5000);
114 | }
115 | });
116 | }
117 |
118 | /**
119 | * Message 内部移除后 ,looper找不到mStartTime归零防止误判
120 | */
121 |
122 | @Override
123 | public void dispatchEnd() {
124 | super.dispatchEnd();
125 | mANRMonitorRunnable.setInvalid(true);
126 | if (mStartTime > 0) {
127 | final long cost = SystemClock.uptimeMillis() - mStartTime;
128 | final boolean isDoFrame = mInDoFrame;
129 | mLinkedBlockingQueue.add(new Runnable() {
130 | @Override
131 | public void run() {
132 | collectInfoAndDispatch(ActivityStack.INSTANCE.getTopActivity(), cost, isDoFrame);
133 | }
134 | });
135 | if (mInDoFrame) {
136 | addFrameCallBack();
137 | mInDoFrame = false;
138 | }
139 | }
140 | }
141 |
142 |
143 | private void addFrameCallBack() {
144 | // 该方法Android P以后无效
145 |
146 | addFrameCallback(CALLBACK_INPUT, new Runnable() {
147 | @Override
148 | public void run() {
149 | mInDoFrame = true;
150 | }
151 | }, true);
152 | }
153 |
154 | private void collectInfoAndDispatch(final Activity activity, final long cost, final boolean inDoFrame) {
155 | // 不记录正常帧帧率
156 | if (cost <= 16) {
157 | mCollectItem.sumFrame++;
158 | mCollectItem.sumCost += Math.max(16, cost);
159 | return;
160 | }
161 | mHandler.post(new Runnable() {
162 | @Override
163 | public void run() {
164 | synchronized (FpsTracker.this) {
165 | if (activity != null) {
166 | mCollectItem.activity = activity;
167 | mCollectItem.sumCost += Math.max(16, cost);
168 | mCollectItem.sumFrame++;
169 | final long sumFrame = mCollectItem.sumFrame;
170 | final long sumCost = mCollectItem.sumCost;
171 | if (sumFrame > 3) {
172 | long averageFps = Math.min(60, sumCost > 0 ? sumFrame * 1000 / sumCost : 60);
173 | if (mITrackListener != null) {
174 | mITrackListener.onFpsTrack(activity, cost, Math.max(1, cost / 16 - 1), inDoFrame, averageFps);
175 | }
176 | }
177 | // 不过度累积
178 | if (mCollectItem.sumFrame > 60) {
179 | resetCollectItem();
180 | }
181 | }
182 | }
183 | }
184 | });
185 | }
186 |
187 |
188 | private Method reflectChoreographerMethod(Object instance, String name, Class>... argTypes) {
189 | try {
190 | Method method = instance.getClass().getDeclaredMethod(name, argTypes);
191 | method.setAccessible(true);
192 | return method;
193 | } catch (Exception e) {
194 |
195 | }
196 | return null;
197 | }
198 |
199 | private T reflectObject(Object instance, String name) {
200 | try {
201 | Field field = instance.getClass().getDeclaredField(name);
202 | field.setAccessible(true);
203 | return (T) field.get(instance);
204 | } catch (Exception e) {
205 | e.printStackTrace();
206 |
207 | }
208 | return null;
209 | }
210 |
211 |
212 | /**
213 | * 监测不同的阶段 Input 、动画、布局
214 | * 简化处理FPS的时候, 没必要区分的这么细
215 | **/
216 | private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
217 | // Android P 以后无效
218 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
219 | return;
220 | }
221 | try {
222 | synchronized (callbackQueueLock) {
223 | Method method = null;
224 | switch (type) {
225 | case CALLBACK_INPUT:
226 | method = addInputQueue;
227 | break;
228 | case CALLBACK_ANIMATION:
229 | method = addAnimationQueue;
230 | break;
231 | case CALLBACK_TRAVERSAL:
232 | method = addTraversalQueue;
233 | break;
234 | }
235 | if (null != method) {
236 | method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
237 | }
238 | }
239 | } catch (Exception ignored) {
240 |
241 | }
242 | }
243 |
244 | @Override
245 | public void destroy(Application application) {
246 | sInstance = null;
247 | LooperMonitor.INSTANCE.release();
248 | }
249 |
250 |
251 | @Override
252 | public void startTrack(Application application) {
253 | Collie.getInstance().addActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks);
254 | }
255 |
256 | private void resumeTrack() {
257 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
258 | if (choreographer == null) {
259 | choreographer = Choreographer.getInstance();
260 | callbackQueueLock = reflectObject(choreographer, "mLock");
261 | callbackQueues = reflectObject(choreographer, "mCallbackQueues");
262 | addInputQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
263 | addAnimationQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
264 | addTraversalQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
265 | }
266 | addFrameCallBack();
267 | }
268 | LooperMonitor.INSTANCE.registerListener(this);
269 | }
270 |
271 | @Override
272 | public void pauseTrack(Application application) {
273 | LooperMonitor.INSTANCE.unregisterListener(this);
274 | mHandler.removeCallbacksAndMessages(null);
275 | mANRHandler.removeCallbacksAndMessages(null);
276 | try {
277 | mLinkedBlockingQueue.clear();
278 | } catch (Exception ignored) {
279 | }
280 | resetCollectItem();
281 | mStartTime = 0;
282 | }
283 |
284 | private void resetCollectItem() {
285 | mCollectItem.sumCost = 0;
286 | mCollectItem.sumFrame = 0;
287 | mCollectItem.activity = null;
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/fps/ITrackFpsListener.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.fps
2 |
3 | import android.app.Activity
4 |
5 | interface ITrackFpsListener {
6 |
7 | fun onFpsTrack(
8 | activity: Activity,
9 | currentCostMils: Long,
10 | currentDropFrame: Long,
11 | isInFrameDraw: Boolean,
12 | averageFps: Long
13 | )
14 |
15 | fun onANRAppear(activity: Activity?)
16 |
17 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/mem/AppMemory.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.mem
2 |
3 | import android.os.Debug
4 |
5 | class AppMemory {
6 | @JvmField
7 | var dalvikPss //java占用内存大小
8 | : Long = 0
9 | @JvmField
10 | var nativePss //前进程总私有已用内存大小
11 | : Long = 0
12 | @JvmField
13 | var totalPss //当前进程总内存大小
14 | : Long = 0
15 |
16 | // 整体信息
17 | @JvmField
18 | var mMemoryInfo: Debug.MemoryInfo? = null
19 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/mem/FdLeakTrack.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.mem
2 |
3 | import android.os.Process
4 | import android.system.Os
5 | import android.util.Log
6 | import java.io.File
7 |
8 | object FdLeakTrack {
9 |
10 | fun collectLeakFds() {
11 | val fdFile = File("/proc/" + Process.myPid() + "/fd/")
12 | val files = fdFile.listFiles()
13 | files?.forEach { file ->
14 | try {
15 | Os.readlink(file.absolutePath);
16 | } catch (e: Exception) {
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/mem/MemoryLeakTrack.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.mem
2 |
3 | import android.app.Activity
4 | import android.app.ActivityManager
5 | import android.app.Application
6 | import android.content.Context
7 | import android.os.Debug
8 | import android.os.Handler
9 | import android.os.SystemClock
10 | import android.text.TextUtils
11 | import com.snail.collie.core.ActivityStack.isInBackGround
12 | import com.snail.collie.core.ProcessUtil.getProcessName
13 | import com.snail.collie.core.ActivityStack.getSize
14 | import com.snail.collie.core.ITracker
15 | import com.snail.collie.core.CollieHandlerThread
16 | import com.snail.collie.Collie
17 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks
18 | import java.lang.Exception
19 | import java.util.*
20 | import kotlin.jvm.Volatile
21 |
22 | //WeakHashMap的Key-Value回收原理 还是依赖ref+Queue
23 | class MemoryLeakTrack private constructor() : ITracker {
24 | private val mHandler = Handler(CollieHandlerThread.looper)
25 | private val mActivityStringWeakHashMap = WeakHashMap()
26 | private val mSimpleActivityLifecycleCallbacks: SimpleActivityLifecycleCallbacks =
27 | object : SimpleActivityLifecycleCallbacks() {
28 | override fun onActivityDestroyed(activity: Activity) {
29 | super.onActivityDestroyed(activity)
30 | mActivityStringWeakHashMap[activity] = activity.javaClass.simpleName
31 | }
32 |
33 | override fun onActivityStopped(activity: Activity) {
34 | super.onActivityStopped(activity)
35 | // 退后台,GC 找LeakActivity
36 | if (!isInBackGround()) {
37 | return
38 | }
39 | mHandler.postDelayed({
40 | mallocBigMem()
41 | Runtime.getRuntime().gc()
42 | }, 1000)
43 | mHandler.postDelayed(Runnable {
44 | try {
45 | if (!isInBackGround()) {
46 | return@Runnable
47 | }
48 | // 分配大点内存促进GC
49 | mallocBigMem()
50 | Runtime.getRuntime().gc()
51 | SystemClock.sleep(100)
52 | System.runFinalization()
53 | val hashMap = HashMap()
54 | for ((key) in mActivityStringWeakHashMap) {
55 | val name = key.javaClass.simpleName
56 | val value = hashMap[name]
57 | if (value == null) {
58 | hashMap[name] = 1
59 | } else {
60 | hashMap[name] = value + 1
61 | }
62 | }
63 | if (mMemoryListeners.size > 0) {
64 | for ((key, value) in hashMap) {
65 | for (listener in mMemoryListeners) {
66 | listener.onLeakActivity(key, value)
67 | }
68 | }
69 | }
70 | } catch (ignored: Exception) {
71 | }
72 | }, 10000)
73 | }
74 | }
75 |
76 | override fun destroy(application: Application) {
77 | Collie.instance.removeActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks)
78 | mHandler.removeCallbacksAndMessages(null)
79 | }
80 |
81 | override fun startTrack(application: Application) {
82 | Collie.instance.addActivityLifecycleCallbacks(mSimpleActivityLifecycleCallbacks)
83 | mHandler.postDelayed(object : Runnable {
84 | override fun run() {
85 | if (mMemoryListeners.size > 0 && !isInBackGround()) {
86 | val trackMemoryInfo = collectMemoryInfo(application)
87 | for (listener in mMemoryListeners) {
88 | listener.onCurrentMemoryCost(trackMemoryInfo)
89 | }
90 | }
91 | mHandler.postDelayed(this, (30 * 1000).toLong())
92 | }
93 | }, (30 * 1000).toLong())
94 | }
95 |
96 | override fun pauseTrack(application: Application) {}
97 | private val mMemoryListeners: MutableSet = HashSet()
98 | fun addOnMemoryLeakListener(leakListener: ITrackMemoryListener) {
99 | mMemoryListeners.add(leakListener)
100 | }
101 |
102 | fun removeOnMemoryLeakListener(leakListener: ITrackMemoryListener) {
103 | mMemoryListeners.remove(leakListener)
104 | }
105 |
106 | interface ITrackMemoryListener {
107 | fun onLeakActivity(activity: String?, count: Int)
108 | fun onCurrentMemoryCost(trackMemoryInfo: TrackMemoryInfo?)
109 | }
110 |
111 | private fun collectMemoryInfo(application: Application): TrackMemoryInfo {
112 | if (TextUtils.isEmpty(display)) {
113 | display =
114 | "" + application.resources.displayMetrics.widthPixels + "*" + application.resources.displayMetrics.heightPixels
115 | }
116 | val activityManager =
117 | application.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
118 | // 系统内存
119 | val memoryInfo = ActivityManager.MemoryInfo()
120 | activityManager.getMemoryInfo(memoryInfo)
121 | val systemMemory = SystemMemory()
122 | systemMemory.availMem = memoryInfo.availMem shr 20
123 | systemMemory.totalMem = memoryInfo.totalMem shr 20
124 | systemMemory.lowMemory = memoryInfo.lowMemory
125 | systemMemory.threshold = memoryInfo.threshold shr 20
126 |
127 | //java内存
128 | val rt = Runtime.getRuntime()
129 |
130 | //进程Native内存
131 | val appMemory = AppMemory()
132 | val debugMemoryInfo = Debug.MemoryInfo()
133 | Debug.getMemoryInfo(debugMemoryInfo)
134 | appMemory.nativePss = (debugMemoryInfo.nativePss shr 10).toLong()
135 | appMemory.dalvikPss = (debugMemoryInfo.dalvikPss shr 10).toLong()
136 | appMemory.totalPss = (debugMemoryInfo.totalPss shr 10).toLong()
137 | appMemory.mMemoryInfo = debugMemoryInfo
138 | val trackMemoryInfo = TrackMemoryInfo()
139 | trackMemoryInfo.systemMemoryInfo = systemMemory
140 | trackMemoryInfo.appMemory = appMemory
141 | trackMemoryInfo.procName = getProcessName(application)
142 | trackMemoryInfo.display = display
143 | trackMemoryInfo.activityCount = getSize()
144 | return trackMemoryInfo
145 | }
146 |
147 | private fun mallocBigMem() {
148 | val leakHelpBytes = ByteArray(4 * 1024 * 1024)
149 | var i = 0
150 | while (i < leakHelpBytes.size) {
151 | leakHelpBytes[i] = 1
152 | i += 1024
153 | }
154 | }
155 |
156 | companion object {
157 | @Volatile
158 | private var sInstance: MemoryLeakTrack? = null
159 | @JvmStatic
160 | val instance: MemoryLeakTrack?
161 | get() {
162 | if (sInstance == null) {
163 | synchronized(MemoryLeakTrack::class.java) {
164 | if (sInstance == null) {
165 | sInstance = MemoryLeakTrack()
166 | }
167 | }
168 | }
169 | return sInstance
170 | }
171 | private var display: String? = null
172 | }
173 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/mem/SystemMemory.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.mem
2 |
3 | class SystemMemory {
4 | @JvmField
5 | var availMem: Long = 0
6 | @JvmField
7 | var lowMemory = false
8 | @JvmField
9 | var threshold: Long = 0
10 | @JvmField
11 | var totalMem: Long = 0
12 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/mem/TrackMemoryInfo.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.mem
2 |
3 | class TrackMemoryInfo {
4 | @JvmField
5 | var procName: String? = null
6 |
7 | @JvmField
8 | var appMemory: AppMemory? = null
9 |
10 | @JvmField
11 | var systemMemoryInfo: SystemMemory? = null
12 |
13 | @JvmField
14 | var display: String? = null
15 |
16 | @JvmField
17 | var activityCount = 0
18 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/startup/LauncherTracker.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.startup
2 |
3 | import android.app.Activity
4 | import android.app.ActivityManager
5 | import android.app.Application
6 | import android.content.Context
7 | import android.graphics.Canvas
8 | import android.graphics.Color
9 | import android.os.*
10 | import android.util.Log
11 | import android.view.View
12 | import android.view.ViewGroup
13 | import android.view.ViewTreeObserver
14 | import com.snail.collie.core.ProcessUtil
15 | import com.snail.collie.core.CollieHandlerThread
16 | import com.snail.collie.core.ITracker
17 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks
18 |
19 | object LauncherTracker : ITracker {
20 |
21 | private var collectHandler: Handler = Handler(CollieHandlerThread.looper)
22 | private var codeStartUp = false
23 | private var launcherFlag = 0
24 | private var createFlag = 1
25 | private var lastActivityPauseTimeStamp: Long = 0
26 | private val mUIHandler = Handler(Looper.getMainLooper())
27 | var iLaunchTrackListener: ILaunchTrackListener? = null
28 | private var sStartUpTimeStamp = 0L
29 | private val activityLifecycleCallbacks: Application.ActivityLifecycleCallbacks =
30 | object : SimpleActivityLifecycleCallbacks() {
31 | override fun onActivityCreated(p0: Activity, p1: Bundle?) {
32 | super.onActivityCreated(p0, p1)
33 | // 重新开始或者第一个Activity
34 | if (lastActivityPauseTimeStamp == 0L) {
35 | lastActivityPauseTimeStamp = SystemClock.uptimeMillis()
36 | }
37 | launcherFlag = createFlag
38 | val currentTimeStamp = lastActivityPauseTimeStamp
39 | mUIHandler.post {
40 | if (p0.isFinishing) {
41 | collectInfo(p0, currentTimeStamp, true)
42 | lastActivityPauseTimeStamp = SystemClock.uptimeMillis()
43 | }
44 | }
45 | }
46 |
47 | override fun onActivityResumed(p0: Activity) {
48 | super.onActivityResumed(p0)
49 | if (launcherFlag == createFlag) {
50 |
51 | val currentTimeStamp = lastActivityPauseTimeStamp
52 | (p0.window.decorView as ViewGroup).addView(InnerView(p0, currentTimeStamp))
53 | p0.window.decorView.viewTreeObserver.addOnWindowFocusChangeListener(object :
54 | ViewTreeObserver.OnWindowFocusChangeListener {
55 | override fun onWindowFocusChanged(hasFocus: Boolean) {
56 |
57 | if (hasFocus) {
58 | iLaunchTrackListener?.let {
59 | it.onActivityFocusableCost(
60 | p0,
61 | SystemClock.uptimeMillis() - currentTimeStamp,
62 | false
63 | )
64 | }
65 | }
66 | p0.window.decorView.viewTreeObserver.removeOnWindowFocusChangeListener(
67 | this
68 | )
69 | }
70 | })
71 | }
72 | launcherFlag = 0
73 | }
74 |
75 | override fun onActivityPaused(p0: Activity) {
76 | super.onActivityPaused(p0)
77 | lastActivityPauseTimeStamp = SystemClock.uptimeMillis()
78 | launcherFlag = 0
79 | }
80 |
81 | override fun onActivityStopped(p0: Activity) {
82 | super.onActivityStopped(p0)
83 | // 退到后台
84 | if (launcherFlag == 0) {
85 | launcherFlag = 0
86 | lastActivityPauseTimeStamp = 0
87 | }
88 | }
89 | }
90 |
91 |
92 | override fun destroy(application: Application) {
93 | application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks)
94 | iLaunchTrackListener = null
95 | }
96 |
97 | override fun startTrack(application: Application) {
98 | sStartUpTimeStamp = SystemClock.uptimeMillis()
99 | application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
100 | codeStartUp = isForegroundProcess(application)
101 | }
102 |
103 | override fun pauseTrack(application: Application) {
104 |
105 | }
106 |
107 | private fun collectInfo(activity: Activity, lastPauseTimeStamp: Long, finishNow: Boolean) {
108 |
109 | val markTimeStamp = SystemClock.uptimeMillis()
110 | val activityStartCost = markTimeStamp - lastPauseTimeStamp
111 | collectHandler.post {
112 | iLaunchTrackListener?.let {
113 | if (codeStartUp) {
114 | val coldLauncherTime = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
115 | markTimeStamp - Process.getStartUptimeMillis() else
116 | markTimeStamp - sStartUpTimeStamp
117 | it.onAppColdLaunchCost(coldLauncherTime, ProcessUtil.getProcessName(activity.application))
118 | codeStartUp = false
119 | }
120 | it.onActivityLaunchCost(activity, activityStartCost, finishNow)
121 | }
122 | }
123 |
124 | }
125 |
126 | // 判断进程启动的时候是否是前台进程
127 | private fun isForegroundProcess(context: Context): Boolean {
128 | val runningAppProcesses =
129 | (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses
130 | for (info in runningAppProcesses) {
131 | if (Process.myPid() == info.pid) {
132 | return info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
133 | }
134 | }
135 | return false
136 | }
137 |
138 | interface ILaunchTrackListener {
139 | fun onAppColdLaunchCost(duration: Long, procName: String?)
140 | fun onActivityLaunchCost(activity: Activity?, duration: Long, finishNow: Boolean)
141 | fun onActivityFocusableCost(activity: Activity?, duration: Long, finishNow: Boolean)
142 | }
143 |
144 | class InnerView(val activity: Activity, private val lastPauseTimeStamp: Long) :
145 | View(activity) {
146 | var marked = false
147 |
148 | init {
149 | setBackgroundColor(Color.TRANSPARENT);
150 | }
151 |
152 | override fun onDraw(canvas: Canvas?) {
153 | super.onDraw(canvas)
154 | if (marked) {
155 | return
156 | }
157 | marked = true
158 | // 可以看做是draw的时机,之后就会交给GPU,误差可能在一个VSYNC
159 | collectInfo(activity, lastPauseTimeStamp, false)
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/trafficstats/ITrackTrafficStatsListener.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.trafficstats
2 |
3 | import android.app.Activity
4 |
5 | interface ITrackTrafficStatsListener {
6 |
7 | fun onTrafficStats(activity: Activity, value: Long)
8 |
9 | }
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/trafficstats/TrafficStatsItem.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.trafficstats
2 |
3 | import android.app.Activity
4 |
5 | class TrafficStatsItem(
6 | var activity: Activity?,
7 | var trafficCost: Long,
8 | var sequence: Int,
9 | var activityName: String?
10 | )
--------------------------------------------------------------------------------
/collie/src/main/java/com/snail/collie/trafficstats/TrafficStatsTracker.kt:
--------------------------------------------------------------------------------
1 | package com.snail.collie.trafficstats
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.net.TrafficStats
6 | import android.os.Bundle
7 | import android.os.Process
8 | import com.snail.collie.core.ITracker
9 | import com.snail.collie.core.SimpleActivityLifecycleCallbacks
10 |
11 | object TrafficStatsTracker : ITracker {
12 |
13 | private var currentStatsCost: Long = 0
14 | private var statsMap: MutableMap = mutableMapOf()
15 | private var sequence: Int = 0
16 | var trafficStatsListener: ITrackTrafficStatsListener? = null
17 |
18 | private val applicationLife: SimpleActivityLifecycleCallbacks =
19 | object : SimpleActivityLifecycleCallbacks() {
20 |
21 | override fun onActivityCreated(p0: Activity, p1: Bundle?) {
22 | super.onActivityCreated(p0, p1)
23 | statsMap[p0] = TrafficStatsItem(p0, 0, sequence++, p0.javaClass.simpleName)
24 | currentStatsCost = TrafficStats.getUidRxBytes(Process.myUid())
25 | }
26 |
27 | override fun onActivityPaused(p0: Activity) {
28 | super.onActivityPaused(p0)
29 | statsMap[p0]?.let {
30 | it.trafficCost += TrafficStats.getUidRxBytes(Process.myUid()) - currentStatsCost
31 | }
32 | }
33 |
34 | override fun onActivityDestroyed(p0: Activity) {
35 | super.onActivityDestroyed(p0)
36 | statsMap[p0]?.let { item ->
37 | trafficStatsListener?.onTrafficStats(p0, item.trafficCost)
38 | }
39 | }
40 | }
41 |
42 |
43 | override fun destroy(application: Application) {
44 | application.unregisterActivityLifecycleCallbacks(applicationLife)
45 | }
46 |
47 | override fun startTrack(application: Application) {
48 | application.registerActivityLifecycleCallbacks(applicationLife)
49 | }
50 |
51 | override fun pauseTrack(application: Application) {
52 |
53 | }
54 |
55 |
56 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useDeprecatedNdk=true
21 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happylishang/Collie/bfdc6782d568bfcefef01e846e81ccfd5a7e3470/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 02 20:44:07 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happylishang/Collie/bfdc6782d568bfcefef01e846e81ccfd5a7e3470/keystore.jks
--------------------------------------------------------------------------------
/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | //每次修改这个文件即可 替换PUBLISH_ARTIFACT_ID即可
5 |
6 | //每次publish之后有延迟
7 |
8 | //先build 在generate pom 在pushlish最长的
9 | task androidSourcesJar(type: Jar) {
10 | classifier = 'sources'
11 | from android.sourceSets.main.java.source
12 |
13 | exclude "**/R.class"
14 | exclude "**/BuildConfig.class"
15 | }
16 |
17 | ext {
18 | PUBLISH_GROUP_ID = 'io.github.happylishang'
19 | PUBLISH_ARTIFACT_ID = 'collie'
20 | PUBLISH_VERSION = '1.1.8'
21 | }
22 |
23 | ext["signing.keyId"] = ''
24 | ext["signing.password"] = ''
25 | ext["signing.secretKeyRingFile"] = ''
26 | ext["ossrhUsername"] = ''
27 | ext["ossrhPassword"] = ''
28 |
29 | File secretPropsFile = project.rootProject.file('local.properties')
30 |
31 | if (secretPropsFile.exists()) {
32 | println "Found secret props file, loading props"
33 | Properties p = new Properties()
34 | p.load(new FileInputStream(secretPropsFile))
35 | p.each { name, value ->
36 | ext[name] = value
37 | }
38 | } else {
39 | println "No props file, loading env vars"
40 | }
41 | publishing {
42 | publications {
43 | release(MavenPublication) {
44 | // The coordinates of the library, being set from variables that
45 | // we'll set up in a moment
46 | groupId PUBLISH_GROUP_ID
47 | artifactId PUBLISH_ARTIFACT_ID
48 | version PUBLISH_VERSION
49 |
50 | // Two artifacts, the `aar` and the sources
51 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
52 | artifact androidSourcesJar
53 |
54 | // Self-explanatory metadata for the most part
55 | pom {
56 | name = PUBLISH_ARTIFACT_ID
57 | description = 'Activity start for result util'
58 | // If your project has a dedicated site, use its URL here
59 | url = 'https://github.com/happylishang/Collie'
60 | licenses {
61 | license {
62 | //协议类型,一般默认Apache License2.0的话不用改:
63 | name = 'The Apache License, Version 2.0'
64 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
65 | }
66 | }
67 | developers {
68 | developer {
69 | id = 'BookSnail'
70 | name = 'BookSnail'
71 | email = 'happylishang@163.com'
72 | }
73 | }
74 | // Version control info, if you're using GitHub, follow the format as seen here
75 | scm {
76 | //修改成你的Git地址:
77 | connection = 'https://github.com/happylishang/Collie.git'
78 | developerConnection = 'https://github.com/happylishang/Collie.git'
79 | url = 'https://github.com/happylishang/Collie'
80 | }
81 | // A slightly hacky fix so that your POM will include any transitive dependencies
82 | // that your library builds upon
83 | withXml {
84 |
85 | def dependenciesNode = asNode().appendNode('dependencies')
86 |
87 | project.configurations.implementation.allDependencies.each {
88 | if (it.name != 'unspecified') {
89 | def dependencyNode = dependenciesNode.appendNode('dependency')
90 | dependencyNode.appendNode('groupId', it.group)
91 | dependencyNode.appendNode('artifactId', it.name)
92 | dependencyNode.appendNode('version', it.version)
93 | }
94 | }
95 |
96 | }
97 | }
98 | }
99 | }
100 | repositories {
101 | // The repository to publish to, Sonatype/MavenCentral
102 | maven {
103 | // This is an arbitrary name, you may also use "mavencentral" or
104 | // any other name that's descriptive for you
105 | name = "mavencentral"
106 |
107 | def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
108 | def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
109 | // You only need this if you want to publish snapshots, otherwise just set the URL
110 | // to the release repo directly
111 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
112 |
113 | // The username and password we've fetched earlier
114 | credentials {
115 | username ossrhUsername
116 | password ossrhPassword
117 | }
118 | }
119 | }
120 | }
121 | signing {
122 | sign publishing.publications
123 | }
--------------------------------------------------------------------------------
/release.properties:
--------------------------------------------------------------------------------
1 | storepassword=lishang2011
2 | keyalias=keystore
3 | keypassword=lishang2011
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':collie'
2 | include ':app'
3 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 |
2 | # Required metadata
3 | # key 和 name 就是创建项目时输入的字段,这里填写自己的项目name和key,其他不用管。
4 | sonar.projectKey=my:project
5 | sonar.projectName=SonarLintCheck
6 | sonar.projectVersion=1.2
7 |
8 | # Path to the parent source code directory.
9 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
10 | # Since SonarQube 4.2, this property is optional if sonar.modules is set.
11 | # If not set, SonarQube starts looking for source code from the directory containing
12 | # the sonar-project.properties file.
13 | sonar.sources=src
14 | # Encoding of the source code
15 | sonar.sourceEncoding=UTF-8
16 | sonar.modules=permmisioncompat,app,compiler
17 |
18 | # Additional parameters
19 | sonar.my.property=value
--------------------------------------------------------------------------------