溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

發(fā)布時(shí)間:2021-06-28 17:59:56 來源:億速云 閱讀:224 作者:Leah 欄目:移動(dòng)開發(fā)

這篇文章給大家介紹Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn),內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

1、AOP的概念

其實(shí)這已經(jīng)涉及到AOP(Aspect Oriented Programming),即面向切面編程,在編譯期間對(duì)代碼進(jìn)行動(dòng)態(tài)管理,以達(dá)到統(tǒng)一維護(hù)的目的。

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

AOP切面

舉個(gè)栗子,Android開發(fā)我們都知道,在項(xiàng)目越來越大的時(shí)候,應(yīng)用可能被分解為多個(gè)模塊,如果你要往所有模塊的方法里頭加一句‘我是大傻叼'的Toast,那是不是得跪。所以最好的方式是想辦法在編譯的時(shí)候拿到所有方法,往方法里頭懟一個(gè)Toast,這樣還不會(huì)影響到運(yùn)行期間性能。

2、Transform

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

Android打包流程

如圖所示是Android打包流程,.java文件->.class文件->.dex文件,只要在紅圈處攔截住,拿到所有方法進(jìn)行修改完再放生就可以了,而做到這一步也不難,Google官方在Android Gradle的1.5.0 版本以后提供了 Transfrom API, 允許第三方 Plugin 在打包 dex 文件之前的編譯過程中操作 .class 文件,我們做的就是實(shí)現(xiàn)Transform進(jìn)行.class文件遍歷拿到所有方法,修改完成對(duì)原文件進(jìn)行替換。

/**
 * 自動(dòng)埋點(diǎn)追蹤,遍歷所有文件更換字節(jié)碼
 */
public class AutoTransform extends Transform {

 @Override
 String getName() {
  return "AutoTrack"
 }
 @Override
 Set<QualifiedContent.ContentType> getInputTypes() {
  return TransformManager.CONTENT_CLASS
 }
 @Override
 Set<QualifiedContent.Scope> getScopes() {
  return TransformManager.SCOPE_FULL_PROJECT
 }

 @Override
 boolean isIncremental() {
  return false
 }
 @Override
 public void transform(
   @NonNull Context context,
   @NonNull Collection<TransformInput> inputs,
   @NonNull Collection<TransformInput> referencedInputs,
   @Nullable TransformOutputProvider outputProvider,
   boolean isIncremental) throws IOException, TransformException, InterruptedException {
   //此處會(huì)遍歷所有文件
   /**遍歷輸入文件*/
   inputs.each { TransformInput input ->
    /**
    * 遍歷jar
    */
    input.jarInputs.each { JarInput jarInput ->
     ...
    }
    /**
    * 遍歷目錄
    */
    input.directoryInputs.each { DirectoryInput directoryInput ->
    ...
    }
  }
}

3、Gradle插件實(shí)現(xiàn)

通過Transform提供的api可以遍歷所有文件,但是要實(shí)現(xiàn)Transform的遍歷操作,得通過Gradle插件來實(shí)現(xiàn),關(guān)于Gradle插件的知識(shí)可以看相關(guān)博客,也可以直接看博主的項(xiàng)目 Luffy 。編寫Gradle插件可能需要一點(diǎn)Goovy知識(shí),具體編寫直接用java語言寫也可以,Goovy是完全兼容java的,只截取插件入口部分實(shí)現(xiàn)PluginEntry.groovy

class PluginEntry implements Plugin<Project> {

 @Override
 void apply(Project project) {
  ...
  //使用Transform實(shí)行遍歷
  def android = project.extensions.getByType(AppExtension)
  registerTransform(android)
  ...
 }

def static registerTransform(BaseExtension android) {
 AutoTransform transform = new AutoTransform()
 android.registerTransform(transform)
}

4、字節(jié)碼編寫

完成上面的操作以后就剩下一件事了,那就是拿到.class文件了,大家都知道.class文件是字節(jié)碼格式的,操作起來難度是相當(dāng)于大的,所以需要一個(gè)字節(jié)碼操作庫來減輕難度,那就是ASM了。

4.1、ASM簡(jiǎn)介

ASM 可以直接產(chǎn)生二進(jìn)制的class 文件,也可以在增強(qiáng)既有類的功能。Java class 被存儲(chǔ)在嚴(yán)格格式定義的 .class文件里,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節(jié)碼(指令)。

4.2、具體使用ASM

ASM框架中的核心類有以下幾個(gè):

