UC浏览器应该是android手机里 最流行的浏览器之一了,他们有一个功能 相信大家都体验过,就是如果你复制了什么文字,(在其他app中 复制也有这个效果!,所以能猜到肯定是监控了剪切板),就会弹出一个悬浮窗。

悬浮窗这个东西 相信大家很多人都使用过,但是在小米的手机上,应该很多人的悬浮窗是无法使用的,因为小米默认是关闭这个悬浮窗权限的。但是uc往往能绕过小米这个悬浮窗权限控制。除此之外 剪切板在api 11以下

和11以上都是不一样的实现。所以我们要完全复制uc浏览器的这个功能,我们主要需要解决2个问题:

1.对剪切板 这个api 做版本兼容处理。

2.如何绕过悬浮窗权限检查 去实现在小米等收紧悬浮窗权限的手机里依然正常显示悬浮窗。

首先来看api兼容处理怎么做:

在以往app开发的时候,我们基本上都会使用到一个本地物理缓存文件夹,这个里面存放着我们本app的缓存图片啊 之类的其他信息,但是考虑到用户手机容量有限,我们在相当多的时候在操作这个缓存路径的时候是会判断他的大小的,

如果太大了,我们就删除一部分缓存。通常我们会这么做:

  /**
* 返回path路径下的 所有文件大小
* @param path 全路径
* @return 返回-1代表path值为null
*/
public static long getTotalSpace(File path)
{
if (path==null)
{
return -1;
}
return path.getTotalSpace();
}

