文章标签 » 原创

超星雅儿跳课Xposed模块开发

目的实现:超星雅儿跳课Xposed模块开发

前言

上大学的时候大多数的本科或者专科学生会在 超星XX选一门课程作为通修课,然后下载app看视频。最恶心的事情是什么?不能拖动进度条和快进 除非你看完了再看第二边。 而我们利用HOOK技术实现快速看完视频。。。

以下内容仅供学习,禁止商业用途。。。
适用超星版本:3.0.2(现在最新的了吧)
以下教程因为涉及大多高校正常教学制度。所以教程加密并且不公开。核心教程和源码地址(已经加密):

我的思路一: 先不管 三七二十一 反编译app 很可惜 加固了。。。脱壳拖了几天最后把自己裤子了睡觉

思路二:抓取数据包 然后分析访问接口的规则 然后自己把自己本地架设成远程访问服务器。

思路三:HOOK

思路一走不通,对于思路二我在想 核心接口都用了某个加密规则加密了吧?因为高考数学没到140所以放弃吧。我最后考虑最后一个。

提示:我原本思路二是解析加密规则麻烦,返现app某个接口没有加密,后来英语四级没时间咯,所以只实现HOOK。。
有人问我 这一步有人问我怎么做。这里提供一个小思路 ,我们电脑访问www.baidu.com ,先会到电脑C:\Windows\System32\drivers\etc\hosts 寻找域名和端口的射影,如果不存在 会像网络的DNS(域名系统)寻找。假设app 访问 www.XXXX.com 你改下射影文件不就好了?(别问我这不是windos下的修改方法,稍微变通下你懂的)然后你反回一段完成此视频的信息,那么你就可以拖动进度条了。

HOOK初探:
这里是基于XPosed编写的。
我的HOOK思路之一:首先查看视频播放界面。DDMS有dump view工具。
然后看到如下界面:
这里写图片描述

看到一个SeekBar,我就想着开发者是不是给SeekBar添加一个监听,判断是不是用户手动拖动的(监听回调有一个参数判断是不是手动滑动的进度条)。
1 如果是用户手动拖动的那么判断是否看过视频,如果没看过,禁止拖动
2 如果不是那么不管

结论:失败。。。。

tip:布局中还有个Seekbar那时候我获取错了,弄了半天

直接反射修改SeekBar 进度。

结论:失败

tip:得到思路 作者应该每次拖动的时候判断 你以上看到哪了,或者是否看完了。

所以。。。。。。 以下思路加密提供

思路和源码(已加密)

Android之免清单注册启动Activity

在此立志:我要努力大学毕业进BAT


实习目标:Activity不需要注册在清单即可通过intent启动。

  • 有些文章叫做hook技术。大致内容为监听方法或者的调用或触发,期间修改方法参数或者返回值达到无须需改app源码即可修改app。如Xpose有插件可防止qq撤销消息。

  • 我们今天监听activity的启动然后进行方法修改,期间会用动态代理和大量的反射

Activity启动流程分析 第一章

首先学习activity启动流程:
假设我们界面有个按钮 点击后触发activityonclick方法

//activity
public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

按照我们的理解接下来的事情自然就是界面跳转了。我主要研究的startActivity内部实现的调用过程到启动一个activity流程。
这个方法的实现是在ContextImpl.java 中然后我们再看继承关系图:
这里写图片描述
结果方法继承关系里面并没有ContextImpl.java 。他其实被包裹在ContextWrapper.java

//ContextWrapper.java
public class ContextWrapper extends Context {
    Context mBase;
    ...省略其他代码
 @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
    ...省略其他代码  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看到里面有个mBase属性对象。在调用startActivity的时候会调用mBasestartActivity方法。这个mBase就是ContextImpl.java

现在来分析ContextImpl.java
我们看看这个类的startActivity的方法的实现

//ContextImpl.java
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

直接看startActivity(intent, null);方法,前一个方法是用于进程检测的。

