1 getIdentifier是安卓开发一个非常有用的方法,它属于Context类,用于根据资源的名称和类型动态获取资源的id,常用于跨模块和获取系统的资源id等。
/**
* Return a resource identifier for the given resource name. A fully
* qualified resource name is of the form "package:type/entry". The first
* two components (package and type) are optional if defType and
* defPackage, respectively, are specified here.
*
* <p>Note: use of this function is discouraged. It is much more
* efficient to retrieve resources by identifier than by name.
*
* @param name The name of the desired resource.
* @param defType Optional default resource type to find, if "type/" is
* not included in the name. Can be null to require an
* explicit type.
* @param defPackage Optional default package to find, if "package:" is
* not included in the name. Can be null to require an
* explicit package.
*
* @return int The associated resource identifier. Returns 0 if no such
* resource was found. (0 is not a valid resource ID.)
*/
@Discouraged(message = "Use of this function is discouraged because resource reflection makes "
+ "it harder to perform build optimizations and compile-time "
+ "verification of code. It is much more efficient to retrieve "
+ "resources by identifier (e.g. `R.foo.bar`) than by name (e.g. "
+ "`getIdentifier("bar", "foo", null)`).")
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
name:资源的名称,即在res目录下的资源文件的名称,例如"id_test" defType:资源的类型,例如drawable,layout,string等。 defPackage 资源所在的包名。 如果找不到对应的资源,此方法会返回0。
val resourceId = resources.getIdentifier("id_test", "string", packageName)
if (resourceId !== 0) {
val textString = resources.getString(resourceId)
Log.d("zhangqing", "[onCreate] resourceId=$textString")
} else {
// 资源不存在
Log.d("zhangqing", "[onCreate] resourceId no found")
}
<resources>
<string name="app_name">KotlinDemoTest</string>
<string name="id_test">我爱安卓</string>
</resources>
另外
val getResourceName = resources.getResourceName(resourceId)
val getResourceEntryName = resources.getResourceEntryName(resourceId)
val getResourcePackageName = resources.getResourcePackageName(resourceId)
val getResourceTypeName = resources.getResourceTypeName(resourceId)
Log.d("zhangqing", "[onCreate] getResourceName=$getResourceName")
Log.d("zhangqing", "[onCreate] getResourceEntryName=$getResourceEntryName")
Log.d("zhangqing", "[onCreate] getResourcePackageName=$getResourcePackageName")
Log.d("zhangqing", "[onCreate] getResourceTypeName=$getResourceTypeName")
有时我们在一个应用程序中访问另一个应用程序的资源或上下文,这时我们需要使用createPackageContext()方法,这个方法允许我们获取其他应用的上下文,并且使用这个上下文访问其资源。
2 HttpLoggingInterceptor是OkHttp3提供的一个拦截器,主要用于记录HTTP请求和响应的详细信息,包括请求的URL、方法、头部信息,以及响应的状态码、响应体等内容。(下文会结合具体例子,详细说明)
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.logging;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.platform.Platform;
import okio.Buffer;
import okio.BufferedSource;
import static okhttp3.internal.platform.Platform.INFO;
/**
* An OkHttp interceptor which logs request and response information. Can be applied as an
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
* this class should not be considered stable and may change slightly between releases. If you need
* a stable logging format, use your own interceptor.
*/
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
public interface Logger {
void log(String message);
/** A {@link Logger} defaults output appropriate for the current platform. */
Logger DEFAULT = new Logger() {
@Override public void log(String message) {
Platform.get().log(INFO, message, null);
}
};
}
public HttpLoggingInterceptor() {
this(Logger.DEFAULT);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
private final Logger logger;
private volatile Level level = Level.NONE;
/** Change the level at which this interceptor logs. */
public HttpLoggingInterceptor setLevel(Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
public Level getLevel() {
return level;
}
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
Request request = chain.request();
if (level == Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
return response;
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
}
3 View按压反馈
package com.pictorial.test.screenshot.activity
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.View
import android.view.animation.PathInterpolator
class PressFeedbackHelper {
companion object {
private const val DEFAULT_NARROW_THRESHOLD_SCALE_DURATION = 50L //延迟50ms触发
private const val DEFAULT_NARROW_SCALE_DURATION = 100L //动画执行时长
private const val DEFAULT_NARROW_SCALE = 0.93f //放大缩小的倍数
private const val DEFAULT_NARROW_ALPHA = 0.8f //透明度变化倍数
private val mPressFeedbackInterpolator = PathInterpolator(0.4f, 0f, 0.2f, 1f)
}
private var view: View? = null
private var mPressAnimatorSet: AnimatorSet? = null
private fun cancelAnimation() {
if (mPressAnimatorSet != null && mPressAnimatorSet?.isRunning == true) {
mPressAnimatorSet?.cancel()
mPressAnimatorSet = null
}
}
private fun scalePressFeedback(view: View, scaleDown: Float, alphaDown: Float) {
mPressAnimatorSet = AnimatorSet().apply {
duration = DEFAULT_NARROW_SCALE_DURATION
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, scaleDown),
ObjectAnimator.ofFloat(view, "scaleY", 1f, scaleDown),
ObjectAnimator.ofFloat(view, "alpha", 1f, alphaDown)
)
interpolator = mPressFeedbackInterpolator
}
mPressAnimatorSet?.start()
}
private fun normalPressFeedback(view: View, scaleUp: Float, alphaUp: Float) {
cancelAnimation()
val animatorSet = AnimatorSet().apply {
duration = DEFAULT_NARROW_SCALE_DURATION
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", scaleUp, 1f),
ObjectAnimator.ofFloat(view, "scaleY", scaleUp, 1f),
ObjectAnimator.ofFloat(view, "alpha", alphaUp, 1f)
)
interpolator = mPressFeedbackInterpolator
}
animatorSet.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
}
override fun onAnimationEnd(animation: Animator) {
animatorSet.cancel()
}
override fun onAnimationCancel(animation: Animator) {
}
override fun onAnimationRepeat(animation: Animator) {
}
})
animatorSet.start()
}
fun triggerPressFeedback(view: View) {
this.view = view
val mHandler = Handler(Looper.getMainLooper())
val mPressRunnable = Runnable {
scalePressFeedback(view, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
view.setOnTouchListener { v, event ->
if (event?.action == MotionEvent.ACTION_DOWN) {
mHandler.postDelayed(
mPressRunnable, DEFAULT_NARROW_THRESHOLD_SCALE_DURATION
)
}
if (event?.action == MotionEvent.ACTION_UP) {
mHandler.removeCallbacks(mPressRunnable)
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
v.performClick()
}
if (event?.action == MotionEvent.ACTION_CANCEL) {
mHandler.removeCallbacks(mPressRunnable)
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
return@setOnTouchListener true
}
}
/**
* 按压反馈和处理点击事件
*/
fun triggerPressFeedback(view: View, actionUpListener: ((x: Int, y: Int) -> Unit?)? = null) {
this.view = view
val mHandler = Handler(Looper.getMainLooper())
val mPressRunnable = Runnable {
scalePressFeedback(view, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
view.setOnTouchListener { v, event ->
if (event?.action == MotionEvent.ACTION_DOWN) {
mHandler.postDelayed(
mPressRunnable, DEFAULT_NARROW_THRESHOLD_SCALE_DURATION
)
}
if (event?.action == MotionEvent.ACTION_UP) {
mHandler.removeCallbacks(mPressRunnable)
actionUpListener?.invoke(event.x.toInt(), event.y.toInt())
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
v.performClick()
}
if (event?.action == MotionEvent.ACTION_CANCEL) {
mHandler.removeCallbacks(mPressRunnable)
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
return@setOnTouchListener true
}
}
/**
* touchView 触摸的View
* animView 进行动画的View
*/
fun triggerPressFeedback(touchView: View, animView: View) {
val mHandler = Handler(Looper.getMainLooper())
val mPressRunnable = Runnable {
scalePressFeedback(animView, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
touchView.setOnTouchListener { v, event ->
if (event?.action == MotionEvent.ACTION_DOWN) {
mHandler.postDelayed(
mPressRunnable, DEFAULT_NARROW_THRESHOLD_SCALE_DURATION
)
}
if (event?.action == MotionEvent.ACTION_UP) {
mHandler.removeCallbacks(mPressRunnable)
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
v.performClick()
}
if (event?.action == MotionEvent.ACTION_CANCEL) {
mHandler.removeCallbacks(mPressRunnable)
normalPressFeedback(v, DEFAULT_NARROW_SCALE, DEFAULT_NARROW_ALPHA)
}
return@setOnTouchListener true
}
}
}
4 截屏监听
package com.pictorial.test.screenshot.activity
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.database.ContentObserver
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import java.util.Locale
class ScreenShotHelper(val context: Context) {
private val mainHandler = Handler(Looper.getMainLooper())
private var mStartListenTime = 0L
/**
* 内部存储器内容观察者
*/
private val mInternalObserver: ContentObserver
/**
* 外部存储器内容观察者
*/
private val mExternalObserver: ContentObserver
private val mResolver: ContentResolver
private var listener: OnScreenShotListener? = null
private var lastData: String? = null
private val sHasCallbackPaths: MutableList<String?>? = ArrayList()
private val shotCallBack = Runnable {
if (listener != null) {
val path = lastData
if (!path.isNullOrEmpty()) {
listener?.onShot(path)
}
}
}
/**
* 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br/>
* 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏.
*/
private fun checkCallback(imagePath: String): Boolean {
if (sHasCallbackPaths!!.contains(imagePath)) {
Log.d(
TAG, "ScreenShot: imgPath has done; imagePath = $imagePath"
)
return true
}
// 大概缓存15~20条记录便可
if (sHasCallbackPaths.size >= 20) {
for (i in 0..4) {
sHasCallbackPaths.removeAt(0)
}
}
sHasCallbackPaths.add(imagePath)
return false
}
init {
// 初始化
mInternalObserver = MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, null)
mExternalObserver = MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null)
mResolver = context.contentResolver
// 添加监听
mResolver.registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI, true, mInternalObserver
)
Log.d(TAG, "here start register")
mResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, mExternalObserver
)
mStartListenTime = System.currentTimeMillis()
}
private fun getSystemHigherAndroidT(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun hasPermission(): Boolean {
val isHigherAndroidT = getSystemHigherAndroidT()
var permissions: Array<String> = if (isHigherAndroidT) {
arrayOf(
Manifest.permission.READ_MEDIA_IMAGES,
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
)
}
var hasPermission: Boolean = if (isHigherAndroidT) {
context.checkSelfPermission(Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED
} else {
context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
}
if (!hasPermission) {
ActivityCompat.requestPermissions(context as Activity, permissions, CODE_REQUEST)
}
return hasPermission
}
fun setScreenShotListener(listener: OnScreenShotListener?) {
this.listener = listener
}
fun removeScreenShotListener(listener: OnScreenShotListener) {
if (this.listener === listener) {
synchronized(ScreenShotHelper::class.java) {
if (this.listener === listener) {
this.listener = null
}
}
}
}
fun stopListener() {
Log.d(TAG, "here start unregister")
mResolver.unregisterContentObserver(mInternalObserver)
mResolver.unregisterContentObserver(mExternalObserver)
}
@SuppressLint("ObsoleteSdkInt")
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun handleMediaContentChange(contentUri: Uri) {
var cursor: Cursor? = null
try {
// 数据改变时查询数据库中最后加入的一条数据
cursor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//这里是Cursor查询截屏数据核心代码
val selection = MediaStore.Images.Media.DATA + " LIKE ?"
val selectionArgs = arrayOf("%Screenshots%")
val dataTime = arrayOf(MediaStore.Images.ImageColumns.DATE_ADDED)
mResolver.query(
contentUri, MEDIA_PROJECTIONS, Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
putStringArray(
ContentResolver.QUERY_ARG_SORT_COLUMNS, dataTime
)
putInt(
ContentResolver.QUERY_ARG_SORT_DIRECTION,
ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
putInt(ContentResolver.QUERY_ARG_LIMIT, 1)
putInt(ContentResolver.QUERY_ARG_OFFSET, 0)
}, null
)
} else {
mResolver.query(
contentUri,
MEDIA_PROJECTIONS,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
)
}
if (!hasPermission()) {
Log.d(TAG, "[handleMediaContentChange] hasPermission null")
return
}
if (cursor == null) {
Log.d(TAG, "[handleMediaContentChange]cursor == null")
return
}
if (!cursor.moveToFirst()) {
Log.d(TAG, "[handleMediaContentChange] cursor.moveToFirst() false")
return
}
// 获取各列的索引
val dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
val dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN)
val dateAddIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_ADDED)
// 获取行数据
val data = cursor.getString(dataIndex)
val dateTaken = cursor.getLong(dateTakenIndex)
val dateAdded = cursor.getLong(dateAddIndex)
Log.d(TAG, "[handleMediaContentChange] data =$data")
if (data.isNotEmpty()) {
if (TextUtils.equals(lastData, data)) {
//更改资源文件名也会触发,并且传递过来的是之前的截屏文件,所以只对2分钟以内的有效
// if (System.currentTimeMillis() - dateTaken < 2 * 60*1000) {
// mainHandler.removeCallbacks(shotCallBack)
// mainHandler.postDelayed(shotCallBack, 500)
// }
} else if (checkScreenShot(data, dateTaken) && !checkCallback(data)) {
Log.d(TAG, "[handleMediaContentChange] screenshot Success")
mainHandler.removeCallbacks(shotCallBack)
lastData = data
mainHandler.post(shotCallBack)
}
}
} catch (e: Exception) {
Log.d(TAG, "exception = $e")
} finally {
if (cursor != null && !cursor.isClosed) {
cursor.close()
}
}
}
/**
* 根据包含关键字判断是否是截屏
*/
private fun checkScreenShot(data: String, dateTaken: Long): Boolean {/*
* 判断依据一: 时间判断
*/
// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏
if (dateTaken < mStartListenTime || (System.currentTimeMillis() - dateTaken) > 10 * 1000) {
return false
}
var data: String? = data
if (data == null || data.length < 2) {
return false
}
data = data.lowercase(Locale.getDefault())
for (keyWork in KEYWORDS) {
if (data.contains(keyWork)) {
return true
}
}
return false
}
private inner class MediaContentObserver(
private val mContentUri: Uri, handler: Handler?
) : ContentObserver(handler) {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
if (listener != null) {
handleMediaContentChange(mContentUri)
}
}
}
interface OnScreenShotListener {
fun onShot(data: String?)
}
companion object {
private val KEYWORDS = arrayOf(
"screenshot",
"screen_shot",
"screen-shot",
"screen shot",
"screencapture",
"screen_capture",
"screen-capture",
"screen capture",
"screencap",
"screen_cap",
"screen-cap",
"screen cap",
"snap",
"截屏"
)
const val TAG = "ScreenShotHelper"
const val CODE_REQUEST = 101
val MEDIA_PROJECTIONS = arrayOf(
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DATE_ADDED
)
}
}
package com.pictorial.test.screenshot.activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.pictorial.test.R
class MainActivity : AppCompatActivity() {
private var context: Context? = null
private var mScreenShotHelper: ScreenShotHelper? = null
private var listener = object : ScreenShotHelper.OnScreenShotListener {
override fun onShot(data: String?) {
//目前这里实现的主要是这界面可见监听截屏,界面不可见就不监听截屏了
//当然也可以进程一直在就监听
Log.i("zhangqing", "here screenshot Success")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
}
override fun onResume() {
super.onResume()
mScreenShotHelper = ScreenShotHelper(context as MainActivity)
mScreenShotHelper?.setScreenShotListener(listener)
}
override fun onPause() {
super.onPause()
mScreenShotHelper?.stopListener()
mScreenShotHelper?.removeScreenShotListener(listener)
}
}