看上去代码很完美对吧,但是如果我们改一个地方minSdkVersion 改成8

 android {
compileSdkVersion 23
buildToolsVersion "23.0.1" defaultConfig {
applicationId "com.example.administrator.clipboardmanagertest"
minSdkVersion 8
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

我们再看:

你看 ide直接报错了,原来这个api要求是

public static final int GINGERBREAD = 9;

也就是说,这个getTotalSpace这个函数 一定得在9或者9以上的手机里才能正常使用 在9之下的比如8 ,是没有这个api的。
有些人为了懒,他就这么做了:
  /**
* 返回path路径下的 所有文件大小
* @param path 全路径
* @return 返回-1代表path值为null
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static long getTotalSpace(File path)
{
if (path==null)
{
return -1;
}
return path.getTotalSpace();
}

加了一个注解,这样编译能通过了,但实际上这并没有什么卵用,因为这段代码只要在api小于9的手机里执行 依然会报错的。

因为小于9的手机里 没有这个方法。所以这里要做一个简单的api兼容:

  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static long getTotalSpace(File path)
{
if (path==null)
{
return -1;
}
//如果这个sdk大于9 那就使用系统的api
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD)
{ return path.getTotalSpace();
}else//小于9 系统没有这个api 我们就自己算吧。
{
final StatFs statFs=new StatFs(path.getPath());
return statFs.getBlockSize()*statFs.getBlockCount();
}
}

你看这样做就很完美了。同样的,我们在剪切板这个api 上也一样要做兼容处理:

你想一下 uc的那个功能,其实肯定就是开启了一个服务,然后在服务里 监听剪切板的变化对吧,那就看看剪切板的变化 怎么监听:

  public void testCliboardApi()
{
ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() { }
});
}

实际上就是通过这个api进行监听剪切板的变化的,但是 这个api只能支持11或者11以上啊你改成10 就会报错了:

所以我们的目标就是让这个api在11以下的版本也能兼容。好 现在就来完成这个功能,我们首先来自定义一个接口,这个接口实际上就只是写了5个方法 这5个方法在api 》11的 源码里面是都有实现的。

在<11的源码里,实际上只有3个方法实现了,还有2个没有实现(我们主要就是要在小于api11的里面 实现这2个方法)

 package com.example.administrator.clipboardmanagertest;

 /**
* Created by Administrator on 2015/11/25.
*/
//这里我们就定义一个接口,这个接口囊括了 所有我们需要使用的方法
//注意后三个方法 api11以下也是有的,而前2个方法 11或者11以上才有
public interface ClipboardManagerInterfaceCompat { //注意这里的参数 我们使用的是自己定义的接口 而不是sdk里面的ClipboardManager.OnPrimaryClipChangedListener
void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener); void removePrimaryClipChangedListener(OnPrimaryClipChangedListener listener); CharSequence getText(); void setText(CharSequence text); boolean hasText(); }

然后我们可以看一下高于api11的版本里面,这个监听剪切板变化的功能是怎么做的,来稍微看一下源码:

其实也很简单,无非就是发生内容变化的时候 回调一下这个接口的onPrimaryClipChanged方法罢了。

为了兼容 我们也定义一个这样的接口,实际上就是把这段代码给抠出来。

 package com.example.administrator.clipboardmanagertest;

 /**
* Created by Administrator on 2015/11/25.
*/ //注意这个OnPrimaryClipChangedListener 是在api11以后才有的
//我们这里就是把这个接口给拿出来 定义一下 看下CliboardManager的源码就知道了(注意要看api11 以后的源码)
public interface OnPrimaryClipChangedListener {
void onPrimaryClipChanged();
}

然后继续,我们可以想一下 既然是要对api11 以上和以下做2个版本,但实际上这2个版本 都得实现我们上面一开始的那个接口,所以可以定义一个抽象类 帮助我们完成这个功能:

 package com.example.administrator.clipboardmanagertest;

 import java.util.ArrayList;

 /**
* Created by Administrator on 2015/11/25.
*/
//既然我们是要对api11 以上和以下 分别做2个 实体类出来,而且这2个实体类 都必须实现我们的自定义接口。
//所以不妨先定义一个base 的抽象类
public abstract class ClipboardManagerInterfaceCompatBase implements ClipboardManagerInterfaceCompat{ //这个抽象类实际上就只做了一件事 维持一个监听器的list 罢了。
//注意OnPrimaryClipChangedListener 这个类 是我们自定义的,不是高于api11的源码里的
protected final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
= new ArrayList<OnPrimaryClipChangedListener>(); @Override
public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
synchronized (mPrimaryClipChangedListeners) {
mPrimaryClipChangedListeners.add(listener);
}
} //这个方法其实还挺重要的 就是通知所有在这个上面的listenser 内容发生了变化
//注意这里的mPrimaryClipChangedListeners是自定义的 不是系统的
protected final void notifyPrimaryClipChanged() {
synchronized (mPrimaryClipChangedListeners) {
for (int i = 0; i < mPrimaryClipChangedListeners.size(); i++) {
mPrimaryClipChangedListeners.get(i).onPrimaryClipChanged();
}
}
} @Override
public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
synchronized (mPrimaryClipChangedListeners) {
mPrimaryClipChangedListeners.remove(listener);
}
}
}