 @Override
    public void startActivity(Intent intent, Bundle options) {
      //...省略代码 只看方法的重点
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到此方法调用 mMainThread.getInstrumentation()获取一个对象然后调用对象的方法啊execStartActivity。其中 mMainThread对象的声明为final ActivityThread mMainThread;

mMainThread.getInstrumentation()查看实现如下

//ActivtyThread.java
 public Instrumentation getInstrumentation()
    {
        return mInstrumentation;
    }
  • 1
  • 2
  • 3
  • 4
  • 5

所以我们继续查看Instrumentation 对象的execStartActivity方法

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 //...省略方法其他方法
 int result = ActivityManagerNative.getDefault().startActivity(一堆参数);

//...     
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的代码就是传入的参数特别的参数太多。所以用四个字代替。我们看到ActivityManagerNative.getDefault()获取一个对象然后调用startActivity()

ActivityManagerNative.getDefault()方法探究

//ActivityManagerNative.java
  static public IActivityManager getDefault() {
        return gDefault.get();
    }
  • 1
  • 2
  • 3
  • 4

gDefaultActivityManagerNative.java属性对象

声明如下:

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Singleton又是什么?这个是一个单例工具类,当你第一次调用此类的get()会回调使用则自己实现的抽象方法create()进而进行单例操作

此类完整代码:

//Singleton.java
package android.util;
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这类我们以后反射其拿到IActivityManager 大家可以简单可以

大家再回过头看看实现抽象的方法create()

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            //从系统服务管理器获取activityMnagerServer(这是一个系统服务,所以需要AIDL进行进程间通信)
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
           //利用IBinder 对象转化为对应实例化接口
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从上面的例子可以看到,看到ActivityManagerNative.getDefault()返回了远程服务对象IActivityManager 接口。

再回头看看Instrumentation 对象的execStartActivity方法

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 //...省略方法其他方法
 int result = ActivityManagerNative.getDefault().startActivity(一堆参数);

//...     
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以得出以下结论:获取一个远程服务对象接口IActivityManager(并且是单例)然后调用IActivityManagerstartActivity方法。此时会回调到远程服务ActivityManagerServer的真正startActivity接口方法。里面会检查你传入的Intent对象的Activity是否在清单内,如果你的activity不在清单内,就是一个异常.但是我们不需要再看ActivityManagerServer源码,因为它是另一个app进程,所以无法干涉。如果可以干涉的话,Android系统就太不稳定了,肯定会有流氓开发者,让你按上一个app干扰其他app那不是奔了?所以我们无法hook到远程服务,但是IActivityManager我们却是可以用手脚的。因为那是服务端存在客户端一个代理接口对象、(这一块需要大家简单了解下AIDL的知识。如果不理解没关系,只需记住它做些手脚)

看完上面的很乱吧,看下图来屡屡思路:
(大家可以右键保存看大图)
这是个图片
这时候读者一定会问?既然ActivityManagerServer启动activity我们无法干涉那么后面干嘛?

我们仔细看上面的图IActivityManager调用的时候会把要启动的ActivityIntent去给ActivityManagerServer,假设我们此时用动态代理在IActivityManager调用远程服务之前,把在一个在清单文件注册过的ActivityIntent替换不就通过校验了?当然这时候你又会想既然替换通过了验证,但是我们真正要启动的Activity都被替了哪还有什么意义?这里过验证后系统没有真正启动Activity,因为这个ActivityManagerServer最终回调到* *启动的 。这里大家先不要着急,先通过验证再说。

第一个小目标 实现替换

假设我们我们有3个Activity第一个为MainActivity,第二个PlaceholdActivity 第三个为SecondActivity

  • MainActivity 在清单文件注册过,继承哪个Activity都无所谓

  • PlaceholdActivity 在清单文件注册过,继承哪个Activity都无所谓

  • SecondActivity没有清单文件注册过,并且继承Activity。(不要继承不然会失败AppCompatActivity)

其中我们在MainActivity 点击一个按钮触发其onclick方法调用startActivity跳转到SecondActivity

//MainActivity.java 
    public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

按照常理因为SecondActivity没有在清单文件注册过所以会失败,我们的目的把这个意图换成启动PlaceholdActivity 的意图

步骤1

创建一个Application类名为APP.java(记得在清单文件中关联哦)
重写attachBaseContext方法

  • attachBaseContext方法介绍:
    此方法会在oncreate方法前调用,还记得我们说的ContextWrapperContextImpl吗?再回过头看看吧(application也是继承ContextWrapper的)。
public class ContextWrapper extends Context {
    Context mBase;

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

attachBaseContext是在传入mBase对象赋值的回调的。此时你其实就可以使用上下文了。

此时我们的代码如下:

//APP.java
public class APP extends Application {
    //后面有用,用于保存原目标Activity的Intent的KEY 大家先看看
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们前面分析知道想要在过Activity的清单文件校验,就必须在IActivityManager调用startActivity方法之前进行Intent意图替换。
所以我们直接用动态代理IActivityManagerIActivityManager

首先要动态代理你必须拿到IActivityManager对象。而此对象保存在ActivityManagerNative的属性对象gDefault中。我们调用属性对象gDefault(Singleton类)的get方法或者得到其内部属性对象mInstance

mInstance是用于保存一个单例对象的实例。很混乱?再回头看看Singleton

//Singleton.java
public abstract class Singleton<T> {
      //保存对象实例
    private T mInstance;
    //Singleton对象为空会调用此方法得到实例
    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

好了先获取gDefault对象吧,我们看看ActivityManagerNative对其的声明

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我们发现是个静态类,所以我们应该笑了,静态类通过反射机制很容易得到对象实例

  • 所以得到如下代码:
public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            Class<?> ams_class = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ams_class.getDeclaredField("gDefault");
            //因为gDefault是private所以用发射机制打破访问限制
            gDefault.setAccessible(true);
            //得到gDefault实例对象 因为是stati属性所以get传入null即可得到
            Object gDefault_instance = gDefault.get(null);
            //单例工具类class
            Class<?> singleton_class = Class.forName("android.util.Singleton");
            //单例工具类的一个属性对象保存的是IActivityManager对象实例
            Field mInstance = singleton_class.getDeclaredField("mInstance");
            //打破封装访问
            mInstance.setAccessible(true);
            //传入gDefault对象实例得到IActivityManager对象实例
            final Object mInstance_instance = mInstance.get(gDefault_instance);
            //动态代理,这个不会我真不知道讲下去。此方法会返回一个实现IActivityManager接口的实例对象
            //拿着这个对象 替换单例工具类gDefault 的mInstance即可实现
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                   // IActivityManager接口方法太多 我们只关心它启动activity的接口方法
                    if (method.getName().equals("startActivity")) {
                        //获取调用此方法传入的参数 我们这里只要Intent替换,所以只需要Intent替换
                        for (int i = 0; i < args.length; i++) {
                            Object arg = args[i];
                            if (arg instanceof Intent) {
                                Intent intent = new Intent();
                                //PlaceholdActivity因为在清单文件注册过所以 创建一个新的Intent替换原来的SecondActivity的意图。后面用到
                                // 并将其保存到新的Intent中
                                ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
                                intent.setComponent(componentName);
                                intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
                                args[i] = intent;
                                return method.invoke(mInstance_instance, args);
                            }
                        }

                    }

                    return method.invoke(mInstance_instance, args);
                }
            });
//         替换单例工具类gDefault 的mInstance实现动态代理
            mInstance.set(gDefault_instance, proxyInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

你运行代码后发现不管startActivity填入什么意图都是启动PlaceholdActivity就证明你成功了

Activity启动流程分析之Handler源码分析 第二章

这里只会简单讲讲不会过多。不然后脱离主旨
Looper循环器,不断从对应线程MassgaeQueue中死循环拿取mesaage然后发送到对应的handler

handler有消息的会调用如下方法:

    //Handler.java
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //如果mCallback 不为空并且处理后返回true 那么不会调用我们自己写handlerMessage处理消息。
            handleMessage(msg);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 我们后面会用此mCallback 就行再次替换Intent

我们知道在学习javase的时候我们程序是从main方法进入,那么安卓的app入门方法在哪?ActivityThread.java中存在的main方法就是

//ActivityThread.java
public static void main(String[] args) {

        //...代码省略
        //Looper类一些初始化,会从ThreadLocal对象拿取当前线程的Looper
        //关于ThreadLocal不做过多讲述,简单就是用线程资源拷贝。
        //假设你有个变量a 你可以拷贝三份到ThreadLocal中不同的线程 
        Looper.prepareMainLooper();
        //...代码省略 
        ActivityThread thread = new ActivityThread();
         //开启一个子线程,又可以叫binder线程用于ams(ActivityManagerServer通AIDL来进程间通信,如果有信息那么从子线程发送message给主线程handler进行处理)
        thread.attach(false);

       //...代码省略
           //开启一个死循环遍历messageQueue(里面存放messager),如果有就,所以前面才开启一个子线程,不然怎么跟AMS通信?
        Looper.loop();

       //...代码省略
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

看看 thread.attach(false);方法

//ActivityThread.java
  private void attach(boolean system) {
        sCurrentActivityThread = this;
          //...忽略代码
      }
  • 1
  • 2
  • 3
  • 4
  • 5

我们只看到方法 把当前线程对象(ActivityThread)保存到sCurrentActivityThread 我们后面会用到所以这里讲解。

我们前面第一章分析中到通过IActivityManager发送startActivity方法到ActivityManagerServer中,然后我们知道知道在ActivityThread类中main方法死循环前开启了个子线程,这个线程会接收ActivityManagerServer回馈的信息,信息封装在Message中然后添加主线程MeassgeQueue中,当主线程的Loop遍历到有信息的时候交给主线程的Handler处理。

  • ActivityManagerServer收到startActivity信息的信息的时候,会发送一个messageHandler处理。其中message.what=LAUNCH_ACTIVITYLAUNCH_ACTIVITY=100

我们看看ActivityHandler在哪

public class AcitvityThread{
    final H mH = new H();
    private class H extends Handler {
    //。。。代码省略
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们在看看mH这个Handler怎么处理private class H extends Handler 的事件

//ActivityThread.java
class H extends Handler{
//.....
public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                   //从msg拿出ActivityClientRecord 对象,里面包含启动activity的Intent
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                            //带着ActivityClientRecord 对象启动activity
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

大致可简单的理解从msg获取ActivityClientRecord 对象,内包含启动Intent启动意图等信息,还记得我们前面保存一个Intent到一个新的Intent上吗?这时候其实你可以从这个Intent取出来了。调用handleLaunchActivity去启动,最后大家想生命周期可以自己往下深究。

//ActivityThread.java
    static final class ActivityClientRecord {
        IBinder token;
        int ident;
        Intent intent;
        String referrer;
        //....
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

先来整理回调流程:
(因为懒省略很多细节)

这里写图片描述

所以我们可以handlermCallback赋值,此时handler回调lancherActivity的时候,我们知道mCallback不为空的时候,先会回调mCallback再根据其返回值在判断要不要对其自己实现的handlerMessagetrue的时候不会调用自己实现的方法)

所以先获取handler对象,而handler对象存在ActivityThread中,再来看看ActivityThread.java对其的声明

//ActivityThread.java
public final class ActivityThread {
 final H mH = new H();
 }
  • 1
  • 2
  • 3
  • 4

因为只mH不是静态类型,所以先要获取ActivityThread 对象才能获取mH实例。
还记得我们前面分析ActivityThreadmain方法吗?
内部调用

public static void main(String[] args) {
//...
 ActivityThread thread = new ActivityThread();
        thread.attach(false);
    //...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

再看看 thread.attach(false);方法

//ActivityThread.java
private void attach(boolean system) {
        sCurrentActivityThread = this;
        //....
        }
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到ActivityThread对象保存在ActivityThread的属性对象sCurrentActivityThread

//ActivityThread.java
public final class ActivityThread {
 //...
  private static ActivityThread sCurrentActivityThread;
//...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

静态属性证明你懂的,你可以利用反射机制获取对象

所以综上代码综合可得:

//APP.java
package com.example.fmy.myapplication;

public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

            //过检验代码省略
            Class<?> activityThread_class = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread_field.setAccessible(true);
            //获取静态属性对象实例
            Object activityThread_instance = sCurrentActivityThread_field.get(null);

            Field mH_field = activityThread_class.getDeclaredField("mH");

            mH_field.setAccessible(true);
            //获取handler对象实例
            Object mH_insance = mH_field.get(activityThread_instance);

            Field mCallback_field = Handler.class.getDeclaredField("mCallback");


            mCallback_field.setAccessible(true);
            //给handler添加mCallback
            mCallback_field.set(mH_insance, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        //得到ActivityClientRecord
                        Object obj = msg.obj;
                        try {
                            //得到Intent对象
                            Field intent_field = obj.getClass().getDeclaredField("intent");
                            intent_field.setAccessible(true);
                            Intent intent = (Intent) intent_field.get(obj);
                            //取出我们前面存在Intent里的原本没有注册在清单文件的Activity的Intent
                            Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);

                            if (target_intent != null) {
                                intent.setComponent(target_intent.getComponent());
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                    return false;

                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

最后整合下过验证的代码如下:

//APP.java
package com.example.fmy.myapplication;

public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            Class<?> ams_class = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ams_class.getDeclaredField("gDefault");
            //因为gDefault是private所以用发射机制打破访问限制
            gDefault.setAccessible(true);
            //得到gDefault实例对象 因为是stati属性所以get传入null即可得到
            Object gDefault_instance = gDefault.get(null);
            //单例工具类class
            Class<?> singleton_class = Class.forName("android.util.Singleton");
            //单例工具类的一个属性对象保存的是IActivityManager对象实例
            Field mInstance = singleton_class.getDeclaredField("mInstance");
            //打破封装访问
            mInstance.setAccessible(true);
            //传入gDefault对象实例得到IActivityManager对象实例
            final Object mInstance_instance = mInstance.get(gDefault_instance);
            //动态代理,这个不会我真不知道讲下去。此方法会返回一个实现IActivityManager接口的实例对象
            //拿着这个对象 替换单例工具类gDefault 的mInstance即可实现
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // IActivityManager接口方法太多 我们只关心它启动activity的接口方法
                    if (method.getName().equals("startActivity")) {
                        //获取调用此方法传入的参数 我们这里只要Intent替换,所以只需要Intent替换
                        for (int i = 0; i < args.length; i++) {
                            Object arg = args[i];
                            if (arg instanceof Intent) {
                                Intent intent = new Intent();
                                //PlaceholdActivity因为在清单文件注册过所以 创建一个新的Intent替换原来的SecondActivity的意图。后面用到
                                // 并将其保存到新的Intent中
                                ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
                                intent.setComponent(componentName);
                                intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
                                args[i] = intent;
                                return method.invoke(mInstance_instance, args);
                            }
                        }

                    }

                    return method.invoke(mInstance_instance, args);
                }
            });
//         替换单例工具类gDefault 的mInstance实现动态代理
            mInstance.set(gDefault_instance, proxyInstance);

            //过检验结束
            Class<?> activityThread_class = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread_field.setAccessible(true);
            //获取静态属性对象实例
            Object activityThread_instance = sCurrentActivityThread_field.get(null);

            Field mH_field = activityThread_class.getDeclaredField("mH");

            mH_field.setAccessible(true);
            //获取handler对象实例
            Object mH_insance = mH_field.get(activityThread_instance);

            Field mCallback_field = Handler.class.getDeclaredField("mCallback");


            mCallback_field.setAccessible(true);
            //给handler添加mCallback
            mCallback_field.set(mH_insance, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        //得到ActivityClientRecord
                        Object obj = msg.obj;
                        try {
                            //得到Intent对象
                            Field intent_field = obj.getClass().getDeclaredField("intent");
                            intent_field.setAccessible(true);
                            Intent intent = (Intent) intent_field.get(obj);
                            //取出我们前面存在Intent里的原本没有注册在清单文件的Activity的Intent
                            Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);

                            if (target_intent != null) {
                                intent.setComponent(target_intent.getComponent());
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                    return false;

                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

GIT源码附上源码连接

KMP next数组讲解

本文只讲解KMP子串keyString(下标用j表示),中和源字符串SoureceString(下标i表示)某次匹配失败后。下次匹配j的取值。
关于KMP算法入门可以看看其他博文(最主要网上关于这个算法写烂了,我这里只记录下我学习不懂的地方)

前言

在学KMP算法的时候一直感觉next数组的实现是让我最头痛的。所以现在回过头写下笔记,方便以后再看。

规则:
1. keyString(子串)的next数组大小为keyString的长度。
2. next第一个元素为-1
3. next数组下标数等于 子串前缀和后缀的匹配数

前缀概念:
子字符串的从第一个元素开始到第x元素 和 字符第y个元素到最后一个字符串相等.(x和y可以相等)

注意 后缀的概念:有个误区 是指子字符串第z个元素的时候 把z最为最后一个元素

eg:
字符串 keyString =“abcabcx”

这里写图片描述

我们可以把.下标为1的元素(即是第二个元素)作为后缀。那么前缀是a。后缀是b
我们把下标为5的元素作为后缀。那么前缀是abc(下标为0,1,2)后缀是abc(下标为 3,4,5)

上面对应next数组
这里写图片描述
当子串到下标为6的位置和源字符串匹配失败的时候,下次匹配直接到下标为3的位置

ok 现在我们开始现实某个字符串next数组的实现,首先写一个方法为get

    //java语言
    // T为子字符串
    // next数组 
    // 当方法结束的时候 next包含对应T的射影数值
    public static void get(String T, int[] next) {
    ....
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们先实现前两个规则

规则:
1. keyString(子串)的next数组大小为keyString的长度。
2.  next第一个元素为-1
  • 1
  • 2
  • 3

如下

//默认传入的next数组大小为子字符串大小 所以实现规则2即可
public static void get(String T, int[] next) {

        int i, j;

        j = -1;
        next[0] = -1;
        i = 0;

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后一个规则:

next数组下标数等于 子串前缀和后缀的匹配数.先看一下next完成的案例在说

算法核心也在这此处,再回头看看如下字符串和对应next数组
这里写图片描述

理解next数组的含义
我们看一下‘x’对应next为6
意味着:
1. 字符串从0到5作为前缀。(为什么不到6?因为数组下标从0开始)
2. 从x下标减6开始到x前面一个数作为后缀。
3. 有6个循环数。
这里写图片描述

先看最终代码(可能会疑惑在回溯部分,先暂时看看后面再解释):

 public static void get(String T, int[] next) {

        int i, j;

        j = -1;
        next[0] = -1;
        i = 0;
        while (i < T.length() - 1) {

            if (j == -1 || T.charAt(i) == T.charAt(j)) {

                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];//回溯
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

关于回溯我们举例说明说明

我们假设数组末尾追加一个字母d(下标为10)

这里写图片描述

假设我们的算法遍历到下标为10 数值为x的元素。
此时

j=6. i = 9
  • 1

此时算法 T.chatAt(i) 不等 T.chatAt(j)所以走else路径

 j = next[j];//回溯
  • 1
  • 将j回溯到next[6]的位置,如果此时相等那么 即T.chatAt(i)== T.chatAt(j)。那么就结束了。不然继续回溯。我当时在想为什么j会回溯到next[j]。原因如下,j代表着有多少个后缀和前缀数相等的数量。那么我们将设 下标为10(数值x)的元素等于下标为6(数值a)的元素 。循环到此的时候j=6代表着在下标为9的元素之前有6个相等数。(0,1,2,3,4,5 和 3,4,5,6,7,8)。那么6 和 9 相等就代表着相同后缀有6个。所以我们只需要判断字符串6和9即可(因为此算法如果前面的数字前缀相等那么会建立再次基础上 进行判断【j= 6 就是一个基础,它代表着 前缀的后面一个数 也代表着前缀和后缀相同数】)。

  • 那么假设不相等的情况的解释:
    继续假设运行到
    我们的算法遍历到下标为10 数值为x的元素。
    此时

j=6. i = 9
  • 1

(0,1,2,3,4,5 和 3,4,5,6,7,8前面判断已经相等),但是数组下标6和9不相等 进行j回溯到next[6]的位置,也就是 j = 3 的位置( next[6] = 3),
j代表某前缀的最后一个数的后面一个数。比如说 j =3 那么前缀就是 0 1 2。我们在我们 j =6 i =9 匹配失败后 你可以让 j = j-1继续匹配 T.chatAt(9),但是这是多余的。

假设 数组下标6 和9 匹配失败 进行 j=j-1
T.chatAt(6) != T.chatAt(9);
j = j-1 = 5;
那么
T.chatAt(5) == T.chatAt(9);
也就是if判断条件成立 可得
0 1 2 3 4 5 和 4 5 6 7 8 9相等.
那么可以得以下结论:
T.chatAt(5) == T.chatAt(9);
T.chatAt(5) == T.chatAt(8);
T.chatAt(8) == T.chatAt(9);
这三个不难推理出

继续
T.chatAt(4) == T.chatAt(8);
T.chatAt(9) == T.chatAt(4);
T.chatAt(9) == T.chatAt(5);

也就是 7==8==9 和 1==2==3

最终你将推出 0==1==2==3==4==5 ==7==8==9
显然这会和next数组原有数组数值发生冲突。顾不能直接j= j-1。当然如果数组前缀和后缀所有元素都相等 时候可以推出next[j] = j -1

AIDL源码分析

前言

本文是本人研究AIDL时候的笔记,包含很多UML图和截图,内容仓促且不包含驱动层分析,如下文有错漏还请指出(容我精通Linux和C++后杀入,很可惜现在太菜)

服务端

  • 首先写一个AIDL文件 如下:
// IMyAidlInterface.aidl
package com.fmy.changevoice.aidl_resource;



interface IMyAidlInterface {

    void test1(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

                String hello( String aString);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 在编译后生成一个java文件(as位于build/generated/source/aidl/debug),文件内容如下:

package com.fmy.changevoice.aidl_resource;

public interface IMyAidlInterface extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.fmy.changevoice.aidl_resource.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.fmy.changevoice.aidl_resource.IMyAidlInterface";


        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }


        public static com.fmy.changevoice.aidl_resource.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.fmy.changevoice.aidl_resource.IMyAidlInterface))) {
                return ((com.fmy.changevoice.aidl_resource.IMyAidlInterface) iin);
            }
            return new com.fmy.changevoice.aidl_resource.IMyAidlInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_test1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.test1(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_hello: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _result = this.hello(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.fmy.changevoice.aidl_resource.IMyAidlInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void test1(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_test1, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String hello(java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_hello, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_test1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void test1(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public java.lang.String hello(java.lang.String aString) throws android.os.RemoteException;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 先看一下类图结构示例图:

这里写图片描述

  • 首先分析IMyAidlInterface这个类(内部类先不分析):
public interface IMyAidlInterface extends android.os.IInterface {
//内部类先不做分析
 public static abstract class Stub extends android.os.Binder implements com.fmy.changevoice.aidl_resource.IMyAidlInterface{...}
//声明接口方法
public void test1(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
//声明接口方法
    public java.lang.String hello(java.lang.String aString) throws android.os.RemoteException;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 我们看到IMyAidlInterface 还继承类一个接口IInterface ,我们再看看这个接口:
package android.os;

public interface IInterface
{


//检索与此接口相关联的Binder对象。您必须使用此代替普通转换,以便代理对象可以返回正确的结果。
    public IBinder asBinder();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 从描述可得治会在代理对象用使用使用此方法。然而IMyAidlInterface 没有实现此方法。我们继续看内部类Stub。
    这里写图片描述

  • 分析1:Stub是一个抽象类。继承Binder和实现IMyAidlInterface接口。
    但是从类图可以看到并没有实现我们IMyAidlInterface的方法(test1和hello方法)。

综上分析,我们服务实现接口的时候我们会继承IMyAidlInterface.Stub类。而不是直接IMyAidlInterface因为此接口还继承了IInterface接口 还必须实现其IBinder asBinder();方法。但是这个方法怎么实现返回我们却不知道。好在Stub实现除了我们自己定义接口方法之外IMyAidlInterface类的所有继承方法。

也就是说IMyAidlInterface我们定义了两个方法test1()和hello()没有被Stub实现,但是IMyAidlInterface其继承IInterface方法我们实现了。

所以我们写一个服务如下:

////MyService.java
package com.fmy.changevoice.aidl_resource;

public class MyService extends Service {



    @Override
    public IBinder onBind(Intent intent) {

        return new MyAidlInterface();
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    class MyAidlInterface extends IMyAidlInterface.Stub {



        @Override
        public void test1(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String hello(String aString) throws RemoteException {
            return null;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 分析2: 我们再看一下我们Stub类实例化的过程.在上面的Myservice中我们在实例化内部类MyAidlInterface 的时候会调用父类无参构造方法。所以我们从Stub的构造方法开始看。
 private static final String DESCRIPTOR = "com.fmy.changevoice.aidl_resource.IMyAidlInterface";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
  • 1
  • 2
  • 3
  • 4
  • 5

构造方法调用attachInterface传入this和一个描述。而attachInterface属于Binder方法
继续跟进

 //将特定接口与Binder关联的便捷方法。
 //调用后,将为您实现queryLocalInterface()
 //返回给定的所有者IInterface时相应的
 //描述符被请求。
    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

DESCRIPTOR 字符串就是一个id一样 用于确定一个接口对应的Binder对象。而Binder对象是IBinder子类用于内存共享实现进程通信.当我们使用IInterface接口的asBinder()方法时候会根据descriptor返回对应IBinder对象

既然上文提到了queryLocalInterface ()那么我们顺便看看这个方法。
这个方法在Binder实现继承自IBinder

//使用提供给attachInterface()的信息来返回
关联的IInterface。
 public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

mOwner对象是Binder属性对象,就是我们attachInterface()传入的。回过头来再看看我们怎么调用attachInterface()的传入实参

 public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
  • 1
  • 2
  • 3

可见调用queryLocalInterface()方法后直接返回如果字符串匹配那么直接返回Stub对象

Stub父类的构造方法Binder如下

public Binder() {
        init();

        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Binder> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    }
  private native final void init();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

和可惜主要的init()方法是native方法,所以暂时到这


客户端

我们连接Myservice这个服务类代码如下

//MainActivity.java
package com.fmy.changevoice.aidl_resource;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = "FMY";

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(TAG, "onStart: ");
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //连接核心代码
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.e(TAG, "onServiceConnected: ");
               //32行 
                IMyAidlInterface.Stub.asInterface(service);

               try {
                    //调用接口中的一个方法. //36行
                    String test = iMyAidlInterface.hello("test");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

我们来先分析一下32行代码:
我们看看调用Stub的方法asInterface

     /**
        将IBinder对象转换为com .fmy. changevoice.aidl_resource.IMyAidlInterface接口,在需要时生成代理。
         */
        public static com.fmy.changevoice.aidl_resource.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            //30行
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.fmy.changevoice.aidl_resource.IMyAidlInterface))) {
                return ((com.fmy.changevoice.aidl_resource.IMyAidlInterface) iin);
            }
            //34行
            return new com.fmy.changevoice.aidl_resource.IMyAidlInterface.Stub.Proxy(obj);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们先分析30行

//30行
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  • 1
  • 2

queryLocalInterface方法在前面我们大致介绍了下,用你传入的DESCRIPTOR检索接口和binder相关的接口实现。如果你的ServiceClient在一个同一个进程那么直接返回本地对象,不需要进程间通信。否则返回空

如果返回本地对象那么后面也没有什么介绍了,我们假设这里ServiceClient不在同一进程。

34行

  return new com.fmy.changevoice.aidl_resource.IMyAidlInterface.Stub.Proxy(obj);
  • 1

可以看到假设不在同进程的时候用Proxy类创建返回IMyAidlInterface接口的实现

我们来大致看看Proxy类

这里写图片描述

ProxyStub静态私有内部类代码如下

   private static class Proxy implements com.fmy.changevoice.aidl_resource.IMyAidlInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void test1(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_test1, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public String hello(String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_hello, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

我们先把精力放在Proxy构造方法上

  private static class Proxy implements com.fmy.changevoice.aidl_resource.IMyAidlInterface {
            private android.os.IBinder mRemote;
            //当Client和Service不在同进程的时候 传入Stub
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可见当ClientSerivice不在同进程的时候 ,Stub把自身传给Proxy构造对象 然后返回接口实例。
Stub因为继承自Binder可以把一些数据写入到共享内存中,所以保存此对象的一个引用。而proxy是我们接口的实现。

自此我们分析完成了
IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);这句代码

接下来我们看下用返回实例对象调用接口方法:

  Log.e(TAG, "onServiceConnected: ");
                //32
                IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

                try {
                    //调用接口中的一个方法. //36行
                    String test = iMyAidlInterface.hello("test");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 36行分析开始:
  String test = iMyAidlInterface.hello("test");
  • 1

我们知道aidl连接对象当客户端服务端不在同一进程的时候 ,在客户端返回的接口实例是Stub内部类Proxy的实例。

所以上面的调用 hell(“test”);的源码我们直接从Proxyhell()开始看

hell() 是我们最开始定义的两个方法的其中一个,另外一个是test1()

Proxy hell方法

这里写图片描述

上图已给出行号

先解释一个类android.os.Parcel 这个类运行在内存中传递序列化后的数据。Parcelable这类相比大家都用过。Parcelable内部就是封装了Parcel 对象而已。

Serializable是java提供的序列化,Parcel是google提供。更轻量并且在内存传输胜过Serializable

122-123获取parcel池获取对象实例,

   android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
  • 1
  • 2

_data 用于写入数据,reply用于接受数据。

124行接收结果。因为我们hello()返回值是String所以他自然也是String。

写入token。DESCRIPTOR就是我们前面说过确定Binder和哪个接口关联的一个ID。这里很明显。你要告诉我你要写个哪个Binder我才能确定 传个哪个接口。

 _data.writeInterfaceToken(DESCRIPTOR);
  • 1

127行

//写入参数
 _data.writeString(aString);
  • 1
  • 2

128行 放入Stub对象transact方法中 然后写入共享内存

mRemote.transact(Stub.TRANSACTION_hello, _data, _reply, 0);
  • 1

其中第一个参数 final的int值。是用于确定你调用哪个接口方法。每个定义接口方法都有一个对应的final值。其声明在Stub类中
本案例中
这里写图片描述

继续分析。。。。。。。
mRemote.transact()客户端的实现类是final class BinderProxy implements IBinder {。。。}
BinderProxyBinder 两个都是IBinder的实现类

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
        if (Binder.isTracingEnabled()) { Binder.getTransactionTracker().addTrace(); }
        return transactNative(code, data, reply, flags);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

后面是JNI代码有兴趣的同学可以继续往下学习。
在这之后我们会回调到服务端的StubonTransact()方法

@Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_test1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.test1(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_hello: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _result = this.hello(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

上面的代码不需要介绍太多了相信代价都看得懂。

这里写图片描述
看完这些我们再来看看bindService的执行过程。这里我直接贴出时序图给大家看,不做过多介绍这里写图片描述

Activity的实现类为ContextImpl。你也许在继承类图看不到这个类的,但是你可以看到Activity的父类ContextWrapper的构造方法传入的一个对象实例是什么。
这里写图片描述

安卓7.1 新特性Shortcut

介绍

Shortcut 是谷歌在API25提出来的 类似苹果3D touch 但是没有压力感应.在安卓中完全就是长按.
来看下效果吧:
这里写图片描述

是不是很赞? 那么请随本文一起学习吧

更新

  1. 新建项目 在你项目下的build.gradle下

    以下目的很简单更新你编译工具 和指定项目版本

    1. compileSdkVersion 25
    2. buildToolsVersion “25.0.0”
    3. minSdkVersion 25
    4. targetSdkVersion 25
  2. 更新platform-tools 到25
    打开SDK Manager
    这里写图片描述
    如果你的Android SDK Platform-tools小于25那么请勾选然后点右下角更新

静态写法

静态写法?说白了和BroadcastReceiver(广播接受者)一样 .一个在清单文件中注册广播我们称为静态,用代码注册称为动态

  1. 在res创建xml文件夹
    这里写图片描述

  2. 在res/xml下新建一个文件命名为my_shortcut.xml字符串貌似必须引用方法比如@string/xxxx
    具体内容

    <shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
        <shortcut
            android:shortcutId="settings"
            android:enabled="true"
            android:icon="@mipmap/ic_launcher"
            android:shortcutShortLabel="@string/my_short"
            android:shortcutLongLabel="@string/my_long"
            android:shortcutDisabledMessage="@string/my_disable">
            <intent
                android:action="android.intent.action.VIEW"
                android:targetPackage="com.example.administrator.myapplication"
                android:targetClass="com.example.administrator.myapplication.MainActivity" />
            <intent
                android:action="android.intent.action.VIEW"
                android:targetPackage="com.example.administrator.myapplication"
                android:targetClass="com.example.administrator.myapplication.SettingsActivity" />
            <categories android:name="android.shortcut.conversation"/>
        </shortcut>
    </shortcuts>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    参数说明

    shortcut 属性说明:
    android:shortcutId 就是一个id标志 后面动态注册会讲到
    android:enabled 是否可用 如果不可用那么将不显示此快捷
    android:shortcutShortLabel 快捷短名:大家注意到一开始的效果图没?快捷是可以脱出来在变成一个桌面快捷方式图标.那么此图标的名字就是这个
    android:shortcutLongLabel :快捷长名 长按下图标弹出来列表框中每个快捷名
    android:shortcutDisabledMessage: 当快捷不可用时用户点击会提示此文字 后面动态会详细说明
    intent属性说明:

    假设1:shortcut (看清楚不是shorcuts 没有s哦)下只有一个intent 那么结果:用户点击此快捷用户跳转到intent制定的activity

    假设2:shortcut 下有两个intent 我们按照顺序命名为intent1intent2 那么用户点击快捷的时候将会跳转到intent2 此时 若用户按下back键(返回键) 那么将会跳转到intent1的界面

    categories 属性说明
    反正就一个值就是上面写的 写死即可

  3. 在清单文件注册
    注意一个小坑:注册信息必须要在activity为启动项的activity的根标签注册写下<meta-data>

     <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
                <meta-data
                    android:name="android.app.shortcuts"
                    android:resource="@xml/my_shortcut"/>
            </activity>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    做法如下:
    下面少打错了”android.app.shortcuts” 下面少打了个s (电脑实在太卡了,不想重录)注意!!!!!!!!
    这里写图片描述

  4. 效果展示:
    这里写图片描述

  5. 小知识点
    假如:你打开快捷item的程序所在的应用已经有多个activity在回退栈 请猜猜会怎么样?这里留给读者自行尝试..哪怕什么都没有反生你也可以增加记忆嘛

动态写法 -添加

特点和广播接受者一样灵活
核心代码(本例只要点击”创建”按钮会执行下面方法生成快捷):

 //动态添加
    public void onclick2(View view) {
        mShortcutManager = getSystemService(ShortcutManager.class);
        List<ShortcutInfo> infos = new ArrayList<>();
        //快捷最多只能有5个
        // getMaxShortcutCountPerActivity只能返回5
        for (int i = 0; i < mShortcutManager.getMaxShortcutCountPerActivity(); i++) {
            Intent intent = new Intent(this, SettingsActivity.class);
            intent.setAction(Intent.ACTION_VIEW);
            Intent intent2 = new Intent();
            intent2.setAction("fmy.fmy");
            intent2.setClassName(getPackageName(),getPackageName()+".MainActivity.java");
            Intent[] intents = new Intent[2];
            //开始点击快捷时跳进此 返回键跳入intent2 其他类似
            intents[0]=intent;
            intents[1]=intent2;
                //第一个参数 上下文
                //第二个参数id嘛            
            ShortcutInfo info = new ShortcutInfo.Builder(this, "id" + i)
                    .setShortLabel("短的名字"+i+"")
                    .setLongLabel("长的名字:" + i+"")
                    .setIcon(Icon.createWithResource(this, R.mipmap.ic_launcher))
//                   .setIntent(intent)
                    .setIntents(intents)
                    .build();
            infos.add(info);

        }

        mShortcutManager.setDynamicShortcuts(infos);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

效果:

解析:下图我一开始没有点击”创建”按钮 直接在桌面长按按钮发现没有任何反应.然后进入程序按下创建按钮并返回桌面发现可以长按点出快捷
这里写图片描述

注意和静态写法一起的坑(算本人经验吧):

那些年我们一起踩过的坑—>>上面的代码会动态创建5个快捷点击item.但是如果你此时静态写一个了快捷item那么恭喜你见红了(出现异常)

Caused by: java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded

解决:先获取其已有的快捷item数量然后要么移除原来的,要么减少你创建.或者更新 这就是我为什么知道只能创建5个原因.解决方法读者看完 “动态写法-更新(覆盖)”和”动态写法-删除”自然会明白,如果想先解决问题那么请直接拷贝 更新 和 删除 中部分核心代码

动态写法 -更新(覆盖)

如果你想某些时候改变某些快捷item的名字或者意图(intent)那么请参照以下代码

 public void onclick3(View view) {
        Intent intent2 = new Intent();
        intent2.setAction("fmy.fmy");
        intent2.setClassName(getPackageName(),getPackageName()+".MainActivity.java");
//设置id为id1 会覆盖原来快捷item为id为id1的快捷
//如果没有则什么都不会发生
        ShortcutInfo info = new ShortcutInfo.Builder(this,"id1")
                .setIntent(intent2)
                .setLongLabel("动态更新的长名")
                .setShortLabel("动态更新的短名")
                .build();
        mShortcutManager = getSystemService(ShortcutManager.class);
        List<ShortcutInfo> dynamicShortcuts = mShortcutManager.getDynamicShortcuts();
        mShortcutManager.updateShortcuts(Arrays.asList(info));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

效果:
这里写图片描述

动态写法 -删除(不可用)

下面一小段描述转载的(我不想写,再说次作者写这个描述非常不错此段描述原作者地址)
我们先来介绍一个名词-Pinning Shortcuts, 这是个啥玩意呢? 其实对于Shortcut, Android是允许我们直接放到桌面的, 这样就更加方便了用户的操作, google把他称作为Pinning Shortcuts, 具体啥样, 我们来张图就明白了.
这里写图片描述
对于这个Pinning Shortcuts, google的文档说, 我们开发者是没有权利去删除的, 能删除它的只有用户. 那我该项功能删除了咋办? 这东西还在桌面上, 是不是APP要崩? 当然Google考虑到了这点, 它允许我们去disable这个shortcut. 让其变为灰色 用户点击时提示个小土司
好了引用结束 感谢原作者

我们在桌面长按拖出来的快捷item到桌面 这个item对象为ShortcutInfo
代码是最好的老师:

//删除
    public void onclick(View view) {

        mShortcutManager = getSystemService(ShortcutManager.class);
        //获取所有被拉取出来的快捷item(如果一个item都没有被拉出那么返回长度为0)
        List<ShortcutInfo> infos = mShortcutManager.getPinnedShortcuts();
        //遍历所有的被拉出的item 然后让其变成灰色不可点击
        for (ShortcutInfo info :
                infos ) {
            //此时被拉出的快捷item 变为灰色 用户再点击 会弹出吐司内容为第二个参数 "不可点击哦"
           // 此时桌面长按原程序图标弹出的快捷列表已经没有了
            mShortcutManager.disableShortcuts(Arrays.asList(info.getId()), "不可点击哦");
            List<ShortcutInfo> dynamicShortcuts = mShortcutManager.getDynamicShortcuts();
            Log.e("TAG","大小"+dynamicShortcuts.size());
            //虽然不可见但是你 依然要移除在动态添加列表里的东西 不过我调用disableShortcuts
            // 后发现其大小变了.内部应该调用此方法了.由于电脑太卡没下载源码 所以保险起见写上吧
            mShortcutManager.removeDynamicShortcuts(Arrays.asList(info.getId()));

        }


        List<ShortcutInfo> dynamicShortcuts = mShortcutManager.getDynamicShortcuts();
        Log.e("TAG","大小"+dynamicShortcuts.size());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

小知识点

  1. 用户删除数据时 被拖出来快捷item会被删除
  2. 用户删除数据时 动态创建的item 你在桌面在长按程序图标也没有 需要重新写入
  3. 用户卸载时被拖出来快捷item会被删除

关于这篇博文

我偶然看到这个7.1新特性 于是一直在找学习资料.然后想写下 期间看了几篇文章 并结合自己体会写下来.这篇博客用到模拟器要用SDK manager 下载镜像 因为只有它有7.1镜像 genymotion 最新的也就只有7.0 而已.运行谷歌自带镜像及其耗费内存 我就4G内存 开完stuio和博客和模拟器 内存只剩下80mb 卡的程度可想而知.但是一直想写一篇高质量的博文.于是硬着头皮卡了5个小时写下了.由于时间仓促错漏在所难免,由于卡到不行不敢点击源码去看 而且我也没下载.如果以上文字对你有那么一点带你帮助 将是我最大的欣慰;

自定义view实现水波纹效果

今天看到一篇自定view 实现水波纹效果 觉得真心不错 学习之后再次写下笔记和心得.但是感觉原作者写得有些晦涩难懂,也许是本人愚笨 所以重写此作者教程.原作者博文大家可以去看下,感觉他在自定义view方面非常厉害,本文是基于此作者原文重新改写,拥有大量像相似部分

先看下效果吧:
1. 效果1:
这里写图片描述
2. 效果2
这里写图片描述


我先们来学习效果1:

效果1实现本质:用一张波形图和一个圆形图的图片,然后圆形图波形图上方,然后使用安卓的图片遮罩模式desIn(不懂?那么先记住有这样一个遮罩模式).(只显示上部图像和下部图像公共部分的下半部分),是不是很难懂?那么我在说清一点并且配图.假设圆形图波形图上面,那么只会显示两者相交部分的波形图
下面是解释效果图(正方形蓝色图片在黄色圆形上面):
这里写图片描述

学习此模式具体地址学习安卓图片遮罩模式

这里写图片描述

所用到波形图:
这里写图片描述

所用到圆形图:

这里写图片描述

这次的实现我们都选择继承view,在实现的过程中我们需要关注如下几个方法:

1.onMeasure():最先回调,用于控件的测量;

2.onSizeChanged():在onMeasure后面回调,可以拿到view的宽高等数据,在横竖屏切换时也会回调;

3.onDraw():真正的绘制部分,绘制的代码都写到这里面;

先来看看我们定义的变量:


    //波形图
    Bitmap waveBitmap;

    //圆形遮罩图
    Bitmap circleBitmap;

    //波形图src
    Rect waveSrcRect;
    //波形图dst
    Rect waveDstRect;

    //圆形遮罩src
    Rect circleSrcRect;

    //圆形遮罩dst
    Rect circleDstRect;

    //画笔
    Paint mpaint;

    //图片遮罩模式
    PorterDuffXfermode mode;

    //控件的宽
    int viewWidth;
    //控件的高
    int viewHeight;

    //图片过滤器
    PaintFlagsDrawFilter paintFlagsDrawFilter ;

    //每次移动的距离
    int speek = 10 ;

    //当前移动距离
    int nowOffSet;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

介绍一个方法:

 void android.graphics.Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
  • 1

此方法的参数:

参数1:你的图片

参数2:矩形 .也就是说此矩形决定你画出图片参数1 的哪个位置,比如说你的矩形是设定是Rect rect= new Rect(0,0,图片宽,图片高) 那么将会画出图片全部

参数3:矩形.决定你图片缩放比例和在view中的位置.假设你的矩形Rect rect= new Rect(0,0,100,100) 那么你将在自定义view中(0,0)点到(100,100)绘画此图片并且如果图片大于(小于)此矩形那么按比例缩小(放大)

来看看 初始化方法

//初始化
    private void init() {

        mpaint = new Paint();
        //处理图片抖动
        mpaint.setDither(true);
        //抗锯齿
        mpaint.setAntiAlias(true);
        //设置图片过滤波
        mpaint.setFilterBitmap(true);
        //设置图片遮罩模式
        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

        //给画布直接设定参数
        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);

        //初始化图片
        //使用drawable获取的方式,全局只会生成一份,并且系统会进行管理,
        //而BitmapFactory.decode()出来的则decode多少次生成多少张,务必自己进行recycle;

        //获取波形图
        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();

        //获取圆形遮罩图
        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();

        //不断刷新波形图距离 读者可以先不看这部分内容  因为需要结合其他方法
        new Thread(){
            public void run() {
                while (true) {
                    try {
                        //移动波形图
                        nowOffSet=nowOffSet+speek;
                        //如果移动波形图的末尾那么重新来
                        if (nowOffSet>=waveBitmap.getWidth()) {
                            nowOffSet=0;
                        }
                        sleep(30);
                        postInvalidate();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            };
        }.start();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

以下获取view的宽高并设置对应的波形图和圆形图矩形(会在onMesure回调后执行)

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取view宽高
        viewWidth = w;
        viewHeight = h ;

        //波形图的矩阵初始化
        waveSrcRect = new Rect();
        waveDstRect = new Rect(0,0,w,h);

        //圆球矩阵初始化
        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());

        circleDstRect = new Rect(0,0,viewWidth,viewHeight);


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

那么最后来看看绘画部分吧

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);




        //给图片直接设置过滤效果
        canvas.setDrawFilter(paintFlagsDrawFilter);
        //给图片上色
        canvas.drawColor(Color.TRANSPARENT);
        //添加图层 注意!!!!!使用图片遮罩模式会影响全部此图层(也就是说在canvas.restoreToCount 所有图都会受到影响) 
        int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);
        //画波形图部分 矩形
        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);
        //画矩形
        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);
        //设置图片遮罩模式
        mpaint.setXfermode(mode);
        //画遮罩
        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);
        //还原画笔模式
        mpaint.setXfermode(null);
        //将图层放上
        canvas.restoreToCount(saveLayer);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

最后看下完整的代码

package com.fmy.shuibo1;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.icu.text.TimeZoneFormat.ParseOption;
import android.util.AttributeSet;
import android.view.View;

public class MySinUi extends View{


    //波形图
    Bitmap waveBitmap;

    //圆形遮罩图
    Bitmap circleBitmap;

    //波形图src
    Rect waveSrcRect;
    //波形图dst
    Rect waveDstRect;

    //圆形遮罩src
    Rect circleSrcRect;

    //圆形遮罩dst
    Rect circleDstRect;

    //画笔
    Paint mpaint;

    //图片遮罩模式
    PorterDuffXfermode mode;

    //控件的宽
    int viewWidth;
    //控件的高
    int viewHeight;

    //图片过滤器
    PaintFlagsDrawFilter paintFlagsDrawFilter ;

    //每次移动的距离
    int speek = 10 ;

    //当前移动距离
    int nowOffSet;

    public MySinUi(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();

    }

    //初始化
    private void init() {

        mpaint = new Paint();
        //处理图片抖动
        mpaint.setDither(true);
        //抗锯齿
        mpaint.setAntiAlias(true);
        //设置图片过滤波
        mpaint.setFilterBitmap(true);
        //设置图片遮罩模式
        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

        //给画布直接设定参数
        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);

        //初始化图片
        //使用drawable获取的方式,全局只会生成一份,并且系统会进行管理,
        //而BitmapFactory.decode()出来的则decode多少次生成多少张,务必自己进行recycle;

        //获取波形图
        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();

        //获取圆形遮罩图
        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();

        //不断刷新波形图距离 读者可以先不看这部分内容  因为需要结合其他方法
        new Thread(){
            public void run() {
                while (true) {
                    try {
                        //移动波形图
                        nowOffSet=nowOffSet+speek;
                        //如果移动波形图的末尾那么重新来
                        if (nowOffSet>=waveBitmap.getWidth()) {
                            nowOffSet=0;
                        }
                        sleep(30);
                        postInvalidate();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            };
        }.start();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);




        //给图片直接设置过滤效果
        canvas.setDrawFilter(paintFlagsDrawFilter);
        //给图片上色
        canvas.drawColor(Color.TRANSPARENT);
        //添加图层 注意!!!!!使用图片遮罩模式会影响全部此图层(也就是说在canvas.restoreToCount 所有图都会受到影响) 
        int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);
        //画波形图部分 矩形
        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);
        //画矩形
        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);
        //设置图片遮罩模式
        mpaint.setXfermode(mode);
        //画遮罩
        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);
        //还原画笔模式
        mpaint.setXfermode(null);
        //将图层放上
        canvas.restoreToCount(saveLayer);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取view宽高
        viewWidth = w;
        viewHeight = h ;

        //波形图的矩阵初始化
        waveSrcRect = new Rect();
        waveDstRect = new Rect(0,0,w,h);

        //圆球矩阵初始化
        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());

        circleDstRect = new Rect(0,0,viewWidth,viewHeight);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

学习效果2:

此方法实现原理:运用三角函数画出两个不同速率正弦函数图

我们先来复习三角函数吧

正余弦函数方程为:
y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

w:周期就是一个完整正弦曲线图此数值越大sin的周期越小 (cos越大)
如下图:
这里写图片描述
(原作者说我们画一个以自定义view的宽度为周期的图:意思是说你view的宽度正好可以画一个上面的图.)

这里写图片描述

A:振幅两个山峰最大的高度.如果A越大两个山峰越高和越低

h:你正弦曲线和y轴相交点.(影响正弦图初始高度的位置)

b:初相会让你图片向x轴平移

具体大家可以百度学习,我们在学编程,不是数学


为什么要两个正弦图画?好看…..
先来看看变量:

// 波纹颜色
    private static final int WAVE_PAINT_COLOR = 0x880000aa;

    // 第一个波纹移动的速度
    private int oneSeep = 7;

    // 第二个波纹移动的速度
    private int twoSeep = 10;

    // 第一个波纹移动速度的像素值
    private int oneSeepPxil;
    // 第二个波纹移动速度的像素值
    private int twoSeepPxil;

    // 存放原始波纹的每个y坐标点
    private float wave[];

    // 存放第一个波纹的每一个y坐标点
    private float oneWave[];

    // 存放第二个波纹的每一个y坐标点
    private float twoWave[];

    // 第一个波纹当前移动的距离
    private int oneNowOffSet;
    // 第二个波纹当前移动的
    private int twoNowOffSet;

    // 振幅高度
    private int amplitude = 20;

    // 画笔
    private Paint mPaint;

    // 创建画布过滤
    private DrawFilter mDrawFilter;

    // view的宽度
    private int viewWidth;

    // view高度
    private int viewHeight;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

画初始的波形图并且保存到数组中

// 大小改变
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 获取view的宽高
        viewHeight = h;
        viewWidth = w;

        // 初始化保存波形图的数组
        wave = new float[w];
        oneWave = new float[w];
        twoWave = new float[w];

        // 设置波形图周期
        float zq = (float) (Math.PI * 2 / w);

        // 设置波形图的周期
        for (int i = 0; i < viewWidth; i++) {
            wave[i] = (float) (amplitude * Math.sin(zq * i));
        }


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

初始化各种

// 初始化
    private void init() {
        // 创建画笔
        mPaint = new Paint();
        // 设置画笔颜色
        mPaint.setColor(WAVE_PAINT_COLOR);
        // 设置绘画风格为实线
        mPaint.setStyle(Style.FILL);
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 设置图片过滤波和抗锯齿
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

        // 第一个波的像素移动值 换算成手机像素值让其在各个手机移动速度差不多
        oneSeepPxil = dpChangPx(oneSeep);

        // 第二个波的像素移动值
        twoSeepPxil = dpChangPx(twoSeep);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
// 绘画方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);

        oneNowOffSet =oneNowOffSet+oneSeepPxil;

        twoNowOffSet = twoNowOffSet+twoSeepPxil;

        if (oneNowOffSet>=viewWidth) {
            oneNowOffSet = 0;
        }
        if (twoNowOffSet>=viewWidth) {
            twoNowOffSet = 0;
        }
        //此方法会让两个保存波形图的 数组更新 头到NowOffSet变成尾部,尾部的变成头部实现动态移动
        reSet();

        Log.e("fmy", Arrays.toString(twoWave));

        for (int i = 0; i < viewWidth; i++) {

            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);
            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);
        }

        postInvalidate();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

来看看能让两个数组重置的


    public void reSet() {
        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)
        int one = viewWidth - oneNowOffSet;
        // 把未走过的波纹放到最前面 进行重新拼接
        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);
        // 把已走波纹放到最后
        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);

        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)
        int two = viewWidth - twoNowOffSet;
        // 把未走过的波纹放到最前面 进行重新拼接
        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);
        // 把已走波纹放到最后
        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

最后大家看下完整代码

package com.exam1ple.myshuibo2;

import java.util.Arrays;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.icu.text.TimeZoneFormat.ParseOption;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;

public class MyUi2 extends View {

    // 波纹颜色
    private static final int WAVE_PAINT_COLOR = 0x880000aa;

    // 第一个波纹移动的速度
    private int oneSeep = 7;

    // 第二个波纹移动的速度
    private int twoSeep = 10;

    // 第一个波纹移动速度的像素值
    private int oneSeepPxil;
    // 第二个波纹移动速度的像素值
    private int twoSeepPxil;

    // 存放原始波纹的每个y坐标点
    private float wave[];

    // 存放第一个波纹的每一个y坐标点
    private float oneWave[];

    // 存放第二个波纹的每一个y坐标点
    private float twoWave[];

    // 第一个波纹当前移动的距离
    private int oneNowOffSet;
    // 第二个波纹当前移动的
    private int twoNowOffSet;

    // 振幅高度
    private int amplitude = 20;

    // 画笔
    private Paint mPaint;

    // 创建画布过滤
    private DrawFilter mDrawFilter;

    // view的宽度
    private int viewWidth;

    // view高度
    private int viewHeight;

    // xml布局构造方法
    public MyUi2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    // 初始化
    private void init() {
        // 创建画笔
        mPaint = new Paint();
        // 设置画笔颜色
        mPaint.setColor(WAVE_PAINT_COLOR);
        // 设置绘画风格为实线
        mPaint.setStyle(Style.FILL);
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 设置图片过滤波和抗锯齿
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

        // 第一个波的像素移动值 换算成手机像素值让其在各个手机移动速度差不多
        oneSeepPxil = dpChangPx(oneSeep);

        // 第二个波的像素移动值
        twoSeepPxil = dpChangPx(twoSeep);
    }

    // 绘画方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);

        oneNowOffSet =oneNowOffSet+oneSeepPxil;

        twoNowOffSet = twoNowOffSet+twoSeepPxil;

        if (oneNowOffSet>=viewWidth) {
            oneNowOffSet = 0;
        }
        if (twoNowOffSet>=viewWidth) {
            twoNowOffSet = 0;
        }
        reSet();

        Log.e("fmy", Arrays.toString(twoWave));

        for (int i = 0; i < viewWidth; i++) {

            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);
            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);
        }

        postInvalidate();
    }

    public void reSet() {
        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)
        int one = viewWidth - oneNowOffSet;
        // 把未走过的波纹放到最前面 进行重新拼接
        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);
        // 把已走波纹放到最后
        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);

        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)
        int two = viewWidth - twoNowOffSet;
        // 把未走过的波纹放到最前面 进行重新拼接
        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);
        // 把已走波纹放到最后
        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);


    }

    // 大小改变
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 获取view的宽高
        viewHeight = h;
        viewWidth = w;

        // 初始化保存波形图的数组
        wave = new float[w];
        oneWave = new float[w];
        twoWave = new float[w];

        // 设置波形图周期
        float zq = (float) (Math.PI * 2 / w);

        // 设置波形图的周期
        for (int i = 0; i < viewWidth; i++) {
            wave[i] = (float) (amplitude * Math.sin(zq * i));
        }


    }

    // dp换算成px 为了让移动速度在各个分辨率的手机的都差不多
    public int dpChangPx(int dp) {
        DisplayMetrics metrics = new DisplayMetrics();
        ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
        return (int) (metrics.density * dp + 0.5f);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174

以上源代码:`
源码奉上各位

自定义view实现阻尼效果的加载动画

效果:
这里写图片描述

>

需要知识:
1. 二次贝塞尔曲线
2. 动画知识
3. 基础自定义view知识

先来解释下什么叫阻尼运动

阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动、衰减振动。[1] 不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来。这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运动.阻尼振动系统属于耗散系统。这里的阻尼是指任何振动系统在振动中,由于外界作用或系统本身固有的原因引起的振动幅度逐渐下降的特性,以及此一特性的量化表征。
这里写图片描述

本例中文字部分凹陷就是这种效果,当然这篇文章知识带你简单的使用.

跳动的水果效果实现

剖析:从上面的效果图中很面就是从顶端向下掉落然后再向上 期间旋转即可.
那么我们首先自定义一个view继承FrameLayout

public class My extends FrameLayout {

    public My(Context context) {
        super(context);
    }

    public My(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

需要素材如下三张图片:

这里写图片描述
这里写图片描述
这里写图片描述

也许有人会问我看到你效果图到顶部或者底部就变成向上或者向下了.你三张够吗?
答:到顶部或者底部旋转180度即可
我们现在自定义中定义几个变量


    //用于记录当前图片使用数组中的哪张
    int indexImgFlag = 0;

    //下沉图片 前面三个图片的id
    int allImgDown [] = {R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};

    //动画效果一次下沉或上弹的时间 animationDuration*2=一次完整动画时间
    int animationDuration = 1000;

    //弹起来的图片
    ImageView iv;

    //图片下沉高度(即从最高点到最低点的距离)
    int downHeight = 2;
    //掉下去的动画
    private Animation translateDown;
    //弹起动画
    private Animation translateUp;
    //旋转动画
    private ObjectAnimator rotation;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我们再来看看初始化动画的方法(此方法使用了递归思想,实现无限播放动画,大家可以看看哪里不理解)

 //初始化弹跳动画
    public void  MyAnmation(){
        //下沉效果动画
        translateDown = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
                Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);
        translateDown.setDuration(animationDuration);
        //设置一个插值器 动画将会播放越来越快 模拟重力
        translateDown.setInterpolator(new AccelerateInterpolator());


        //上弹动画
        translateUp = new TranslateAnimation(
         Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
          Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0
       );

        translateUp.setDuration(animationDuration);
        ////设置一个插值器 动画将会播放越来越慢 模拟反重力
        translateUp.setInterpolator(new DecelerateInterpolator());


        //当下沉动画完成时播放启动上弹
        translateDown.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                iv.setImageResource(allImgDown[indexImgFlag]);
                rotation = ObjectAnimator.ofFloat(iv, "rotation", 180f, 360f);
                rotation.setDuration(1000);
                rotation.start();
            }


            @Override
            public void onAnimationEnd(Animation animation) {

                iv.startAnimation(translateUp);


            }


            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        //当上移动画完成时 播放下移动画
        translateUp.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                indexImgFlag = 1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;
                iv.setImageResource(allImgDown[indexImgFlag]);
                rotation = ObjectAnimator.ofFloat(iv, "rotation", 0.0f, 180f);
                rotation.setDuration(1000);

                rotation.start();
            }


            @Override
            public void onAnimationEnd(Animation animation) {
                //递归
                iv.startAnimation(translateDown);

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

以上代码知识:

插值器:会让一个动画在播放时在某一时间段加快动画或者减慢

//设置一个插值器 动画将会播放越来越快 模拟重力
1. translateDown.setInterpolator(new AccelerateInterpolator());
这个插值器 速率表示图:
这里写图片描述

可以从斜率看到使用此插值器 动画将越来越快.意义在于模仿下落时重力的影响

////设置一个插值器 动画将会播放越来越慢 模拟反重力
2. translateUp.setInterpolator(new DecelerateInterpolator());
速率图:
这里写图片描述

最后我们初始化下图片控件到我们的自定义view

private void init() {

    //初始化弹跳图片 控件
    iv = new ImageView(getContext());

    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);

    iv.setLayoutParams(params);
    iv.setImageResource(allImgDown[0]);


    this.addView(iv);

    iv.measure(0,0);

    iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

            if (!flagMeure)
            {
                flagMeure =true;
                //由于画文字是由基准线开始
                path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());

                //计算最大弹力
                maxElasticFactor = (float) (textHeight / elastic);
                //初始化贝塞尔曲线
                path.rQuadTo(textWidth / 2, 0, textWidth, 0);

                //初始化上弹和下沉动画
                MyAnmation();

                iv.startAnimation(translateDown);
            }
        }
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

}

上面的知识:
1. iv.measure(0,0);主动通知系统去测量此控件 不然iv.getwidth = 0;
//下面这个是同理 等iv测量完时回调 不然iv.getwidth = 0;
2. iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){

}

原因:TextView tv = new TextView() 或者 LayoutInflat 填充布局都是
异步所以你在new出来或者填充时直接获取自然返回0

到现在为止你只需要在自定义view 的onSizeChanged回调方法中调用init()即可看到动画的弹动

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        init();
    }
  • 1
  • 2
  • 3
  • 4
  • 5

此方法会在onmesure方法执行完成后回调 这样你就可以在此方法获得自定义view的宽高了

效果图:
这里写图片描述


画文字成u形

首先你得知道如下知识

贝塞尔曲线:具体学习

这里我们用到2此贝塞尔曲线
我们看看大概是什么叫2次贝塞尔曲线

这里写图片描述
我们看看 三个点 p0 p1 p2 我们 把p0 称为 开始点 p1 为控制点 p2 结束点,那么可以用贝塞尔公式画出如图曲线

这里写图片描述

这里大家没必要深究怎么画出来. 也不需要你懂 这个要数学基础的

那么我们在安卓中怎么画呢?

 Path path = new Path();
//p0的x y坐标
path.moveTo(p0.x,y);
path.rQuadTo(p1.x,p1.y,p2.x,p2.y);
  • 1
  • 2
  • 3
  • 4

这就是API调用方法是不是很简单?那么你又会问那么怎么画出来呢?
很简单在 dispatchDraw方法 或者onDraw中 调用

  @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawPath(path,paint);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

那么你画出来的效果应该和在Ps用钢笔画出来的差不多 ps中钢笔工具就是二次贝塞尔曲线
这里写图片描述
(借用下图片)

如果你的三个点的位置如刚开的图片 p0 p1 p2 (p1在p0右上方并且 p1在p2左上方)一样那么在屏幕中的显示效果如下
这里写图片描述
这里随扩张下dispatchDraw和ondraw的区别
如果你的自定义view 是继承view 那么 会先调用 ondraw->>dispatchDraw
如果你的自定义view 是继承viewgroup那么会跳过ondraw方法直接调用dispathchDraw这里特别注意!!我们这个案例中继承的是FrameLayout,
而frameLayout又是继承自viewgroup所以….

那么我们回到主题 如何画一个U形文字?简单就是说按照我们画出来的曲线在上面写字 如: 文字是”CSDN开源中国” 如何让这几个字贴着我们的曲线写出来?
这里介绍一个API
canvas.drawTextOnPath()
这里写图片描述
第一个参数:文字 类型为字符串
第二个参数:路径 也就是我们前面的二次贝塞尔曲线
第三个参数:沿着路径文字开始的位置 说白了偏移量
第四个参数:贴着路径的高度的偏移量

hOffset:
The distance along the path to add to the text’s starting position
vOffset:
The distance above(-) or below(+) the path to position the text

//ok我们看看他可以画出什么样子的文字
这里写图片描述

这种看大家对贝塞尔曲线的理解,你理解的越深那么你可以画出的图像越多,当然不一定要用贝塞尔曲线


确定贝塞尔曲线的起点

我们在回过头来看看我们的效果图

这里写图片描述

我们可以看到文字应该是在iv(弹跳的图片中央位置且正好在 iv弹到底部的位置)

这里我们先补充知识在考虑计算

我们来学习一下文字的测量我们来看幅图
这里写图片描述

我们调用画文字的API时
canvas.drawTextOnPath或者canvas.drawText 是从基准线开始画的也就是说途中的baseline开始画.
如:
canvas.drawText(“FMY”,0,0,paint);
那么你将看不到文字 只能在屏幕看到文字底部如下图:
这里写图片描述

另一个同理API drawTextOnPath 也是

再看看几个简单的API
1 . paint.measureText(“FMY”);返回在此画笔paint下写FMY文字的宽度
下面的API会把文字的距离左边left 上边top 右边right 底部的bottom的值写入此矩形 那么
rect.right-rect.left=文字宽度
rect.bottom-rect.top=文字高度

  //矩形
 Rect   rect = new Rect();
 //将文字画入矩形目的是为了测量高度
 paint.getTextBounds(printText, 0, printText.length(), rect);
  • 1
  • 2
  • 3
  • 4
  • 5

那么请看:

 private void init() {

        //初始化弹跳图片 控件
        iv = new ImageView(getContext());

        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);

        iv.setLayoutParams(params);
        iv.setImageResource(allImgDown[0]);


        this.addView(iv);

        //画笔的初始化
        paint = new Paint();
        paint.setStrokeWidth(1);
        paint.setColor(Color.CYAN);
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(50);
        paint.setAntiAlias(true);

        //矩形
     Rect   rect = new Rect();
        //将文字画入矩形目的是为了测量高度
        paint.getTextBounds(printText, 0, printText.length(), rect);

        //文本宽度
        textWidth = paint.measureText(printText);

        //获得文字高度
        textHeight = rect.bottom - rect.top;

        //初始化路径
        path = new Path();

        iv.setX(getWidth()/2);

        iv.measure(0,0);

        iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                if (!flagMeure)
                {
                    flagMeure =true;



                    //由于画文字是由基准线开始
                    path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());

                    //计算最大弹力
                    maxElasticFactor = (float) (textHeight / elastic);
                    //初始化贝塞尔曲线
                    path.rQuadTo(textWidth / 2, 0, textWidth, 0);

                    //初始化上弹和下沉动画
                    MyAnmation();

                    iv.startAnimation(translateDown);
                }
            }
        });




    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

我们现在写一个类当iv图片(弹跳图)碰到文字顶部时设置一个监听器 时间正好是弹图向上到顶部的时间 期间不断让文字凹陷在恢复正常

  //用于播放文字下沉和上浮动画传入的数值必须是 图片下沉和上升的一次时间
    public void initAnimation(int duration){
        //这里为什maxElasticFactor/4 好看...另一个同理 这个数值大家自行调整
        ValueAnimator animator =  ValueAnimator.ofFloat(maxElasticFactor/4, (float) (maxElasticFactor / 1.5),0);
        animator.setDuration(duration/2);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                calc();//重新画路径
                nowElasticFactor= (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

再来一个重新绘画路径计算的方法

   public void calc(){

        //重置路径
        path.reset();
        //由于画文字是由基准线开始
        path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());
        //画二次贝塞尔曲线
        path.rQuadTo(textWidth / 2, nowElasticFactor, textWidth, 0);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

好了到这里我们看看完整源码吧:

package com.example.administrator.myapplication;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;


public class My extends FrameLayout {

    //画笔
    private Paint paint;
    //路径
    private Path path;
    //要输入的文本
    private String printText = "正在加载";
    //文本宽
    private float textWidth;
    //文本高
    private float textHeight;
    //测量文字宽高的时候使用的矩形
    private Rect rect;
    //最大弹力系数
    private float elastic = 1.5f;

    //最大弹力
    private float maxElasticFactor;

    //当前弹力
    private float nowElasticFactor;

    //用于记录当前图片使用数组中的哪张
    int indexImgFlag = 0;

    //下沉图片
    int allImgDown [] = {R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};

    //动画效果一次下沉或上弹的时间 animationDuration*2=一次完整动画时间
    int animationDuration = 1000;

    //弹起来的图片
    ImageView iv;

    //图片下沉高度(即从最高点到最低点的距离)
    int downHeight = 2;
    private Animation translateDown;
    private Animation translateUp;

    private ObjectAnimator rotation;


    public My(Context context) {
        super(context);
    }

    public My(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawTextOnPath(printText, path, 0, 0, paint);
    }


    //用于播放文字下沉和上浮动画传入的数值必须是 图片下沉和上升的一次时间
    public void initAnimation(int duration){
        //这里为什maxElasticFactor/4为什么
        ValueAnimator animator =  ValueAnimator.ofFloat(maxElasticFactor/4, (float) (maxElasticFactor / 1.5),0);
        animator.setDuration(duration/2);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                calc();
                nowElasticFactor= (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }


    public void calc(){

        //重置路径
        path.reset();
        //由于画文字是由基准线开始
        path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());
        //画二次贝塞尔曲线
        path.rQuadTo(textWidth / 2, nowElasticFactor, textWidth, 0);

    }


    //初始化弹跳动画
    public void  MyAnmation(){
        //下沉效果动画
        translateDown = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
                Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);
        translateDown.setDuration(animationDuration);
        //设置一个插值器 动画将会播放越来越快 模拟重力
        translateDown.setInterpolator(new AccelerateInterpolator());


        //上弹动画
        translateUp = new TranslateAnimation(
         Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
          Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0
       );

        translateUp.setDuration(animationDuration);
        ////设置一个插值器 动画将会播放越来越慢 模拟反重力
        translateUp.setInterpolator(new DecelerateInterpolator());


        //当下沉动画完成时播放启动上弹
        translateDown.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                iv.setImageResource(allImgDown[indexImgFlag]);
                rotation = ObjectAnimator.ofFloat(iv, "rotation", 180f, 360f);
                rotation.setDuration(1000);
                rotation.start();
            }


            @Override
            public void onAnimationEnd(Animation animation) {

                iv.startAnimation(translateUp);
                initAnimation(animationDuration);

            }


            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        //当上移动画完成时 播放下移动画
        translateUp.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                indexImgFlag = 1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;
                iv.setImageResource(allImgDown[indexImgFlag]);
                rotation = ObjectAnimator.ofFloat(iv, "rotation", 0.0f, 180f);
                rotation.setDuration(1000);

                rotation.start();
            }


            @Override
            public void onAnimationEnd(Animation animation) {
                //递归
                iv.startAnimation(translateDown);

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });






    }

    boolean flagMeure;

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        init();
    }

    private void init() {

        //初始化弹跳图片 控件
        iv = new ImageView(getContext());

        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);

        iv.setLayoutParams(params);
        iv.setImageResource(allImgDown[0]);


        this.addView(iv);

        //画笔的初始化
        paint = new Paint();
        paint.setStrokeWidth(1);
        paint.setColor(Color.CYAN);
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(50);
        paint.setAntiAlias(true);

        //矩形
        rect = new Rect();
        //将文字画入矩形目的是为了测量高度
        paint.getTextBounds(printText, 0, printText.length(), rect);

        //文本宽度
        textWidth = paint.measureText(printText);

        //获得文字高度
        textHeight = rect.bottom - rect.top;

        //初始化路径
        path = new Path();

        iv.setX(getWidth()/2);

        iv.measure(0,0);

        iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                if (!flagMeure)
                {
                    flagMeure =true;
                    //由于画文字是由基准线开始
                    path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());

                    //计算最大弹力
                    maxElasticFactor = (float) (textHeight / elastic);
                    //初始化贝塞尔曲线
                    path.rQuadTo(textWidth / 2, 0, textWidth, 0);

                    //初始化上弹和下沉动画
                    MyAnmation();

                    iv.startAnimation(translateDown);
                }
            }
        });




    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268

小人奉上源码一封供大家 参考github源码下载地址

安卓高级 WebView的使用到 js交互

我们先来学习 怎么使用再到用js和安卓源生方法交互

WebView简单使用

此部分转载并做了补充 原博客
原因:比较简单不是很想在写,我只要写js交互部分

  1. WebView可以使得网页轻松的内嵌到app里,还可以直接跟js相互调用。

  2. webview有两个方法:setWebChromeClient 和 setWebClient

  3. setWebClient:主要处理解析,渲染网页等浏览器做的事情

  4. setWebChromeClient:辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等

  5. WebViewClient就是帮助WebView处理各种通知、请求事件的。

在AndroidManifest.xml设置访问网络权限:

<uses-permission android:name="android.permission.INTERNET"/>
  • 1

控件:

<WebView 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/webView"
    />
  • 1
  • 2
  • 3
  • 4
  • 5

用途一:加载本地/Web资源
这里写图片描述
example.html 存放在assets文件夹内

调用WebView的loadUrl()方法,

加载本地资源

webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/example.html");
  • 1
  • 2

加载web资源:

webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("http://baidu.com");
  • 1
  • 2

用途二:在程序内打开网页

这里写图片描述

创建一个自己的WebViewClient,通过setWebViewClient关联

package com.example.testopen;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {
private WebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);             
        init();

    }


    private void init(){
        webView = (WebView) findViewById(R.id.webView);
        //WebView加载web资源
       webView.loadUrl("http://baidu.com");
        //覆盖WebView默认使用第三方或系统默认浏览器打开网页的行为,使网页用WebView打开
       webView.setWebViewClient(new WebViewClient(){
           @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            // TODO Auto-generated method stub
               //返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
             view.loadUrl(url);
            return true;
        }
       });
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

用途三:

如果访问的页面中有Javascript,则webview必须设置支持Javascript

//启用支持javascript
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
  • 1
  • 2
  • 3

用途四:

如果希望浏览的网页后退而不是退出浏览器,需要WebView覆盖URL加载,让它自动生成历史访问记录,那样就可以通过前进或后退访问已访问过的站点。

//改写物理按键——返回的逻辑
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // TODO Auto-generated method stub
        if(keyCode==KeyEvent.KEYCODE_BACK)
        {
            if(webView.canGoBack())
            {
                webView.goBack();//返回上一页面
                return true;
            }
            else
            {
                System.exit(0);//退出程序
            }
        }
        return super.onKeyDown(keyCode, event);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

用途五:判断页面加载过程

webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                // TODO Auto-generated method stub
                if (newProgress == 100) {
                    // 网页加载完成

                } else {
                    // 加载中

                }

            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

用途六:缓存的使用

优先使用缓存

webView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
  • 1
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  • 1

本人补充

还有一些用法由于原作者没写所以我在这里补充下 从这里开始就是原创部分:

需求:假设后台给你返回的是html标签(没有头尾标签 简单说就是没有<html><heand></head><body></body></html>)

//假设返回的字符传如下所示:

package a.com.jswebproject.bean;

/**

 */
public class JString {
    public static final String  CONTENT = "<p style=\"text-indent:32px;line-height:200%;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\"><img src=\"http://s1.sns.maimaicha.com/images/2015/12/31/20151231082937_53817.jpg\" style=\"float:none;\" title=\"771c3d95184d9cb2f73a7d156d332df8.jpg\" border=\"0\" hspace=\"0\" vspace=\"0\" />光阴荏苒,</span><a href=\"http://www.sanwen.net/suibi/suiyue/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">岁月</span></a><span style=\"font-size:15px;\">飞逝如电。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">回眸花落时,</span><span style=\"font-size:15px;font-family:';\">201</span><span style=\"font-size:15px;font-family:';\">5</span><span style=\"font-size:15px;\">就这样悄然而过</span><span style=\"font-size:15px;font-family:';\">……</span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">清浅时光,积聚如山的往事随风游走,</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">带着我们内心所有的牵念,尘封于深深的</span><a href=\"http://huiyi.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">记忆</span></a><span style=\"font-size:15px;\">里。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">记忆,从此被定格!</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">心生温暖,四季平安!每逢岁杪,将昔年一一盘点</span><span style=\"font-size:15px;font-family:';\">……</span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">那一路,我们</span><a href=\"http://cengjing.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">曾经</span></a><span style=\"font-size:15px;\">怎样走过?</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">那一程,谁又曾从心坎上路过?</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">诚然,认识了一些人,却也经历过许多的事。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">浮生若</span><span style=\"font-size:15px;font-family:';\"><a href=\"http://meng.sanwen8.cn/\" target=\"_blank\"><span style=\"font-family:宋体;color:#444444;\">梦</span></a></span><span style=\"font-size:15px;\">,尘缘辗转。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">在心里,就让那些愁殇,随风飘逝吧!</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">站在</span><span style=\"font-size:15px;font-family:';\">201</span><span style=\"font-size:15px;font-family:';\">5</span><span style=\"font-size:15px;\">与</span><span style=\"font-size:15px;font-family:';\">201</span><span style=\"font-size:15px;font-family:';\">6</span><span style=\"font-size:15px;\">年的界碑上,不禁忍不住再次回首</span><span style=\"font-size:15px;font-family:';\">——</span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">到底,有多少得失能够沉淀于心?</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">究竟,有多少</span><a href=\"http://huiyi.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">回忆</span></a><span style=\"font-size:15px;\">值得</span><a href=\"http://yongheng.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">永久</span></a><span style=\"font-size:15px;\">珍藏?</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<a href=\"http://shengming.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">生命</span></a><span style=\"font-size:15px;\">中,总有一些人会成为彼此的匆匆过客;</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">岁月里,总有一些事会流逝而淡出我们的心际;</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">经年间,总有一些</span><a href=\"http://www.sanwen.net/sanwen/xinqing/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">情感</span></a><span style=\"font-size:15px;\">在磨砺中教会我们成熟。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">经历</span><a href=\"http://rensheng.sanwen.net/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">人生</span></a><span style=\"font-size:15px;\">中的点点滴滴,阅历因此而丰硕起来。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">学会淡定从容,坦然</span><a href=\"http://www.sanwen.net/suibi/shenghuo/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">生活</span></a><span style=\"font-size:15px;\">;</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">学会心存善念,静泊尘心。</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">活在当下,最美!在当下,</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">给与</span><a href=\"http://xiangxinziji.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">自己</span></a><span style=\"font-size:15px;\">一份简单的期许,又或是</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">一个</span><a href=\"http://danchun.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">纯真</span></a><span style=\"font-size:15px;\">的祈愿!</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">让明天安然,让未来更好!</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">跨年之</span><a href=\"http://ye.sanwen8.cn/\" target=\"_blank\"><span style=\"font-size:15px;color:#444444;\">夜</span></a><span style=\"font-size:15px;\">,我倚窗凝望,北极星光,绚烂如花!</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<p style=\"text-indent:32px;line-height:200%;text-align:left;margin-bottom:24px;\">\n" +
            "\t<span style=\"font-size:15px;\">祈愿,</span><span style=\"font-size:15px;font-family:';\">201</span><span style=\"font-size:15px;font-family:';\">6</span><span style=\"font-size:15px;\">年每一个阳光灿烂的日子,</span><span style=\"font-size:15px;font-family:';\"></span> \n" +
            "</p>\n" +
            "<span style=\"font-size:15px;\">佑你,佑我,佑他!</span> \n" +
            "<p>\n" +
            "\t<br />\n" +
            "</p>";

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

那我们看看 具体代码怎么加载上面的文字吧

package a.com.jswebproject;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

import qianfeng.com.jswebproject.bean.JString;

public class JsonActivity extends AppCompatActivity {
    private WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_json);
        initView();
        setData();
    }

    private void setData() {
        //如果你直接new WebChromeClient() 或者 new WebClient()默认在程序内打开
        mWebView.setWebChromeClient(new WebChromeClient());
        //params1  baseUrl 基地址 如果你需要在加载的页面里面进行相应操作,那么提交的网址会在基地址的基础上进行添加
        //params2  你要加载的html
        //params3 你加载的html的类型 (text/html) (text/javascript)
        //params4 编码  "UTF-8" "GBK"
        //params5 你访问历史路径
        //http://baidu.com?username=lla&password=123456;
        mWebView.loadDataWithBaseURL(null, JString.CONTENT,"text/html","UTF-8",null);
    }

    private void initView() {
        mWebView = (WebView) findViewById(R.id.wv_json_test);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

再来看个有加载动画的案例 并具有刷新后退前进功能的
这里写图片描述

package qianfeng.com.jswebproject;

import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ProgressBar;

import qianfeng.com.jswebproject.client.MyChormeClient;
import qianfeng.com.jswebproject.client.MyWebViewClient;

public class NetActivity extends AppCompatActivity {
    private WebView mWebView;
    private ActionBar mActionBar;
    private ProgressBar mProgressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_net);
        initView();
        initData();
        setData();
        setListener();
    }

    private void initView() {
      mWebView = (WebView) findViewById(R.id.wv_net_test);
      mActionBar =  getSupportActionBar();
      mProgressBar = (ProgressBar) findViewById(R.id.pb_net_show);
    }

   public void onClick(View view){
       if (view!=null){
           switch (view.getId()){
               case R.id.bt_net_advance:
                   if (mWebView.canGoForward()){
                       mWebView.goForward();
                   }
                   break;
               case R.id.bt_net_back:
                   if (mWebView.canGoBack()){
                       mWebView.goBack();
                   }
                   break;
               case R.id.bt_net_refresh:
                   // WebView从新加载(刷新)
                   mWebView.reload();
                   break;
               case R.id.bt_net_stop:
                   // WebView停止加载
                   mWebView.stopLoading();
                   break;
               default:
                   break;
           }
       }
   }

    private void setListener() {
    }

    private void setData() {
        // 加载网址,要确定你的网址是正确的,然后网络正常,最后权限(联网权限)
        mWebView.loadUrl("http://baidu.com");
        // WebView在加载网页时候需要设置一个WebViewClient 用来监听网络加载开始和介绍的
        // 如果你不设置为客户端,他就会调用系统默认的浏览器来给你加载网页
        //mWebView.setWebViewClient(new WebViewClient());
        MyWebViewClient webViewClient = new MyWebViewClient();
        webViewClient.setClientListener(new MyWebViewClient.ClientCallBack() {
            @Override
            public void onStart(String url) {
                // 设置控件显示和隐藏或者消失
                mProgressBar.setVisibility(View.VISIBLE);
            }

            @Override
            public void onFinish(String url) {
             mProgressBar.setVisibility(View.GONE);
            }
        });
        mWebView.setWebViewClient(webViewClient);
        // 给WebView设置一个ChormeClient,来检测网页加载进度和收到的标题
       // mWebView.setWebChromeClient(new WebChromeClient());
        mWebView.setWebChromeClient(new MyChormeClient());
        MyChormeClient client = new MyChormeClient();
        client.setChormeListener(new MyChormeClient.ChormeCallBack() {
            @Override
            public void onProgressChanged(int progress) {
                // 给ProgressBar设置进度
                mProgressBar.setProgress(progress);

            }

            @Override
            public void onReceivedTitle(String title) {
              if (!TextUtils.isEmpty(title)){
                  mActionBar.setTitle(title);
              }
            }
        });
        mWebView.setWebChromeClient(client);
        // 获取WebView的基本设置
        WebSettings settings = mWebView.getSettings();
        // 设置和js交互是否可用
        settings.setJavaScriptEnabled(true);
        mWebView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

    }

    private void initData() {
    }
    // 当用户按下返回键的时候,系统就会调用这个方法
    @Override
    public void onBackPressed() {
        // 判断WebView是否能够返回
        if (mWebView.canGoBack()){
            // 如果能返回,就返回WebView的内容
            mWebView.goBack();
        }else {
            super.onBackPressed();
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

JS方法调用安卓方法

  1. 我们创建一个类 用于给js交互
    如果你的方法想给js调用,此方法必须加上注解@JavascriptInterface

     class JS {
    
            //如果此方法想 被js调用必须写此注解
            @JavascriptInterface
            public void showToast(String msg){
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
            }
    
            @JavascriptInterface
            public void sub(int a,int b){
                Toast.makeText(MainActivity.this, (a+b)+"", Toast.LENGTH_SHORT).show();
            }
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  2. 添加此类到webView中

//第二个参数随意 当HTML5工程师想调用js方法时
// 第二个参数名字.方法名 
//如:Android.sub(20,30)
   JS js = new JS();
   webView.addJavascriptInterface(js,"Android");
  • 1
  • 2
  • 3
  • 4
  • 5
  1. js 使用时 调用
    先来看看html 源码吧
<html>
<meta charset="UTF-8">
<head>
    <title>这是我的第一个html</title>
    <script type="text/javascript">
        function add (a ,b){
         var count = a+b;
         var textHtml = document.getElementById("result_text");
         textHtml.innerHTML = count;
        }
        function showToast(msg){
          Android.showToast(msg);
        }

        function sub(a,b){
          Android.sub(a,b);
        }
        function test(msg){
         var textHtml = document.getElementById("result_text");
         textHtml.innerHTML = msg;
        }


    </script>
</head>
<body>
<h1>这是啥啊</h1>
<h2>这是啥啊</h2>
<h3>这是啥啊</h3>
<h4>这是啥</h4>

<p>CSDN的朋友们一起学习</p>
<input value="这是一个button" type="button" onclick="javascript:alert('大家好')"><br/>
<input value="点击我试试" type="button" onclick="add(20,10)"><br/>
<input value="点击调用Android显示一个Toast" type="button" onclick="showToast('这是来着网页的文本')"><br/>
<input value="点击调用Android 进行一个减法" type="button" onclick="sub(90,10)">

<a href="https://baidu.com">点击去百度</a>

<form>
    <label><input type="text" name="username"></label>
    <label><input type="text" name="password"></label>
    <input type="submit" name="点击提交">

</form>

<div id="result_text"></div>

</body>

</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

核心部分:

 <script type="text/javascript">

        function showToast(msg){
          Android.showToast(msg);
        }

        function sub(a,b){
          Android.sub(a,b);
        }
    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

//感谢同学们
这里写图片描述

安卓调用JS

  webView.loadUrl("javascript:test('你好啊朋友')");
  • 1

test为js中的方法

function test(msg){
         var textHtml = document.getElementById("result_text");
         textHtml.innerHTML = msg;
        }
  • 1
  • 2
  • 3
  • 4

好了大家看下完整一点的代码吧

package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private WebView webView;
    private WebSettings settings;

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

    private void initView() {
        webView = (WebView) findViewById(R.id.webView);
        webView.setWebChromeClient(new WebChromeClient());
        settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        webView.loadUrl("file:///android_asset/haha.html");
        JS js = new JS();
        webView.addJavascriptInterface(js,"Android");

    }


    class JS {

        //如果此方法想 被js调用必须写此注解
        @JavascriptInterface
        public void showToast(String msg){
            Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public void sub(int a,int b){
            Toast.makeText(MainActivity.this, (a+b)+"", Toast.LENGTH_SHORT).show();
        }
    }

    public void onClick(View view) {
        webView.loadUrl("javascript:test('你好啊朋友')");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

源码:github源码

安卓热修复之AndFIX

我致力于最新的前沿安卓技术分析和使用教学,不打算将很多很深的东西,因为有多少人愿意沉下你的心境去学习难点?我一般只会简单提及.文字错漏在所难免还希望同学们喜欢

热修复介绍

热修复是什么? 如果你一个项目已经上线,出现了严重缺陷,那么你第一反应是推送新版本.那么问题来.老子刚下你的APP 你就叫我重新下载?啥东西!卸了.从而导致用户流量的减退.而热修复就是推送一个补丁文件到客户端(很小),用户打开应用时自动安装.是不是很棒?

AndFix 热修复原理

已经翻译的源码分析

阿里github官网(英文)

推荐自行看官网
如果时间上允许我会帮大家翻译完整版的

AndFix注意的地方

  1. 不支持添加字段 方法 资源 布局修改 类
  2. 虽然不支持添加字段 但是你可以在方法内添加 局部变量(后面解释)

错误:
1. 如果添加了在生成补丁包会有如下错误(大家先看看不需要先知道怎么生成,后面阐述)
这里写图片描述

成功:
仔细看 上面的文字会有什么add modifie(添加修改) com.xxx(你修改的文件)
这里写图片描述

AndFix使用

  1. 首先添加依赖

    dependencies {
            compile 'com.alipay.euler:andfix:0.5.0@aar'
    }
    
    • 1
    • 2
    • 3
    • 4
  2. 初始化 一般在application的实现类
    我们首先创建一个类继承之 application 然后在oncreat方法初始化,
    我先介绍几个API:

    此API返回你的应用版本名()
    java
    String appversion; appversion=getPackageManager().getPackageInfo(getPackageName(),0).ersionName;

    这里写图片描述

    再来看个依赖包的类PatchManager

     public PatchManager patchManager ;
    • 1

    此类有如下几个方法

    参数为上下文 一般都在application中

    patchManager = new PatchManager(this);
    • 1
        初始化 这里传入的是版本名 可以用上面介绍的一个api填入 也可以直接写死
    
    • 1
    • 2
     patchManager.init("1.0");
     patchManager.init(versionName);
    • 1
    • 2

    初始化加载补丁 (你直接当写死就行一般默认在new出来并初始版本后调用.这里还是不真正的加载方法)

      patchManager.loadPatch();
    • 1

    打入补丁 参数为字符串 打入后生效 路径你随意写 文件名也是一样
    addPatch(你的补丁路径)
    //移除补丁路径
    patchManager.removeAllPatch();

    String path = Environment.getExternalStorageDirectory().getAbsoluteFile() + File.separator + "out.apatch";
    
            File file = new File(path);
            if (file.exists()){
                Log.e("fmy","文件存在");
                try {
                    patchManager.addPatch(path);
                    patchManager.removeAllPatch();
                    Log.e("fmy","热修复成功");
                } catch (IOException e) {
                    Log.e("fmy","热修复失败:"+e.getMessage());
                }
            }else{
                Log.e("fmy","文件不存在");
            }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    完整代码:

    package com.example.administrator.myapplication;
    
    import android.app.Application;
    import android.os.Environment;
    import android.util.Log;
    
    import com.alipay.euler.andfix.patch.PatchManager;
    
    import java.io.File;
    import java.io.IOException;
    
    /**
     * Created by Administrator on 2016/11/9.
     */
    
    public class MainAppliacation extends Application {
    
    
        public PatchManager patchManager ;
        private String path;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            patchManager = new PatchManager(this);
            patchManager.init("1.0");
            patchManager.loadPatch();
    
            path = Environment.getExternalStorageDirectory().getAbsoluteFile() + File.separator + "out.apatch";
    
            File file = new File(path);
            if (file.exists()){
                Log.e("fmy","文件存在");
                try {
                    patchManager.addPatch(path);
                    patchManager.removeAllPatch();
                    Log.e("fmy","热修复成功");
                } catch (IOException e) {
                    Log.e("fmy","热修复失败:"+e.getMessage());
                }
            }else{
                Log.e("fmy","文件不存在");
            }
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  3. 添加权限和把我们自定义application关联上
    权限:

     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    • 1
    • 2

    //关联我们自定义application类

     <application
            android:name=".MainAppliacation"
            ....
    • 1
    • 2
    • 3

    完整清单文件:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.administrator.myapplication">
    
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    
        <application
            android:name=".MainAppliacation"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  4. 生成新旧版本的两个已经签名的APK(签名需一样)
    假设我们原本的apk 两个类 一个是activity 类 和一个我们自定义的application类(上面已经说了)

    我们看看activity 文件

    package com.example.administrator.myapplication;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        String name ="你好";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        //一个按钮的点击事件
        public void onClick(View view) {
    
            Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    看下布局文件吧

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.administrator.myapplication.MainActivity">
    
        <Button
            android:onClick="onClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="看看热修复是否成功" />
    </RelativeLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    大概的效果图
    这里写图片描述
    打包签名此apk 并取名为old.apk(这里是为了后面好讲此命名)

    修改一下activity文件
    修改了name的数值

    package com.example.administrator.myapplication;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        String name ="修复了!!!!";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        //一个按钮的点击事件
        public void onClick(View view) {
    
            Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    打包签名此apk 并取名为new.apk(这里是为了后面好讲此命名)

  5. 下载生成补丁工具
    阿里github官网(英文)

    在github下载源码不用我教吧?把阿里整个master下载到本地 并解压
    ![这里写图片描述](http://img.blog.csdn.net/20161109113056564)
    
    • 1
    • 2
    • 3

    打开上述目录的tools目录 解压工具包到你喜欢的位置

    这里写图片描述

    现在我打开我解压的后的目录
    这里写图片描述

  6. 将我们生成的new.apk 和old.apk 还有签名文件一并放入此文件夹
    这里写图片描述

  7. 生成补丁
    在此目录下按下Shit键+鼠标右键 选择 可以看到一个选项为”在此处打开一个命令窗口” —>>不好截图 一点截图就关掉了所以 I’m so sorry

    在跳出命令窗口输入如下命令

    命令 : apkpatch.bat -f new.apk -t old.apk -o output1 -k debug.keystore -p android -a androiddebugkey -e android
    
    -f <new.apk> :新版本
    -t <old.apk> : 旧版本
    -o <output> : 输出目录
    -k <keystore>: 打包所用的keystore
    -p <password>: keystore的密码
    -a <alias>: keystore 用户别名
    -e <alias password>: keystore 用户别名密码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    提示在 dos 窗口按下tab 输入此文件夹下的某个文件名首字母或者一段可以提示哦
    这里写图片描述

-o o 我这里的用法是 在此目录下创建一个o文件夹然后将生产的东西放入此文件夹
  • 1

我们打开看看我们的o文件夹有什么
这里写图片描述

可以看到有一个叫
“new-4d459be7271c372742862b1a885b176a.apatch”的文件
这个就是我们需要的 其他文件用处我以后会补充的 如加固的一些问题

我们把此文件改名 “out.apatch”

现在呢我们演示一下吧:
首先安装old.apk 到手机中 我们运行看看
这里写图片描述

我们把文件out.apath导入到此模拟器的sd卡根目录 在关闭程序(记得清后台!!!!!) 不然你不会重新调用application的oncreat

这里写图片描述

What ?为什么没有变成我们后面改成的字符串?其实是我故意写下的一个坑 我的目的很简单 如果让同学一帆风顺的写下来你永远不会影响深刻对AndFIX哪些部分可以热修复 如前文 布局文件等

这里之所以 不出来 是因为他把重新再name的类中赋值当成重新生成了一个字段(前面说过新字段不可以热修复),所以解锁方法如下

package com.example.administrator.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    String name ="你好";

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

    //一个按钮的点击事件
    public void onClick(View view) {
        name ="修复了";

        Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

然后重新生成一个new.apk 在次和签名文件生成一个新的补丁效果就出来了.这里我就不重复无聊的步骤了,我现在导入正确的补丁
这里写图片描述
(^__^) 我很无聊吧?

混淆

# 先版本两个字换成你的发布版本 一般是release
#debug
# release
-applymapping build/outputs/mapping/换成你的版本(一般是release版本)/mapping.txt
-keep class * extends java.lang.annotation.Annotation
-keep class com.alipay.euler.** {*;}
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
   native <methods>;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

第一次编译的时候注释这行(前面加个#号)

-applymapping build/outputs/mapping/换成你的版本(一般是release版本)/mapping.txt
  • 1

android注解入门 并来自己写一个框架

介绍

这里我带大家来学习一下注解 并且用来写下一个模仿xUtils3 中View框架
此框架 可以省略activity或者fragment的 findViewById 或者设置点击事件的烦恼
我正参加2016CSDN博客之星的比赛 希望您能投下宝贵的一票,点击进入投票
我的github上的源码,包含doc和使用说明

如下代码:

fragment

package a.fmy.com.myapplication;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import a.fmy.com.mylibrary.FmyClickView;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;

//你的fragment的布局id  Your fragment's LayoutId
@FmyContentView(R.layout.fragment_blank)
public class BlankFragment extends Fragment {
    //你想实例化控件的id
    //Do you want to control instance id
    // 等价于 findViewByid
    //Equivalent to the findViewByid
    @FmyViewView(R.id.tv1)
    TextView tv1;
    @FmyViewView(R.id.tv2)
    TextView tv2;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
       //初始化fragment Initialize Fragement
        return FmyViewInject.injectfragment(this,inflater,container);
    }
    //你想给哪个控件添加 添加事件 的id
    //Do you want to add add event id to which controls
    @FmyClickView({R.id.tv1,R.id.tv2})
    public void myOnclick(View view){
        switch (view.getId()) {
            case R.id.tv1:
                tv1.setText("TV1  "+Math.random()*100);
                break;
            case R.id.tv2:
                tv2.setText("TV2  "+Math.random()*100);
                break;
            default:

        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

Activity

package a.fmy.com.myapplication;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.widget.FrameLayout;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;

@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @FmyViewView(R.id.fl)
    FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //initActivity
        // 初始化activity
        FmyViewInject.inject(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.fl,new BlankFragment());
        fragmentTransaction.commit();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

java注解学习

java注解教学大家点击进入大致的看一下即可 不然我不知道这篇博客需要写多久

activity设置填充布局框架

这里我们先写一个用于activity框架 你学习完了之后其实你也会fragment了.
1. 实现activity不需要调用setContentView(R.layout.activity_main);此方法完成布局填充 我们看下效果
不使用框架:

package a.fmy.com.mylibrary;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

使用框架:

package a.fmy.com.mylibrary;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            FmyViewInject.inject(this);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第一步:
创建一个注解类如下
@Target —>>此注解在什么地方可以使用 如类还是变量
ElementType.TYPE只能在类中使用此注解
@Retention(RetentionPolicy.RUNTIME) 注解可以在运行时通过反射获取一些信息(这里如果你疑惑那么请六个悬念继续向下看)

/**
 * 此方注解写于activity类上 可以免去 setContentView()步骤 
 * @author 范明毅
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FmyContentView {  
    /**
     * 保存布局文件的id eg:R.layout.main
     * @return 返回 布局id
     */
    int value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第二步:
写一个工具类 配合注解使用 当开发者使用此类时激活注解的作用

public class FmyViewInject {
    /**
     * 保存传入的activity
     */
    private static Class<?> activityClass;
    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
    }

    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

大家先不用着急看不懂为什么这样写原因

核心源码位于injectContent 我们来实现此方法

    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {

        // 获取注解
        FmyContentView annotation = activityClass
                .getAnnotation(FmyContentView.class);

        if (annotation != null) {
            // 获取注解中的对应的布局id 因为注解只有个方法 所以@XXX(YYY)时会自动赋值给注解类唯一的方法
            int id = annotation.value();
            try {
                // 得到activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
                Method method = activityClass.getMethod("setContentView",
                        int.class);

                // 调用方法 第一个参数为哪个实例去掉用 第二个参数为 参数
                method.invoke(obj, id);
            } catch (Exception e) {

                e.printStackTrace();
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

此方法写完后工具类的inject()方法调用即可

    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
        activityClass = obj.getClass();
        // 初始化activity布局文件
        injectContent(obj);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

完整代码:

public class FmyViewInject {
    /**
     * 保存传入的activity
     */
    private static Class<?> activityClass;
    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
        activityClass = obj.getClass();
        // 初始化activity布局文件
        injectContent(obj);
    }
    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {

        // 获取注解
        FmyContentView annotation = activityClass
                .getAnnotation(FmyContentView.class);

        if (annotation != null) {
            // 获取注解中的对应的布局id 因为注解只有个方法 所以@XXX(YYY)时会自动赋值给注解类唯一的方法
            int id = annotation.value();
            try {
                // 得到activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
                Method method = activityClass.getMethod("setContentView",
                        int.class);

                // 调用方法 第一个参数为哪个实例去掉用 第二个参数为 参数
                method.invoke(obj, id);
            } catch (Exception e) {

                e.printStackTrace();
            }
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

赶快去试试 我们继续写下一步 用法在开始的示例有

activity查找控件

效果如下

@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {
    //直接实例化
    @FmyViewView(R.id.fl)
    private FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FmyViewInject.inject(this);


    }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第一步:
继续写一个注解

/**
 * 此方注解写于activity类中 控件变量上 可以省去findViewId 的烦恼
 * @author 范明毅
 * @version 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyViewView {
    /**
     * 保存view控件的id
     * @return view控件id
     */
    int value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第二步 继续第一节的”activity设置填充布局框架”中的工具类添加新的方法

 /**
     * 初始化activity中的所有view控件 让其不用一个findViewid 实例化
     *
     * @param activity
     */
    private static void injectView(Object activityOrFragment) {

        // 对象所有的属性
        Field[] declaredFields = null;


        // 健壮性
        if (activityClass != null) {
            // 获取du所有的属性 包含私有 保护 默认 共开 但不包含继承等
            // getFields可以获取到所有公开的包括继承的 但无法获取到私有的属性
            declaredFields = activityClass.getDeclaredFields();
        }


        // 健壮性
        if (declaredFields != null) {
            // 遍历所有的属性变量
            for (Field field : declaredFields) {

                // 获取属性变量上的注解
                FmyViewView annotation = field.getAnnotation(FmyViewView.class);

                // 如果此属性变量 包含FMYViewView
                if (annotation != null) {
                    // 获取属性id值
                    int id = annotation.value();

                    Object obj = null;
                    try {

                        // 获取activity中方法
                        obj = activityClass.getMethod("findViewById",
                                int.class).invoke(activityOrFragment, id);


                        Log.e("FMY", "" + field.getClass());
                        // 设置属性变量 指向实例

                        // 如果修饰符不为公共类 这里注意了 当activity
                        // 控件变量为private的时候 我们去访问会失败的 要么打破封装系 要么变量改为public
                        //如 private TextView tv 这种情况 如果不打破封装会直接异常
                        if (Modifier.PUBLIC != field.getModifiers()) {
                            // 打破封装性
                            field.setAccessible(true);
                        }
                        // 这里相当于 field= acitivity.obj
                        field.set(activityOrFragment, obj);
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
            }
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

第三步
在工具类中的inject ()方法调用

    /**
     * 初始化activity和所有注解
     *
     * @param obj 你需要初始化的activity
     */
    public static void inject(Object obj) {

        activityClass = obj.getClass();

        // 初始化activity布局文件
        injectContent(obj);

        // 初始化所有控件实例 省去findViewId的痛苦
        injectView(obj);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

activity设置控件的点击事件

这里需要的知识点 如动态代理等 这里大家可以自己百度看下
效果如下

@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {

    @FmyViewView(R.id.fl)
    private FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FmyViewInject.inject(this);


    }

    //当填充的布局中 id为R.id.fl 被点击将调用如下方法
    @FmyClickView({R.id.fl})
    public void onClick(View v){
        Log.e("fmy", "===>>");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第一步 :
同样写下一个注解

/**
 * 
 * 设置点击事件的注解 只需要在某方法 上写上此注解即可 如@FmyClickView({R.id.bt1,R.id.bt2})
 * @version 1.0
 * @author 范明毅
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyClickView {
    /**
     * 保存所有需要设置点击事件控件的id
     * @return 
     */
    int [] value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

第二步:
写下一个代理处理类(我写在工具类中)

/**
     * 代理处理点击逻辑代码
     * 
     * @author 范明毅
     *
     */
    static class MInvocationHandler implements InvocationHandler {
        //这里我们到时候回传入activity
        private Object target;

        // 用户自定义view 的点击事件方法
        private Method method;

        public MInvocationHandler(Object target, java.lang.reflect.Method method) {
            super();
            this.target = target;
            this.method = method;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // 调用用户自定义方法的点击事件 让activity调用中开发者设定的方法 
            return this.method.invoke(target, args);
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

第三步:
在工具类中写下一个方法用于初始化点击事件

    /**
     * 初始化所有控件的点击事件 只需要某方法上写上对应注解和id即可
     * 
     * @param activity
     */
    private static void inijectOnClick(Object activityOrFragment) {

        //获得所有方法
        Method[] methods  = null;


             methods = activityClass.getMethods();



        // 遍历所有的activity下的方法
        for (Method method : methods) {
            // 获取方法的注解
            FmyClickView fmyClickView = method
                    .getAnnotation(FmyClickView.class);
            // 如果存在此注解
            if (fmyClickView != null) {

                // 所有注解的控件的id
                int[] ids = fmyClickView.value();

                // 代理处理类
                MInvocationHandler handler = new MInvocationHandler(activityOrFragment,
                        method);

                // 代理实例 这里也可以返回     new Class<?>[] { View.OnClickListener.class }中的接口类
                //第一个参数用于加载其他类 不一定要使用View.OnClickListener.class.getClassLoader() 你可以使用其他的
                //第二个参数你所实现的接口
                Object newProxyInstance = Proxy.newProxyInstance(
                        View.OnClickListener.class.getClassLoader(),
                        new Class<?>[] { View.OnClickListener.class }, handler);

                // 遍历所有的控件id 然后设置代理
                for (int i : ids) {
                    try {
                        Object view = null;

                    //如果对象是activity

                             view = activityClass.getMethod("findViewById",
                                        int.class).invoke(activityOrFragment, i);


                        if (view != null) {
                            Method method2 = view.getClass().getMethod(
                                    "setOnClickListener",
                                    View.OnClickListener.class);
                            method2.invoke(view, newProxyInstance);
                        }
                    } catch (Exception e) {

                        e.printStackTrace();
                    }

                }

            }
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

第四部:
在工具类的inject()调用即可


    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {

        activityClass = obj.getClass();

        // 初始化activity布局文件
        injectContent(obj);

        // 初始化所有控件实例 省去findViewId的痛苦
        injectView(obj);

        // 初始化所有控件的点击事件
        inijectOnClick(obj);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20