  1. ClassReader:該類用來解析編譯過的class字節(jié)碼文件。

  2. ClassWriter:該類用來重新構(gòu)建編譯后的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的字節(jié)碼文件。

  3. ClassVisitor:主要負(fù)責(zé) “拜訪” 類成員信息。其中包括標(biāo)記在類上的注解,類的構(gòu)造方法,類的字段,類的方法,靜態(tài)代碼塊。

  4. AdviceAdapter:實(shí)現(xiàn)了MethodVisitor接口,主要負(fù)責(zé) “拜訪” 方法的信息,用來進(jìn)行具體的方法字節(jié)碼操作。

  5. ClassVisitor的全部方法如下,按一定的次序來遍歷類中的成員。

ClassVisitor的全部方法如下,按一定的次序來遍歷類中的成員。

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

ClassVisitor全部api

在ClassVisitor中根據(jù)你的條件進(jìn)行判斷,滿足條件的類才會(huì)修改其中方法,比如要統(tǒng)計(jì)點(diǎn)擊事件的話,需要實(shí)現(xiàn)View$OnClickListener接口的類才會(huì)遍歷其中的方法進(jìn)行操作。

class AutoClassVisitor extends ClassVisitor {

 AutoClassVisitor(final ClassVisitor cv) {
  super(Opcodes.ASM4, cv)
 }

 @Override
 void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {

  //進(jìn)行需要滿足類的條件過濾
  ...
  super.visit(version, access, name, signature, superName, interfaces)
 }

 @Override
 void visitInnerClass(String name, String outerName, String innerName, int access) {
  // 內(nèi)部類信息
  ...
  super.visitInnerClass(name, outerName, innerName, access)
 }

 @Override
 MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
  // 拿到需要修改的方法,執(zhí)行修改操作
  MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions)
  MethodVisitor adapter = null
  ...
  adapter = new AutoMethodVisitor(methodVisitor, access, name, desc)
  ...
  return methodVisitor
 }

 @Override
 void visitEnd() {
  //類中成員信息遍歷介紹
  ...
  super.visitEnd()
 }
}

在MethodVisitor中根據(jù)對(duì)已經(jīng)拿到的方法進(jìn)行修改了。

MethodVisitor adapter = new AutoMethodVisitor(methodVisitor, access, name, desc) {
  boolean isAnnotation = false
  @Override
  protected void onMethodEnter() {
   super.onMethodEnter()
   //進(jìn)入方法時(shí)可以插入字節(jié)碼
   ...
  }

  @Override
  protected void onMethodExit(int opcode) {
   super.onMethodExit(opcode)
   //退出方法前可以插入字節(jié)碼
   ...
  }

  /**
   * 需要通過注解的方式加字節(jié)碼才會(huì)重寫這個(gè)方法來進(jìn)行條件過濾
   */
  @Override
  AnnotationVisitor visitAnnotation(String des, boolean visible) {
   ...
   return super.visitAnnotation(des, visible)
  }
 }

5、實(shí)戰(zhàn)演練

以上就是總體的思路了,現(xiàn)在就通過 Luffy 根據(jù)具體需求實(shí)戰(zhàn)一下,比如說在onClick方法點(diǎn)擊的耗時(shí)(自動(dòng)埋點(diǎn)也是一樣的道理,只不過換了插樁的方法)。

5.1、插件配置

先打包一下插件到本地倉庫進(jìn)行引用,在項(xiàng)目的根build.gradle加入插件的依賴

dependencies {
 classpath 'com.xixi.plugin:plugin:1.0.1-SNAPSHOT'
}