好,抽象类也有了,我们就来写一下实体类,首先来实现一个高于api11的 类,这个比较简单:实际上就是引用原来系统的代码就可以了:

 package com.example.administrator.clipboardmanagertest;

 import android.annotation.TargetApi;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build; /**
* Created by Administrator on 2015/11/25.
*/
//注意这个实际上对应的就是api11 以上的ClipboardManager了,其实这个是最简单的,你只要调用系统的ClipboardManager 即可
//不要遗漏注解 TargetApi 因为遗漏的话 编译会不过的
public class ClipboardManagerInterfaceCompatImplNormal extends ClipboardManagerInterfaceCompatBase { ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener = new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() {
notifyPrimaryClipChanged();
}
};
private ClipboardManager mClipboardManager; public ClipboardManagerInterfaceCompatImplNormal(Context context) {
mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
super.addPrimaryClipChangedListener(listener);
synchronized (mPrimaryClipChangedListeners) {
if (mPrimaryClipChangedListeners.size() == 1) {
mClipboardManager.addPrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
}
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
super.removePrimaryClipChangedListener(listener);
synchronized (mPrimaryClipChangedListeners) {
if (mPrimaryClipChangedListeners.size() == 0) {
mClipboardManager.removePrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
}
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public CharSequence getText() {
return mClipboardManager.getText();
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void setText(CharSequence text) {
if (mClipboardManager != null) {
mClipboardManager.setText(text);
}
} @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public boolean hasText() {
return mClipboardManager != null && mClipboardManager.hasText();
} }

你看这个高于api11的 实体类 无非就是把api11的 给包了一层罢了。很简单。那我们来看看如何做api11 向下的兼容实体类。

 package com.example.administrator.clipboardmanagertest;

 import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.ClipboardManager;
import android.text.TextUtils;
import android.util.Log; import java.util.logging.LogRecord; /**
* Created by Administrator on 2015/11/25.
*/
//这个就是对应的api11 以下的ClipboardManager 实体类了,实际上这里主要就是要实现api11 以上的那个监听
//我们就用一个最简单的方法 不断监视text变化就可以了
//思路其实也挺简单的 就是把这个 ClipboardManagerInterfaceCompatImplCustom
public class ClipboardManagerInterfaceCompatImplCustom extends ClipboardManagerInterfaceCompatBase implements Runnable { //静态的不会导致内存泄露
private static Handler mHandler;
private CharSequence mLastText;
//这个是设置间隔多少毫秒去检查一次 默认我们设置成1000ms检查一次
public static int CHECK_TIME_INTERVAL = 1000; static {
mHandler = new Handler(Looper.getMainLooper());
} //api11 以下 是android.text.ClipboardManager; 注意和api11以上的android.content.ClipboardManager是 有区别的
ClipboardManager clipboardManager; public ClipboardManagerInterfaceCompatImplCustom(Context context) {
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
} @Override
public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
super.addPrimaryClipChangedListener(listener);
synchronized (mPrimaryClipChangedListeners) {
if (mPrimaryClipChangedListeners.size() == 1) {
startListenDataChange();
}
}
} @Override
public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
super.removePrimaryClipChangedListener(listener);
synchronized (mPrimaryClipChangedListeners) {
if (mPrimaryClipChangedListeners.size() == 0) {
stopListenDataChange();
}
}
} private void stopListenDataChange() {
mHandler.removeCallbacks(this);
} private void startListenDataChange() {
mLastText = getText();
mHandler.post(this);
} @Override
public CharSequence getText() { if (clipboardManager == null) {
return null;
} return clipboardManager.getText();
} @Override
public void setText(CharSequence text) {
if (clipboardManager != null) {
clipboardManager.setText(text);
}
} @Override
public boolean hasText() {
if (clipboardManager==null)
{
return false;
}
return clipboardManager.hasText();
} @Override
public void run() { CharSequence data=getText();
isChanged(data);
mHandler.postDelayed(this,CHECK_TIME_INTERVAL); } private void isChanged(CharSequence data)
{
if (TextUtils.isEmpty(mLastText) && TextUtils.isEmpty(data)) {
return;
}
if (!TextUtils.isEmpty(mLastText) && data != null && mLastText.toString().equals(data.toString())) {
return;
}
mLastText = data;
//如果发生了变化 就通知
notifyPrimaryClipChanged();
}
}

最后定义一个util

 package com.example.administrator.clipboardmanagertest;

 import android.content.Context;
import android.os.Build; /**
* Created by Administrator on 2015/11/25.
*/
public class CliboardManagerUtils {
public static ClipboardManagerInterfaceCompat create(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new ClipboardManagerInterfaceCompatImplNormal(context);
} else {
return new ClipboardManagerInterfaceCompatImplCustom(context);
}
}
}

然后,我们开启一个服务 来监听下 即可:

