系統(tǒng)級(jí)別截屏監(jiān)聽(tīng)(模仿某商城)

模仿某東商城的詳情頁(yè)面截屏?xí)袕棿笆录捻憫?yīng)功能大體實(shí)現(xiàn)了,細(xì)節(jié)沒(méi)做請(qǐng)不要糾結(jié),進(jìn)入正題瓤荔。

對(duì)于系統(tǒng)級(jí)別截屏的監(jiān)聽(tīng),api中并沒(méi)有針對(duì)于這個(gè)功能的監(jiān)聽(tīng)回調(diào)等方法洞就。我的首先想到的就是針對(duì)資源文件的監(jiān)聽(tīng)擅羞。

思路如下:

1.對(duì)于系統(tǒng)級(jí)別的截圖 肯定會(huì)保存到本地(機(jī)型不一樣要注意適配轴脐,主要還是截圖的命名方式不太一樣)
2.對(duì)于資源文件監(jiān)聽(tīng)時(shí)要判斷是否是符合截圖要求的(主要還是截圖時(shí)間與當(dāng)時(shí)時(shí)間差不能太大堪藐,否則會(huì)因?yàn)橛脩?hù)的其他操作影響到)
3.當(dāng)檢測(cè)到資源文件有變化查詢(xún)最后一條并返回展示

話(huà)不多說(shuō)上代碼

MainActivity


import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView img;
    private ScreenShot mScreenShot;
    private TextView tvHelp;
    private TextView tvShare;
    private PopupWindow popupWindow;
    private LinearLayout linearLayout;
    private View showView;


    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initView() {
        linearLayout = findViewById(R.id.linearLayout);
        showView = LayoutInflater.from(this).inflate(R.layout.layout_show_creenshot, null);

        popupWindow = new PopupWindow(showView,
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        img = showView.findViewById(R.id.show_img);
        tvHelp = showView.findViewById(R.id.tv_help);
        tvShare = showView.findViewById(R.id.tv_share);
        tvHelp.setOnClickListener(this);
        tvShare.setOnClickListener(this);
        popupWindow.setWidth(300);
        popupWindow.setHeight(500);



    }

    private void initData() {
        mScreenShot = ScreenShot.getInstance();
        handelSystemScreenShot();
    }

    private void handelSystemScreenShot() {
        if (!MainActivity.this.isFinishing()) {

            mScreenShot.register(MainActivity.this, new ScreenShot.CallbackListener() {
                @Override
                public void onShot(String path) {
                    final String pa = path;
                    Log.i("tag", "onShot: 成功:" + path);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            img.setImageURI(Uri.fromFile(new File(pa)));
                            popupWindow.showAtLocation(linearLayout, Gravity.LEFT,0,0);

                        }
                    });

                }
            });
        } else {

            mScreenShot.unregister();
        }
    }

    @Override
    public void onClick(View v) {
        if(v.getId()==R.id.tv_help){
            Toast.makeText(MainActivity.this,"客服",Toast.LENGTH_LONG).show();
        }else {
            Toast.makeText(MainActivity.this,"分享",Toast.LENGTH_LONG).show();
        }
    }
    
    @Override
    protected void onDestroy() {
        mScreenShot.unregister();
        super.onDestroy();
    }
}

main布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"

    tools:context=".MainActivity">


    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="80dp"
        android:background="@mipmap/ic_launcher"

         />


</LinearLayout>

ScreenShot 類(lèi)

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;


/**
 * 系統(tǒng)截屏監(jiān)聽(tīng)工具滋恬,監(jiān)聽(tīng)系統(tǒng)截屏匀哄,然后對(duì)截圖進(jìn)行處理
 */
public class ScreenShot {
    private static final String TAG = "ScreenShot";
    private static final String[] MEDIA_PROJECTIONS = {
            MediaStore.Images.ImageColumns.DATA,
            MediaStore.Images.ImageColumns.DATE_TAKEN,
            MediaStore.Images.ImageColumns.DATE_ADDED
    };

    /**
     * 截屏依據(jù)中的路徑判斷關(guān)鍵字
     */
    private static final String[] KEYWORDS = {
            "screenshot", "screen_shot", "screen-shot", "screen shot", "screencapture",
            "screen_capture", "screen-capture", "screen capture", "screencap", "screen_cap",
            "screen-cap", "screen cap"
    };

    private ContentResolver mContentResolver;
    private CallbackListener mCallbackListener;
    private MediaContentObserver mInternalObserver;
    private MediaContentObserver mExternalObserver;
    private static ScreenShot mInstance;

    private ScreenShot() {
    }

    /**
     * 獲取 ScreenShot 對(duì)象
     *
     * @return ScreenShot對(duì)象
     */
    public static ScreenShot getInstance() {
        if (mInstance == null) {
            synchronized (ScreenShot.class) {
                mInstance = new ScreenShot();
            }
        }
        return mInstance;
    }