在app的build.gradle中

apply plugin: 'apk.move.plugin'

xiaoqingwa{
 name = "小傻逼"
 isDebug = true
 //具體配置
 matchData = [
   //是否使用注解來找對(duì)應(yīng)方法
   'isAnotation': false,
   //方法的匹配,可以通過類名或者實(shí)現(xiàn)的接口名匹配
   'ClassFilter': [
     ['ClassName': null, 'InterfaceName':null,
      'MethodName':null, 'MethodDes':null]
   ],
   //插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
   'MethodVisitor':{
    MethodVisitor methodVisitor, int access, String name, String desc ->
     MethodVisitor adapter = new AutoMethodVisitor(methodVisitor, access, name, desc) {
      boolean isAnnotation = false
      @Override
      protected void onMethodEnter() {
       super.onMethodEnter()
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
      }

      @Override
      protected void onMethodExit(int opcode) {
       super.onMethodExit(opcode)
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
      }

      /**
       * 需要通過注解的方式加字節(jié)碼才會(huì)重寫這個(gè)方法來進(jìn)行條件過濾
       */
      @Override
      AnnotationVisitor visitAnnotation(String des, boolean visible) {
       return super.visitAnnotation(des, visible)
      }
     }
     return adapter
   }
 ]
}

要是使用演示的話,因?yàn)檫€沒上傳到j(luò)center庫,所以只能本地倉庫打包插件,記得要先把依賴都注釋掉,插件打包完成后再啟用,不然會(huì)編譯不過去的。

xiaoqingwa{} 里頭的配置信息先不用管,等會(huì)會(huì)講到,主要是為了能夠不修改插件進(jìn)行動(dòng)態(tài)更換插樁的方法。

5.2、應(yīng)用測(cè)試

插件配置好了之后就可以測(cè)試一下效果了,先寫一個(gè)耗時(shí)統(tǒng)計(jì)的工具類

TimeCache.java

/**
 * Author:xishuang
 * Date:2018.01.10
 * Des:計(jì)時(shí)類,編譯器加入指定方法中
 */
public class TimeCache {
 public static Map<String, Long> sStartTime = new HashMap<>();
 public static Map<String, Long> sEndTime = new HashMap<>();

 public static void setStartTime(String methodName, long time) {
  sStartTime.put(methodName, time);
 }

 public static void setEndTime(String methodName, long time) {
  sEndTime.put(methodName, time);
 }

 public static String getCostTime(String methodName) {
  long start = sStartTime.get(methodName);
  long end = sEndTime.get(methodName);
  long dex = end - start;
  return "method: " + methodName + " cost " + dex + " ns";
 }
}

大概思路就是使用HashMap來臨時(shí)保存對(duì)應(yīng)方法的時(shí)間,退出方法時(shí)獲取時(shí)間差。

在一個(gè)方法的前后插入時(shí)間統(tǒng)計(jì)的方法,這個(gè)具體的過程要怎么操作呢,因?yàn)閏lass文件是字節(jié)碼格式的,ASM也是進(jìn)行字節(jié)碼操作,所以必須先把插入的代碼轉(zhuǎn)換成字節(jié)碼先。這里推薦一個(gè)字節(jié)碼查看工具Java Bytecode Editor,導(dǎo)入.class文件就可以看到對(duì)應(yīng)字節(jié)碼了。

比如我們要插入的代碼如下:

private void countTime() {
 TimeCache.setStartTime("newFunc", System.currentTimeMillis());
 
 TimeCache.setEndTime("newFunc", System.currentTimeMillis());
 Log.d("耗時(shí)", TimeCache.getCostTime("newFunc"));
}

先把.java文件編譯成.class文件,用Java Bytecode Editor打開

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

插入代碼的字節(jié)碼

然后根據(jù)其用ASM提供的Api一一對(duì)應(yīng)的把代碼填進(jìn)來加到onMethodEnter和onMethodExit中。