package com.example.administrator.clipboardmanagertest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast; public class MonitorService extends Service { private ClipboardManagerInterfaceCompat clipboardManagerInterfaceCompat; public MonitorService() {
} @Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
} @Override
public void onCreate() {
clipboardManagerInterfaceCompat = CliboardManagerUtils.create(this);
clipboardManagerInterfaceCompat.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() { Toast.makeText(MonitorService.this, "监听到剪切板发生了变化", Toast.LENGTH_LONG).show();
}
});
super.onCreate();
}
}

最后我们来看一下效果,高于11的版本的效果我就不放了,因为是调用系统的所以肯定成功的,我们看看2.3这个低于11版本的效果 就好了:

剪切的api兼容 我们做完了,那最后再看一下如何弹出悬浮窗 :

 package com.example.administrator.clipboardmanagertest;

 import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView; public class MonitorService extends Service { private ClipboardManagerInterfaceCompat clipboardManagerInterfaceCompat; public MonitorService() {
} @Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
} @Override
public void onCreate() {
mWindowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
clipboardManagerInterfaceCompat = CliboardManagerUtils.create(this);
clipboardManagerInterfaceCompat.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() {
showText(clipboardManagerInterfaceCompat.getText().toString());
}
});
super.onCreate();
}
private WindowManager mWindowManager; public void showText(String mContent)
{
final View rootView = View.inflate(this, R.layout.content_view, null); rootView.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
mWindowManager.removeView(rootView);
}
});
final TextView mTextView;
mTextView = (TextView) rootView.findViewById(R.id.contentTv);
mTextView.setText(mContent); int w = WindowManager.LayoutParams.MATCH_PARENT;
int h = WindowManager.LayoutParams.WRAP_CONTENT; int flags = 0;
int type = 0;
//api版本大于19的时候 TYPE_TOAST用这个参数 可以绕过绝大多数对悬浮窗权限的限制,比如miui
//在小于19的时候 其实也是可以绕过的,只不过小于19你绕过了以后 点击事件就无效了 所以小于19的时候
//还是用TYPE_PHONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
type = WindowManager.LayoutParams.TYPE_PHONE;
} WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(w, h, type, flags, PixelFormat.TRANSLUCENT);
layoutParams.gravity = Gravity.TOP;
mWindowManager.addView(rootView, layoutParams);
} }

别忘记权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
最后看下效果
到这里应该来说模仿的就差不多了,当然你要完全做的和UC一样还是要稍微润色一下ui的,此外,还要监听下启动手机时候的广播,当手机启动的时候 接收到广播
就启动这个监听剪切板的服务即可。点击事件也要稍微修改一下,比如点击以后去你自己的业务逻辑activity 等等。

