{"id":15028224,"url":"https://github.com/eleme/lancet","last_synced_at":"2025-05-15T11:08:12.597Z","repository":{"id":37638098,"uuid":"88848560","full_name":"eleme/lancet","owner":"eleme","description":"A lightweight and fast AOP framework for Android App and SDK developers","archived":false,"fork":false,"pushed_at":"2023-08-26T07:58:08.000Z","size":358,"stargazers_count":2159,"open_issues_count":45,"forks_count":339,"subscribers_count":41,"default_branch":"develop","last_synced_at":"2025-04-14T19:58:27.038Z","etag":null,"topics":["android-plugin","bytecode-manipulation"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eleme.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-04-20T09:37:29.000Z","updated_at":"2025-04-06T03:51:44.000Z","dependencies_parsed_at":"2022-07-16T18:17:01.796Z","dependency_job_id":"77430f71-e0ba-48cb-8244-d0d719020b14","html_url":"https://github.com/eleme/lancet","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eleme%2Flancet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eleme%2Flancet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eleme%2Flancet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eleme%2Flancet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eleme","download_url":"https://codeload.github.com/eleme/lancet/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-plugin","bytecode-manipulation"],"created_at":"2024-09-24T20:07:50.597Z","updated_at":"2025-05-15T11:08:12.575Z","avatar_url":"https://github.com/eleme.png","language":"Java","readme":"# Lancet\n\n[Chinese README](README_zh.md)\n\nLancet is a lightweight AOP framework for Android.\n\nIt's fast and just take up a little time during compiling. Also, it supports incremental compiling.\n\nBut it provides great api to help you coding in Android.\n\nIt takes no runtime jar.\n\nIn addition, not only App developers but also SDK developers can use Lancet.\n\n## Usage\n### Installation\n\nFirstly, add following code in root **build.gradle** of your project.\n\n```groovy\ndependencies {\n    classpath 'com.android.tools.build:gradle:3.3.2'\n    classpath 'me.ele:lancet-plugin:1.0.6'\n}\n```\nTips: Lancet 1.0.5 and above only supports gradle 3.3.2 and above.\n\nAnd then, add following code in your **application module's build.gradle**\n\n```groovy\napply plugin: 'me.ele.lancet'\n\ndependencies {\n    provided 'me.ele:lancet-base:1.0.6'\n}\n```\n\nThat's OK.Now you can follow our tutorial to learn how to use it.\n\n### Tutorial\n\nLancet use annotation to indicate where to cut the code and focus on interacting with origin class's methods and fields;\n\nFirstly, let's see a example.\nLook at the following code:\n\n```java\n@Proxy(\"i\")\n@TargetClass(\"android.util.Log\")\npublic static int anyName(String tag, String msg){\n    msg = msg + \"lancet\";\n    return (int) Origin.call();\n}\n```\n\nThere are some key points: \n\n* ```@TargetClass``` directly locate the target ```android.util.Log```\n* ```@Proxy```locate the method name```i```\n* ```Origin.call()``` will be replaced by```Log.i()``` as we explained above\n* so the influence is every ```Log.i```'s second parameter```msg``` will has a trailing **\"lancet\"**\n\n\n### Choose target class\n\n```java\npublic @interface TargetClass {\n    String value();\n\n    Scope scope() default Scope.SELF;\n}\n\npublic @interface ImplementedInterface {\n\n    String[] value();\n\n    Scope scope() default Scope.SELF;\n}\n\npublic enum Scope {\n\n    SELF,\n    DIRECT,\n    ALL,\n    LEAF\n}\n```\nWe use the three classes above to locate our targets.\n\n#### @TargetClass\n\n 1. **value** in ```@TargetClass``` should be a full class name.\n 2. Scope.SELF means the target is the class named by **value**\n 3. Scope.DIRECT locate the direct subclasses of **value**\n 4. Scope.All indicates the all subclasses of **value**\n 5. The Scope.LEAF is a little bit special, it means all leaf subclasses of **value**.For example: ```A \u003c- B \u003c- C, B \u003c- D```, the leaf children of A are C and D.\n\n#### @ImplementedInterface\n\n1. **value** in ```@ImplementedInterface``` is a string array filled with full interface names and classes that satisfied all conditions will be chosen.\n2. Scope.SELF : all classes implements interfaces **literally**\n3. Scope.DIRECT : all classes implements interfaces or their children interfaces **literally**\n4. Scope.ALL: all classes included in *Scope.DIRECT* and their childrens\n5. Scope.LEAF: all classes in *Scope.ALL* with no children.\n\nLet's see a illustration.\n\n![scope](media/14948409810841/scope.png)\n\nWhen we use```@ImplementedInterface(value = \"I\", scope = ...)```, the targets are:\n\n* Scope.SELF -\u003e A\n* Scope.DIRECT -\u003e A C\n* Scope.ALL -\u003e A B C D\n* Scope.LEAF -\u003e B D\n\n\n### Choose target method\n\n#### Choose method name\n\n```java\npublic @interface Insert {\n    String value();\n    boolean mayCreateSuper() default false;\n}\n\npublic @interface Proxy {\n    String value();\n}\n\npublic @interface TryCatchHandler {\n}\n\npublic @interface NameRegex {\n    String value();\n}\n\n```\n\n##### @TryCatchHandler\n\nThis annotation is easy.\n\n```java\n@TryCatchHandler\n@NameRegex(\"(?!me/ele/).*\")\n    public static Throwable catches(Throwable t){\n        return t;\n    }\n```\n\nAs the code above, it hook every try catch handle, you can deal with and return. And the control flow will jump to it's origin space.\n\nBut with the ```@NameRegex```, something is different.\n\n##### @NameRegex\n\n@NameRegex is used to restrict hook method by match the class name.Be caution, the *dot* in class name will be replaced by *slash*.\n\nString value in @NameRegex will be compiled to pattern. the hook method only works if the pattern matches the class name where appear the cut point.\n\nSuch as the above example, every class will be ignored if it's package name start with \"me.ele.\";\n\n@NameRegex can only be used with @Proxy or @TryCatchHandler.\n\n##### @Proxy and @Insert\n\n1. **Value** in ```@Proxy``` and ```@Insert``` is the target method name.\n2. ```@Proxy``` means to hook every invoke point of the target method.\n\n3. ```@Insert``` means to hook the code inside the method.\n4. In another word, if you use @Insert to hook a method, the running code in the target method will be changed.But ```@Proxy``` can control the scope by using it combined with ```@NameRegex```.\n5. Another different is, classes in Android's ROM can't be touched as compiling time.So we can't use @Insert if we want to change the behavior of ROM's classes, but @Proxy can do it.\n\n@Insert has a special boolean parameter is ```mayCreateSuper```.Let's see a example.\n\n```java\n\n@TargetClass(value = \"android.support.v7.app.AppCompatActivity\", scope = Scope.LEAF)\n@Insert(value = \"onStop\", mayCreateSuper = true)\nprotected void onStop(){\n    System.out.println(\"hello world\");\n    Origin.callVoid();\n}\n```\n\nThe goal method of the hook method is \nevery leaf child of AppcompatActivity ```void onStop()```.\n\nIf a class ```MyActivity extends AppcompatActivity```do not override the onStop method, we will create a method for ```MyActivity``` like this:\n\n```java\nprotected void onStop() {\n    super.onStop();\n}\n```\n\nAnd then hook the method.\n\nIf you open the flag, it will always create the method if target class has no matched method no matter it has the super method or doesn't.\n\nAnd the public/protected/private flag is inherited from above hook method.This is the flag's only use.\n\nAlso be care that \n\n#### Choose method descriptor\n\nThe example shown at first also indicates a important rule which is the strict match.The ```Log.i``` 's full descriptor is **\"int Log.i(String, String)\"**, more precisely \"**\"(Ljava/lang/String;Ljava/lang/String;)I\"**\". \n\nIt means our hook method should have the same method descriptor and static flag with target method.\n\nIt doesn't matter if you don't known method descriptor. You can have a look at [JVM Specification Chapter 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3) if interested.\n\nWe doesn't care if parameters' generic type are the same or not.In another word, we don't care signature of method, only descriptor.\n\nAlso, exceptions declaration is also ignorable.You can write them for convenience.\n\nAny other access flag will be ignored except private/protected/public/static that we said above.\n\n##### @ClassOf\n\nSometimes, we can't directly declare a class that we can't touch as parameter of our hook method.\n\nwe can use ```@ClassOf``` to do this job.\n\nTake a look at the following example:\n\n```java\npublic class A {\n    protected int execute(B b){\n        return b.call();\n    }\n\n    private class B {\n\n        int call() {\n            return 0;\n        }\n    }\n}\n\n@TargetClass(\"com.dieyidezui.demo.A\")\n@Insert(\"execute\")\npublic int hookExecute(@ClassOf(\"com.dieyidezui.demo.A$B\") Object o) {\n    System.out.println(o);\n    return (int) Origin.call();\n}\n```\n\nWe use ```@ClassOf```to locate the parameter's actual type.\n\nAnd the parameter declared in method should be it's super class, such as ```java.lang.Object```;\n\nvalue in @ClassOf should be in the form of **\"(package_name.)(outer_class_name$)class_name([]...)\"**, such as:\n* java.lang.Object\n* java.lang.Integer[][]\n* A[]\n* A$B\n\nif no ```@ClassOf```, the hook method's descriptor is **\"(Ljava/lang/Object;)I\"**. But now it is **\"(Lcom/dieyidezui/demo/A$B;)I\"**.\n\nSo the ```hookExecute``` method can match ```A.execute```.\n\n### API\n\nTill now, we have two classes to use, they are ```Origin``` and ```This```.\n\n#### Origin\n\n```Origin``` is used to call original method.\nYou can invoke its method zero or one more times if you like.\n\n##### Origin.call/callThrowOne/callThrowTwo/callThrowThree()\nThis group API is used for call the original method which has return value.\nYou should cast it to original type that the same with hook method descriptor's return type.\n\n##### Origin.callVoid/callVoidThrowOne/callVoidThrowTwo/callVoidThrowThree()\n\nSimilar with above three methods, these methods are used for method without return value.\n\nBy the way, the ```ThrowOne/ThrowTwo/ThrowThree``` are for deceiving the compiler if you want to catch some exceptions for some convenience.\n\nFor example:\n\n```java\n@TargetClass(\"java.io.InputStream\")\n@Proxy(\"read\")\npublic int read(byte[] bytes) throws IOException {\n    try {\n        return (int) Origin.\u003cIOException\u003ecallThrowOne();\n    } catch (IOException e) {\n        e.printStackTrace();\n        throw e;\n    }\n}\n```\n\nSo that on every invoke point of ```int InputStream.read(byte[])```, if ```IOException``` happens, we will see its stacktrace. \n\n#### This\n\n##### get()\n\nThis method is used for none static method to find this object.\nYou can cast it to its actual type.\n\n###### putField(Object, String) / getField(String)\n\nYou can directly get or put a field in the target class even if the field is protected or private!\n\nWhat's more! If the field name is not exists, we will create it for you!\n\nAuto box and unbox are also supported.\n\nAlso, we have some restricts:\n\n* These two methods only are only allowed to use with ```@Insert``` till now.\n* You can't retrieve it's field of super class. When you try to get or put a field that it's super class has. We still will create the field for you. If the field of super class is private, it's OK. Otherwise, you will get a error at runtime.\n\nFor example:\n\n```java\npackage me.ele;\npublic class Main {\n    private int a = 1;\n\n    public void nothing(){\n\n    }\n\n    public int getA(){\n        return a;\n    }\n}\n\n@TargetClass(\"me.ele.Main\")\n@Insert(\"nothing\")\npublic void testThis() {\n    Log.e(\"debug\", This.get().getClass().getName());\n    This.putField(3, \"a\");\n    Origin.callVoid();\n}\n\n```\n\nThen we run the following codes:\n\n```java\nMain main = new Main();\nmain.nothing();\nLog.e(\"debug\", \"a = \" + main.getA());\n```\n\nWe will see:\n\n```\nE/debug: me.ele.Main\nE/debug: a = 3\n```\n\n## Tips\n1. Inner classes should be named like  ```package.outer_class$inner_class```\n2. SDK developer needn't to apply plugin, just ```provided me.ele:lancet-base:x.y.z```\n3. Although we support incremental compilation. But when you use ```Scope.LEAF、Scope.ALL``` or edit the hook class, the incremental judgement will be a little special. It may cause full compilation.\n\n## License\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n\n\n\n\n\n","funding_links":[],"categories":["AOP"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feleme%2Flancet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feleme%2Flancet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feleme%2Flancet/lists"}