//方法前加入
methodVisitor.visitMethodInsn
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setStartTime", "(Ljava/lang/String;J)V", false)

//方法后加入
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setEndTime", "(Ljava/lang/String;J)V", false)
methodVisitor.visitLdcInsn("耗時(shí)")
methodVisitor.visitLdcInsn(name)
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "getCostTime", "(Ljava/lang/String;)Ljava/lang/String;", false)
methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false)

在app的build.gradle中配置得到的字節(jié)碼,最后設(shè)置一下過濾條件,最終的代碼如下:

build.gradle

xiaoqingwa{
 name = "小傻逼"
 isDebug = true
 //具體配置
 matchData = [
   //是否使用注解來找對(duì)應(yīng)方法
   'isAnotation': false,
   //方法的匹配,可以通過類名或者實(shí)現(xiàn)的接口名匹配
   'ClassFilter': [
     ['ClassName': 'com.xishuang.plugintest.MainActivity', 'InterfaceName': 'android/view/View$OnClickListener',
      'MethodName':'onClick', 'MethodDes':'(Landroid/view/View;)V']
   ],
   //插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
   'MethodVisitor':{
    MethodVisitor methodVisitor, int access, String name, String desc ->
     MethodVisitor adapter = new AutoMethodVisitor(methodVisitor, access, name, desc) {
      boolean isAnnotation = false
      @Override
      protected void onMethodEnter() {
       super.onMethodEnter()
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
//       if (!isAnnotation){
//        return
//       }

       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/MainActivity", "notifyInsert", "()V", false)
       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setStartTime", "(Ljava/lang/String;J)V", false)
      }

      @Override
      protected void onMethodExit(int opcode) {
       super.onMethodExit(opcode)
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
//       if (!isAnnotation){
//        return
//       }

       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setEndTime", "(Ljava/lang/String;J)V", false)
       methodVisitor.visitLdcInsn("耗時(shí)")
       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "getCostTime", "(Ljava/lang/String;)Ljava/lang/String;", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false)
      }

      /**
       * 需要通過注解的方式加字節(jié)碼才會(huì)重寫這個(gè)方法來進(jìn)行條件過濾
       */
      @Override
      AnnotationVisitor visitAnnotation(String des, boolean visible) {
//       if (des.equals("Lcom/xishuang/annotation/AutoCount;")) {
//        println "注解匹配:" + des
//        isAnnotation = true
//       }
       return super.visitAnnotation(des, visible)
      }
     }
     return adapter
   }
 ]
}

'isAnotation' 表示是否使用注解的方式找到對(duì)應(yīng)方法,這里false,因?yàn)槲覀儸F(xiàn)在是通過具體類信息來判斷的。

'ClassFilter' 表示過濾條件,其中 'ClassName''InterfaceName' 用于判斷哪些類中的方法可以遍歷其中的方法進(jìn)行匹配修改,不滿足的話就不會(huì)進(jìn)行方法名匹配了,這些感興趣的童鞋都可以改插件自定義擴(kuò)展。

'MethodName''MethodDes' 是方法名和方法描述符,可以唯一確定一個(gè)方法名,滿足類過濾條件的就會(huì)進(jìn)行方法匹配,例如我們要統(tǒng)計(jì)的點(diǎn)擊事件 onClick(View v) 。

意思就是繼承自 android/view/View$OnClickListener 的類或者類名是 'com.xishuang.plugintest.MainActivity' 就可以進(jìn)行方法的遍歷,然后方法滿足 onClick(View v) 就會(huì)進(jìn)行代碼插入操作。

設(shè)置完之后rebuild一下就可以了,可以通過日志看下具體信息, isDebug = true 可以開啟日志打印。

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

日志

通過日志可以看到我們?cè)O(shè)置的字節(jié)碼確實(shí)插樁成功,現(xiàn)在再看一下編譯后的文件驗(yàn)證一下,具體位置是:app\build\intermediates\transforms\AutoTrack\debug\folders

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)

編譯后的.class文件