Android 高仿UC浏览器监控剪切板弹出悬浮窗功能的更多相关文章

  1. ANDROID 通过监听来电去电,并弹出悬浮窗

    要监听android打电话和接电话,有一种的是通过新建一个Receiver继承自BroadcastReceiver. 还有一种也可通过PhoneStateListener来实现.今天就说说后面一种,废 ...

  2. Android高仿UC浏览器和360手机卫士消息常驻栏(通知栏)

    之前网上看了下自己定义消息栏,通知栏,了解到了Notification这个控件.发现UC浏览器等都是这样的类型,今天写个demo实现下.如图: 当中每一个button都有不同的功能.代码例如以下: p ...

  3. android中RecyclerView控件实现长按弹出PopupMenu菜单功能

    之前写过一篇文章:android中实现简单的聊天功能 现在是在之前功能的基础上,添加一个长按聊天记录,删除对应聊天记录的功能 RecyclerView控件,没有对应的长按事件,我们需要自己手工添加,修 ...

  4. Android 高仿微信(QQ)滑动弹出编辑、删除菜单效果,增加下拉刷新功能

    不可否认,微信.QQ列表的滑动删除.编辑功能着实很经典(从IOS那边模仿过来的),然.Android这边,对列表的操作,其实大多还停留上下文菜单来实现. Android如何实现list item的滑动 ...

  5. 3.Android高仿网易云音乐-首页复杂发现界面布局和功能/RecyclerView复杂布局

    0.效果图 效果图依次为发现界面顶部,包含首页轮播图,水平滚动的按钮,推荐歌单:然后是发现界面推荐单曲,点击单曲就是直接进入播放界面:最后是全局播放控制条上点击播放列表按钮显示的播放列表弹窗. 1.整 ...

  6. Android 高仿QQ滑动弹出菜单标记已读、未读消息

    在上一篇博客<Android 高仿微信(QQ)滑动弹出编辑.删除菜单效果,增加下拉刷新功能>里,已经带着大家学习如何使用SwipeMenuListView这一开源库实现滑动列表弹出菜单,接 ...

  7. (android高仿系列)今日头条 --新闻阅读器 (三) 完结 、总结 篇

    从写第一篇今日头条高仿系列开始,到现在已经过去了1个多月了,其实大体都做好了,就是迟迟没有放出来,因为我觉得,做这个东西也是有个过程的,我想把这个模仿中一步一步学习的过程,按照自己的思路写下来,在根据 ...

  8. Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO

    距离上次发布(android高仿系列)今日头条 --新闻阅读器 (二) 相关的内容已经半个月了,最近利用空闲时间,把今日头条客户端完善了下.完善的功能一个一个全部实现后,就放整个源码.开发的进度就是按 ...

  9. Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码

    Android 高仿QQ5.2双向側滑菜单DrawerLayout实现源代码 左右側滑效果图 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a ...

随机推荐

  1. 绑定CPU

    处理器的亲和性 软亲和性(affinity) 意味着进程并不会在处理器之间频繁迁移,而 硬亲和性(affinity) 则意味着进程需要在您指定的处理器上运行. 通常 Linux 内核都可以很好地对进程 ...

  2. 安卓四大组件之--service

    服务:长期后台运行的没有界面的activity,程序写法和activity类似. 安卓系统进程管理是按照一定规则的: 1.默认情况下,关闭掉一个应用程序,清空了这个应用程序的任务栈,应用程序的进程还会 ...

  3. java反射机制(基础版)

    package com.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import ja ...

  4. 内存分析_.Net垃圾回收介绍

    垃圾回收 1.       .Net垃圾回收中涉及的名称 1.1.什么是代? 垃圾回收器为了提升性能使用了代的机制,共分为三代(Gen0.Gen1.Gen2).GC工作机制基于以下假设, 1)  对象 ...

  5. BufferedReader方法-----Scanner方法

    import java.io.*; import java.util.Scanner; public class C { public static void main(String []args) ...

  6. iOS:模态弹出窗控制器UIPopoverPresentationController

    模态弹出窗控制器:UIPopoverPresentationController 实质:就是将内容控制器包装成PopoverPresentationController的形式,然后再模态出来,必须指定 ...

  7. Data Flow ->> Look up & Merge Join

    Look up: Look up组件做的事情和SQL SERVER中的inner和outer hash join差不多. 但是look up每次只能有两张表参与. 在FULL-CACHE模式下,两个s ...

  8. Linux驱动修炼之道-RTC子系统框架与源码分析【转】

    转自:http://helloyesyes.iteye.com/blog/1072433 努力成为linux kernel hacker的人李万鹏原创作品,为梦而战.转载请标明出处 http://bl ...

  9. c#操作txt

    C#追加文件 StreamWriter sw = File.AppendText(Server.MapPath(".")+"\\myText.txt"); sw ...

  10. PHP读取Mongodb数据报错,Cannot natively represent the long 8331412483000 on this platform

    在使用PHP进行读取Mongo数据时,如果读取的int数据过大时,会自动转变为int64位. 并会报以下错误: Cannot natively represent the long 833141248 ...