    /**
     * 注冊(cè)
     *
     * @param context          上下文
     * @param callbackListener 回調(diào)監(jiān)聽(tīng)
     */
    public void register(Context context, CallbackListener callbackListener) {
        mContentResolver = context.getContentResolver();
        mCallbackListener = callbackListener;

        HandlerThread handlerThread = new HandlerThread(TAG);
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, handler);
        mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, handler);

        mContentResolver.registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
                true, mInternalObserver);
        mContentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                true, mExternalObserver);
    }

    /**
     * 注銷(xiāo)
     */
    public void unregister() {
        if (mContentResolver != null) {
            mContentResolver.unregisterContentObserver(mInternalObserver);
            mContentResolver.unregisterContentObserver(mExternalObserver);
        }
    }

    private void handleMediaContentChange(Uri uri) {
        Cursor cursor = null;
        try {
            // 數(shù)據(jù)改變時(shí)秦效,查詢(xún)數(shù)據(jù)庫(kù)中最后加入的一條數(shù)據(jù)
            cursor = mContentResolver.query(uri, MEDIA_PROJECTIONS, null, null,
                    MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1");
            if (cursor == null) {
                return;
            }
            if (!cursor.moveToFirst()) {
                return;
            }
            int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            // 處理獲取到的第一行數(shù)據(jù)
            handleMediaRowData(cursor.getString(dataIndex));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    }

    /**
     * 處理監(jiān)聽(tīng)到的資源
     */
    private void handleMediaRowData(String path) {
        long duration = 0;
        long step = 100;



        // 設(shè)置最大等待時(shí)間為1s雏蛮,因?yàn)轺茸宓牟糠质謾C(jī)保存截圖有延遲
        while (!checkScreenShot(path) && duration <= 1000) {
            try {
                duration += step;
                Thread.sleep(step);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (checkScreenShot(path)) {
            if (mCallbackListener != null) {
                //回調(diào)地址
                mCallbackListener.onShot(path);
            }
        }
    }



    private boolean checkScreenShot(String path) {
        if (path == null) {
            return false;
        }
        path = path.toLowerCase();
        for (String keyword : KEYWORDS) {
            //判斷是否符合 截圖命名規(guī)則(過(guò)濾)
            if (path.contains(keyword)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 媒體內(nèi)容觀(guān)察者
     */
    private class MediaContentObserver extends ContentObserver {
        private Uri mUri;

        MediaContentObserver(Uri uri, Handler handler) {
            super(handler);
            mUri = uri;
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            //Log.d("ScreenShot", "圖片數(shù)據(jù)庫(kù)發(fā)生變化:" + selfChange);
            handleMediaContentChange(mUri);
        }
    }

    /**
     * 回調(diào)監(jiān)聽(tīng)器
     */
    public interface CallbackListener {
        /**
         * 截屏
         *
         * @param path 圖片路徑
         */
        void onShot(String path);
    }

}

彈窗

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:background="@color/colorBlack"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/show_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:adjustViewBounds="true" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_share"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorBlack"
            android:gravity="center"
            android:text="分享頁(yè)面"
            android:textColor="@color/colorWrite" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/colorWrite" />

        <TextView
            android:id="@+id/tv_help"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorBlack"
            android:gravity="center"
            android:text="問(wèn)問(wèn)客服"
            android:textColor="@color/colorWrite" />
    </LinearLayout>
</RelativeLayout>

關(guān)于這個(gè)還有幾個(gè)點(diǎn)需要注意
由于是對(duì)系統(tǒng)資源變化的監(jiān)聽(tīng) 所以會(huì)導(dǎo)致以下的問(wèn)題

1.由于機(jī)型 廠(chǎng)商 版本等問(wèn)題影響 會(huì)導(dǎo)致截屏圖片 存儲(chǔ)圖片消耗的時(shí)間不同
2.刪除圖片資源時(shí) 也可能會(huì)調(diào)用 回調(diào)接口

解決:查詢(xún)圖片時(shí)也對(duì)圖片的時(shí)間進(jìn)行判斷 目前暫定3秒(除人為極限操作)能夠滿(mǎn)足圖片插入時(shí)間和刪除回調(diào)的時(shí)間判斷

系統(tǒng)在程序外截圖也會(huì)通過(guò)過(guò)濾被程序檢測(cè)到

1.添加過(guò)濾 因?yàn)橄到y(tǒng)截圖時(shí)會(huì)自動(dòng)帶有截屏?xí)r間 截屏程序 以及存放位置 增加對(duì)于截屏程序包名過(guò)濾

已經(jīng)找到和某東詳情截圖相同的方法了 已更新
碼字不易,點(diǎn)贊收藏謝謝!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阱州,隨后出現(xiàn)的幾起案子挑秉,更是在濱河造成了極大的恐慌,老刑警劉巖苔货,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犀概,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夜惭,警方通過(guò)查閱死者的電腦和手機(jī)姻灶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)诈茧,“玉大人木蹬,你說(shuō)我怎么就攤上這事∪糁澹” “怎么了镊叁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)走触。 經(jīng)常有香客問(wèn)我晦譬,道長(zhǎng),這世上最難降的妖魔是什么互广? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任敛腌,我火速辦了婚禮,結(jié)果婚禮上惫皱,老公的妹妹穿的比我還像新娘像樊。我一直安慰自己,他們只是感情好旅敷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布生棍。 她就那樣靜靜地躺著,像睡著了一般媳谁。 火紅的嫁衣襯著肌膚如雪涂滴。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天晴音,我揣著相機(jī)與錄音柔纵,去河邊找鬼。 笑死锤躁,一個(gè)胖子當(dāng)著我的面吹牛搁料,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郭计,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼霸琴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拣宏,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沈贝,失蹤者是張志新(化名)和其女友劉穎杠人,沒(méi)想到半個(gè)月后勋乾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗡善,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年辑莫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罩引。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡各吨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出袁铐,到底是詐尸還是另有隱情揭蜒,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布剔桨,位于F島的核電站屉更,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏洒缀。R本人自食惡果不足惜瑰谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望树绩。 院中可真熱鬧萨脑,春花似錦、人聲如沸饺饭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘫俊。三九已至蛛芥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間军援,已是汗流浹背仅淑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胸哥,地道東北人涯竟。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親庐船。 傳聞我的和親對(duì)象是個(gè)殘疾皇子银酬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355