其中的notifyInsert()是我用來彈Toast額外調(diào)試用的,請(qǐng)忽略。在手機(jī)上點(diǎn)擊一下按鈕測(cè)試一下,發(fā)現(xiàn)確實(shí)記錄下點(diǎn)擊的耗時(shí)時(shí)間,完成。

Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn) 

5.3、注解匹配

除了以上的方式來查找修改的方法之外,還可以通過注解來查找,切換很簡(jiǎn)單,只需要改一下app的build.gradle文件就可以了,項(xiàng)目中也有栗子,添加了一個(gè)注解類。

/**
 * Author:xishuang
 * Date:2018.1.9
 * Des:時(shí)間統(tǒng)計(jì)注解
 */
@Target(ElementType.METHOD)
public @interface AutoCount {
}

然后在對(duì)應(yīng)的方法上添加你自定義的注解

@AutoCount
 private void onClick() {
  try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }


 @AutoCount
 @Override
 public void onClick(View v) {
  if (v.getId() == R.id.button) {
   Toast.makeText(this, "我是按鈕", Toast.LENGTH_SHORT).show();
  }
 }

修改一下build.gradle中的配置文件

xiaoqingwa{
 name = "小傻逼"
 isDebug = true
 //具體配置
 matchData = [
   //是否使用注解來找對(duì)應(yīng)方法
   'isAnotation': true,
   //方法的匹配,可以通過類名或者實(shí)現(xiàn)的接口名匹配
   'ClassFilter': [
     ['ClassName': 'com.xishuang.plugintest.MainActivity', 'InterfaceName': 'android/view/View$OnClickListener',
      'MethodName':'onClick', 'MethodDes':'(Landroid/view/View;)V']
   ],
   //插入的字節(jié)碼,方法的執(zhí)行順序visitAnnotation->onMethodEnter->onMethodExit
   'MethodVisitor':{
    MethodVisitor methodVisitor, int access, String name, String desc ->
     MethodVisitor adapter = new AutoMethodVisitor(methodVisitor, access, name, desc) {
      boolean isAnnotation = false
      @Override
      protected void onMethodEnter() {
       super.onMethodEnter()
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
       if (!isAnnotation){
        return
       }

       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/MainActivity", "notifyInsert", "()V", false)
       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setStartTime", "(Ljava/lang/String;J)V", false)
      }

      @Override
      protected void onMethodExit(int opcode) {
       super.onMethodExit(opcode)
       //使用注解找對(duì)應(yīng)方法的時(shí)候得加這個(gè)判斷
       if (!isAnnotation){
        return
       }

       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "setEndTime", "(Ljava/lang/String;J)V", false)
       methodVisitor.visitLdcInsn("耗時(shí)")
       methodVisitor.visitLdcInsn(name)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "com/xishuang/plugintest/TimeCache", "getCostTime", "(Ljava/lang/String;)Ljava/lang/String;", false)
       methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "d", "(Ljava/lang/String;Ljava/lang/String;)I", false)
      }

      /**
       * 需要通過注解的方式加字節(jié)碼才會(huì)重寫這個(gè)方法來進(jìn)行條件過濾
       */
      @Override
      AnnotationVisitor visitAnnotation(String des, boolean visible) {
       if (des.equals("Lcom/xishuang/annotation/AutoCount;")) {
        println "注解匹配:" + des
        isAnnotation = true
       }
       return super.visitAnnotation(des, visible)
      }
     }
     return adapter
   }
 ]
}

關(guān)鍵代碼在于把 'isAnotation' 設(shè)為 true ,然后在 visitAnnotation 方法中添加你的注解類匹配,也就是這句 des.equals("Lcom/xishuang/annotation/AutoCount;") 代碼,注解類的描述符,運(yùn)行效果和上面差不多,但是不會(huì)打印日志,因?yàn)橥ㄟ^注解來查找方法會(huì)遍歷每個(gè)方法,打印信息太多電腦會(huì)爆炸。

關(guān)于Android中怎么利用ASM實(shí)現(xiàn)自動(dòng)埋點(diǎn)就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI