1 /* <lambda>null2 * Copyright (C) 2015 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.example.leakcanary 17 18 import android.animation.ObjectAnimator 19 import android.animation.ValueAnimator 20 import android.app.Activity 21 import android.app.AlertDialog 22 import android.content.BroadcastReceiver 23 import android.content.Context 24 import android.content.Intent 25 import android.content.IntentFilter 26 import android.os.Build 27 import android.os.Bundle 28 import android.os.Handler 29 import android.os.Looper 30 import android.os.SystemClock 31 import android.view.View 32 import android.widget.Button 33 import java.util.concurrent.atomic.AtomicReference 34 import kotlin.concurrent.thread 35 import kotlin.random.Random 36 import leakcanary.AppWatcher 37 38 class MainActivity : Activity() { 39 40 private var leakyReceiver = false 41 42 override fun onCreate(savedInstanceState: Bundle?) { 43 super.onCreate(savedInstanceState) 44 setContentView(R.layout.main_activity) 45 46 val app = application as ExampleApplication 47 findViewById<Button>(R.id.recreate_activity_button).setOnClickListener { recreate() } 48 findViewById<Button>(R.id.leak_activity_button).setOnClickListener { 49 val leakedView = findViewById<View>(R.id.helper_text) 50 when (Random.nextInt(4)) { 51 // Leak from application class 52 0 -> app.leakedViews.add(leakedView) 53 // Leak from Kotlin object singleton 54 1 -> LeakingSingleton.leakedViews.add(leakedView) 55 2 -> { 56 // Leak from local variable on thread 57 val ref = AtomicReference(this) 58 thread(name = "Leaking local variables") { 59 val activity = ref.get() 60 ref.set(null) 61 while (true) { 62 print(activity) 63 SystemClock.sleep(1000) 64 } 65 } 66 } 67 // Leak from thread fields 68 else -> LeakingThread.thread.leakedViews.add(leakedView) 69 } 70 } 71 findViewById<Button>(R.id.show_dialog_button).setOnClickListener { 72 AlertDialog.Builder(this) 73 .setTitle("Leaky dialog") 74 .setPositiveButton("Dismiss and leak dialog") { dialog, _ -> 75 app.leakedDialogs += dialog as AlertDialog 76 } 77 .show() 78 } 79 findViewById<Button>(R.id.start_service_button).setOnClickListener { 80 startService(Intent(this, LeakingService::class.java)) 81 } 82 findViewById<Button>(R.id.leak_receiver_button).setOnClickListener { 83 leakyReceiver = true 84 recreate() 85 } 86 findViewById<Button>(R.id.message_leak_button).setOnClickListener { 87 val leaky = Any() 88 AppWatcher.objectWatcher.expectWeaklyReachable(leaky, "Repeated Message") 89 @Suppress("unused") 90 class LeakyReschedulingRunnable(private val leaky: Any) : Runnable { 91 private val handler = Handler(Looper.getMainLooper()) 92 override fun run() { 93 handler.postDelayed(this, 1000) 94 } 95 } 96 LeakyReschedulingRunnable(leaky).run() 97 } 98 findViewById<Button>(R.id.infinite_animator).setOnClickListener { view -> 99 ObjectAnimator.ofFloat(view, View.ALPHA, 0.1f, 0.2f).apply { 100 duration = 100 101 repeatMode = ValueAnimator.REVERSE 102 repeatCount = ValueAnimator.INFINITE 103 start() 104 } 105 } 106 findViewById<Button>(R.id.finish_activity).setOnClickListener { view -> 107 finish() 108 } 109 } 110 111 class NoOpBroadcastReceiver : BroadcastReceiver() { 112 override fun onReceive( 113 context: Context, 114 intent: Intent 115 ) = Unit 116 } 117 118 override fun onDestroy() { 119 super.onDestroy() 120 if (leakyReceiver) { 121 Handler().postDelayed({ 122 if (Build.VERSION.SDK_INT >= 33) { 123 val flags = Context.RECEIVER_EXPORTED 124 application.registerReceiver(NoOpBroadcastReceiver(), IntentFilter(), flags) 125 } else { 126 application.registerReceiver(NoOpBroadcastReceiver(), IntentFilter()) 127 } 128 }, 500) 129 } 130 } 131 } 132