使用Xposed框架实现全局复制

作者: 十个雨点 | 来源:发表于2017-02-09 18:45 被阅读3669次

    转载注明出处:简书-十个雨点

    简介

    使用辅助服务实现全局复制中,我介绍了通过辅助服务实现全局复制的功能,极大的提高了复制功能的使用范围,补充了通过点击获取文字的不足。
    如何通过Xposed框架获取点击的文字中,介绍了如何基于Xposed框架实现点击取词功能的,以及相对于辅助服务实现的优势。既然要摆脱辅助服务的限制,当然要把全局复制也用Xposed的方式实现了才行吧!

    先看看效果

    全局复制触发全局复制触发

    也可以下载全能分词体验

    如果你觉得跟基于辅助服务实现的基本上没有区别,那就对了,因为压根就是一张图。。。。

    Xposed 是什么?如何使用

    关于Xposed框架如何使用的问题就不再赘述了,感兴趣的同学可以自行百度,或者参考这篇——如何通过Xposed框架获取点击的文字

    如何实现全局复制

    有两个关键点需要先考虑清楚:

    1. 使用辅助服务实现全局复制是通过遍历AccessibilityNodeInfo来获得当前界面的布局,并获取页面中的文字。所以很自然就可以想到,通过Xposed,可以直接遍历View树,从而拿到当前界面的布局和文字。
    2. 全局复制通过通知栏或者悬浮窗触发,在触发以后,需要在当前Activity进行遍历,而不能被其他后台的Activity影响。从描述中就可以联想到Activity的生命周期:只要在onStart里注册一个BroadcastReceiver,用于接受触发全局复制的命令,然后在onStop里注销。

    明确这两点以后就可以写代码了:
    首先是注入Activity的onStart和onStop方法

    
    public class XposedBigBang implements IXposedHookLoadPackage {
    
        private static final String TAG = "XposedBigBang";
    
        private final XposedUniversalCopyHandler mUniversalCopyHandler = new XposedUniversalCopyHandler();
        private XSharedPreferences appXSP;
    
        @Override
        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
            mFilters.add(new Filter.TextViewValidFilter());        
            mUniversalCopyHandler.setFilters(mFilters);
            // installer  不注入。 防止代码出错。进不去installer 中。
            if (!"de.robv.android.xposed.installer".equals(loadPackageParam.packageName) && !"com.android.systemui".equals(loadPackageParam.packageName)) {            
                findAndHookMethod(Activity.class, "onStart",  new UniversalCopyOnStartHook());
                findAndHookMethod(Activity.class, "onStop",  new UniversalCopyOnStopHook());
            }
        }
        private class UniversalCopyOnStartHook extends XC_MethodHook {
    
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Activity activity = (Activity) param.thisObject;
                mUniversalCopyHandler.onStart(activity);
            }
        }
        private class UniversalCopyOnStopHook extends XC_MethodHook {
    
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Activity activity = (Activity) param.thisObject;
                mUniversalCopyHandler.onStop(activity);
            }
        }
    }
    

    最终都调用到了UniversalCopyHandler中,onStart和onStop只要简单的注册和注销BroadcastReceiver就行了,这里要注意的是:用try-catch把这部分代码包起来,否则容易出现崩溃:

    
    public class XposedUniversalCopyHandler {
        public static final String TAG="UniversalCopyHandler";
    
    
        List<Activity> mActivities=new ArrayList<>();
        IntentFilter intentFilter=new IntentFilter(UNIVERSAL_COPY_BROADCAST_XP);
        Handler handler;
        List<Filter> mFilters;
    
        public void setFilters(List<Filter> mFilters) {
            this.mFilters = mFilters;
        }
    
        public void onStart(Activity activity){
            mActivities.add(activity);
            try {
                activity.getApplication().registerReceiver(mUniversalCopyBR,intentFilter);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    
        public void onStop(Activity activity){
            mActivities.remove(activity);
            if (mActivities.size()==0){
                try {
                    activity.getApplication().unregisterReceiver(mUniversalCopyBR);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }
        
        
        private BroadcastReceiver mUniversalCopyBR = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (handler==null){
                    handler=new Handler(Looper.getMainLooper());
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        startUniversalCopy();
                    }
                });
            }
        };
    }
    

    收到广播以后,就会调用到startUniversalCopy()方法,这里做的是:拿到当前Activity,遍历其DecorView,然后把结果发送到显示全局复制结果页中显示。直接看代码

        private void startUniversalCopy(){
            Log.e(TAG,"startUniversalCopy");
            Activity topActivity=null;
            ActivityManager activityManager= (ActivityManager) mActivities.get(0).getApplication().getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningTaskInfo> taskInfos=activityManager.getRunningTasks(1);
            if (taskInfos.size()>0){
                ComponentName top=taskInfos.get(0).topActivity;
                if (top!=null){
                    String name=top.getClassName();
                    for (Activity activity:mActivities){
                        if (activity.getClass().getName().equals(name)){
                            topActivity=activity;
                            break;
                        }
                    }
                }
            }
            if (topActivity==null){
                if (mActivities.size()>0) {
                    topActivity = mActivities.get(mActivities.size() - 1);
                    if (topActivity.isFinishing()){
                        topActivity=null;
                    }
                }
            }
            UniversalCopy(topActivity);
        }
    
        private int retryTimes=0;
        private void UniversalCopy(final Activity activity) {
            if (activity==null){
                return;
            }
            boolean isSuccess=false;
            label37: {
                View decirView =activity.getWindow().getDecorView();
                if(this.retryTimes < 10) {
                    String packageName;
                    packageName = activity.getPackageName();
    
                    if(decirView == null || packageName != null && packageName.contains("com.android.systemui")) {
                        ++this.retryTimes;
                        this.handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                UniversalCopy(activity);
                            }
                        }, 100);
                        return;
                    }
    
                    WindowManager var5 = (WindowManager)activity.getSystemService(Context.WINDOW_SERVICE);
    
                    DisplayMetrics displayMetrics = new DisplayMetrics();
                    var5.getDefaultDisplay().getMetrics(displayMetrics);
                    int var1 = displayMetrics.heightPixels;
                    int var2 = displayMetrics.widthPixels;
                    ArrayList<CopyNode> nodeList = traverseNode(decirView, var2, var1);
                    for (CopyNode node:nodeList) {
                        Log.e(TAG, "traverseNode result= " + node);
                    }
                    if(nodeList.size() > 0) {
    //                    Intent intent = new Intent(activity, CopyActivity.class);
                        Intent intent = new Intent();
                        intent.setComponent(new ComponentName(XposedConstant.PACKAGE_NAME,"com.forfan.bigbang.copy.CopyActivity"));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        Bundle bundle=new Bundle();
                        bundle.setClassLoader(CopyNode.class.getClassLoader());
                        bundle.putString("source_package", packageName);
                        bundle.putParcelableArrayList("copy_nodes", nodeList);
                        intent.putExtras(bundle);
                        try {
                            activity.startActivity(intent);
                        } catch (Throwable e) {
                            e.printStackTrace();
                        }
                        isSuccess = true;
                        break label37;
                    }
    
    //                ae.a(this.getApplication(), "APP_DATA", "UC_MODE_FAILED", packageName);
                }
    
                isSuccess = false;
            }
    
            if(!isSuccess) {
                try {
                    Toast.makeText(activity, "error" , Toast.LENGTH_SHORT).show();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
            this.retryTimes = 0;
        }
    
        private ArrayList<CopyNode> traverseNode(View nodeInfo, int screenWidth, int scerrnHeight) {
            ArrayList nodeList = new ArrayList();
            if(nodeInfo != null ) {
                if (!nodeInfo.isShown()){
                    return nodeList;
                }
                if (nodeInfo instanceof ViewGroup){
                    ViewGroup viewGroup = (ViewGroup) nodeInfo;
                    for(int var4 = 0; var4 < viewGroup.getChildCount(); ++var4) {
                        nodeList.addAll(this.traverseNode(viewGroup.getChildAt(var4), screenWidth, scerrnHeight));
                    }
                }
                if(nodeInfo.getClass().getName() != null && nodeInfo.getClass().getName().equals("android.webkit.WebView")) {
                    return nodeList;
                } else {
                    String content = null;
                    String description = content;
                    if(nodeInfo.getContentDescription() != null) {
                        description = content;
                        if(!"".equals(nodeInfo.getContentDescription())) {
                            description = nodeInfo.getContentDescription().toString();
                        }
                    }
    
                    content = description;
                    String text=getTextInFilters(nodeInfo,mFilters);
                    if(text != null) {
                        content = description;
                        if(!"".equals(text)) {
                            content = text.toString();
                        }
                    }
    
                    if(content != null) {
                        Rect var8 = new Rect();
                        nodeInfo.getGlobalVisibleRect(var8);
                        if(checkBound(var8, screenWidth, scerrnHeight)) {
                            nodeList.add(new CopyNode(var8, content));
                        }
                    }
    
                    return nodeList;
                }
            } else {
                return nodeList;
            }
        }
    
        private String getTextInFilters(View v,List<Filter> filters){
            for (Filter filter:filters){
                if (filter.filter(v)){
                    return filter.getContent(v);
                }
            }
            return null;
        }
    
        private boolean checkBound(Rect var1, int var2, int var3) {
            return var1.bottom >= 0 && var1.right >= 0 && var1.top <= var3 && var1.left <= var2;
        }
    

    至于如何展示和让用户选择要复制的文字,则跟使用辅助服务实现全局复制一毛一样,这里就不再赘述了。

    源码

    详细代码可以看Bigbang工程源码的XposedBigBang和XposedUniversalCopyHandler类,XposedBigBang还包含了监控点击的hook,阅读代码时不要被影响了,感兴趣的同学可以看这篇——如何通过Xposed框架获取点击的文字

    还需要注意的是,Bigbang工程的通过productFlavors来区分Xposed版本和普通版本的,运行代码的时候注意修改。

    源码也可以看UniversalCopy_xposed工程,这个是单独的Xposed实现全局复制的工程,除了和Bigbang中一样的全局复制功能,还包含了一些其他功能。

    相关文章

      网友评论

      本文标题:使用Xposed框架实现全局复制

      本文链接:https://www.haomeiwen.com/subject/cbbybttx.html