Intent匹配规则以及解析框架深入分析

来源:互联网 发布:林仕鼎 知乎 编辑:程序博客网 时间:2024/05/21 08:44

 


                                                                          本文原创 ,转载必须注明出处 :http://blog.csdn.net/qinjuning

 


       前言: 本文博客是公司培训之间的PPT整理而成,在此把相关的细节发布出来,希望对大家能有所帮助。

 

 

    关于Intent以及IntentFilter的基本知识,大家可以参阅如下资料,

                      SDK中对Intent与IntentFilter的介绍  ---- 英文

                       其中文翻译如下:

                              Android开发之旅: Intents和Intent Filters(理论部分)

 

    我重点分析一下两个方面:

         第一部分 、Intent以及IntentFilter说明以及匹配规则分析

         第二部分:Intent的解析过程分析
 
 

 

 第一部分 、Intent以及IntentFilter说明以及匹配规则分析

 

       想当初我看Intent相关知识时,对Intent、IntentFilter的理解就很差劲,总觉得系统定义了一个Intent,为何还要整理个

  IntentFilter出来"祸害"广大程序猿呢?但不解归不解,在具体使用咱可不能含糊,于是只好依葫芦画瓢了,反正绝对还不错。

   

 一、温故而知新 :Intent与IntentFilter两问。


        *  它们是什么 ?
 
       *  它们的区别在哪儿 ?
 
   事实上,这两个问题可以归纳为Intent和Intent的主要功能是什么 ? 大家可以先扪心自问下,看看你的掌握程度如何 ?
 
   我的理解如下:
 
       * Intent :   主要功能是根据特定的条件找到匹配的组件,继而对该组件执 行一些操作。比如执行startActivity()时,系统
              首先要找到特定的Activity组件,然后执行onCreate()方法;startService()也得先找的特定的Service组件,然后执行  
               onCreate()或者onStart()方法 。
 
      * IntentFilter :主要功能是为某个组件向系统注册一些特性(当然一个组件可以注册多个IntentFilter),以便Intent找到对应
             的组件。
 
 

 二、它们之间的关系是如何呢?

 
      通过前面对Intent以及IntentFilter的分析,我们很容易在语意上得出它们其实是个前后关系
 
         * IntentFilter在前:任何一个组件必须先通过IntentFilter注册。
 
        * Intent 在后       :根据特定信息,找到之前以及注册过的组件。
 
 
  源码分析:
 
    Intent类源码(部分) 路径位于:\frameworks\base\core\java\android\content\Intent.java

public class Intent implements Parcelable, Cloneable {    private String mAction;           //action值    private Uri mData;                //uri    private String mType;             //MimeType    private String mPackage;          //所在包名    private ComponentName mComponent; //组件信息    private int mFlags;               //Flag标志位    private HashSet<String> mCategories; //Category值    private Bundle mExtras;           //附加值信息//...}

   IntentFilter类源码(部分) 路径位于:\frameworks\base\core\java\android\content\IntentFilter.java

public class IntentFilter implements Parcelable {//...    //保存了所有action字段的值    private final ArrayList<String> mActions;    //保存了所有Category的值    private ArrayList<String> mCategories = null;    //保存了所有Schema(模式)的值    private ArrayList<String> mDataSchemes = null;    //保存了所有Authority字段的值    private ArrayList<AuthorityEntry> mDataAuthorities = null;    //保存了所有Path的值    private ArrayList<PatternMatcher> mDataPaths = null;    //保存了所有MimeType的值    private ArrayList<String> mDataTypes = null;//...}

 

       PS :大家可以参详下Intent与IntentFilter类中不同字段的属性类型。Intent中属性类型基本上都是单个类型的,而IntentFilter

属性都是集合类型的。从这方面思考,更可以加深我们的理解。

 

 三、Intent匹配规则

 

      匹配种类有如下三种:      

                 *  动作(Action)检测
                 *  种类(Category)检测
                 *  数据(Data & MimeType)检测
 

      比较好理解的是,进行匹配时Intent携带的Action字段值和Category字段值必须包含在IntentFilter中,否则匹配失败。

 

       SDK中说明的具体规则如下:


            *      一个Intent对象既不包含URI,也不包含数据类型 ; 仅当过滤器也不指定任何URIs和数据类型时,才能通过检测;
              否则不能通过。

 

            *     一个Intent对象包含URI,但不包含数据类型:仅当过滤器也不指定数据类型,同时它们的URI匹配,才能通过检测。
            例如,mailto:和tel:都不指定实际数据。
 
            *    一个Intent对象包含数据类型,但不包含URI:仅当过滤也只包含数据类型且与Intent相同,才通过检测。
 
            *    一个Intent对象既包含URI,也包含数据类型(或数据类型能够从URI推断出) ; 数据类型部分,只有与过滤器中之一
             匹配才算通过;URI部分,它的URI要出现在过滤器中,或者它有content:或file: URI,又或者过滤器没有指定URI。
             换句话说,如果它的过滤器仅列出了数据类型,组件假定支持content:和file: 。

 

         PS :可别说我不会总结出来给大家分享,其实我觉得很多知识都需要自己去尝试,去努力吸收,只要经过自己的消化,

          学到的知识就是自己的了。

 

      上面的规则比较生硬吧。我们去源码中去看看Intent与IntentFilter的具体匹配方法吧。

       该方法是IntentFilter中的match()方法,该方法的内部处理逻辑就是按照上面的规则去判断的,大家可以仔细体味下,该方法

  我们在后面讲到Intent解析过程时也会用到。 具体逻辑在代码中进行了说明。

public class IntentFilter implements Parcelable {//匹配算法,,按照匹配规则进行//Intent与该IntentFilter进行匹配时调用该方法参数表示Intent的相关属性值public final int match(String action, String type, String scheme,        Uri data, Set<String> categories, String logTag){//首先、匹配Action字段    if (action != null && !matchAction(action)) {        if (Config.LOGV) Log.v(            logTag, "No matching action " + action + " for " + this);        return NO_MATCH_ACTION;    }//其次、匹配数据(Uri和MimeType)字段    int dataMatch = matchData(type, scheme, data);    //...    //最后,匹配Category字段值    String categoryMatch = matchCategories(categories);    //...}//...}


    这部分我们重点讲解的知识点有如下:
 
           1、Intent以及IntentFilter的主要职能
           2、Intent与IntentFilter的关系
           3、匹配规则说明
 
 

第二部分、 Intent的解析过程分析

  
       在继续看本部分之前,希望您最好对PackageManagerService-----程序包管理服务有一定的认知(没有也是OK的咯)。
  可以参考下面这篇问题去看看PackageManagerService的功能和相关流程。

         老罗的博客:<<Android应用程序安装过程源代码分析>>
           
 

    一、引入PackageManager

           我们知道Android源码总是贴心的(不知道有没有10086贴心),它对外提供了很多借口供应用程序调用,例如AudioManger

    (音频管理)、TelephoneManger(电话管理)、同样也提供了一个包管理-----PackageManager,通过它我们可以、获取应用

    程序包得信息,例如图标、Lable标签等。具体关于PackageManager的使用,可以参考我的另外一篇文章 :

 

                       <<Android中获取应用程序(包)的信息-----PackageManager的使用(一)>>

 

   二、PackageManagerService  ---- 重量级选手

          前面所说PackageManager不过是个傀儡,所有相关的操作都是由PackageManagerService 完成的。这儿我们简单的

      分析下PackageManagerService 的特性:

             ①、开机就启动,由SystemServer进程启动 ;

             ②、启动后它会扫描系统中所有应用程序Apk包下的AndroidManifest.xml文件,然后解析所有的

           AndroidManifest.xml文件,继而形成一个庞大的信息结构树,并且保存在PackageManagerService 的相关属性下。

                 它会扫描这两个目录:

                                /system/app  ------------------> 系统应用程序

                                /data/app      ------------------> 第三方应用程序(所有安装的Apk包都会在该目录下保存一份拷贝)

          扫描完成后,于是所有的信息结构就构建了。PackageManagerService 的四个重要属性如下:

class PackageManagerService extends IPackageManager.Stub {//...//保存了所有Activity节点信息 。         自定义类// All available activities, for your resolving pleasure.final ActivityIntentResolver mActivities =        new ActivityIntentResolver();//保存了所有BroadcastReceiver节点信息  。  自定义类// All available receivers, for your resolving pleasure.final ActivityIntentResolver mReceivers =        new ActivityIntentResolver();//保存了所有Service节点信息。 。  自定义类// All available services, for your resolving pleasure.final ServiceIntentResolver mServices = new ServiceIntentResolver();//保存了所有ContentProvider节点信息 , 以Hash值保存// Keys are String (provider class name), values are Provider.final HashMap<ComponentName, PackageParser.Provider> mProvidersByComponent =        new HashMap<ComponentName, PackageParser.Provider>();    //...}

 

 

       值得注意这些属性类型的不同。Activity、BroadcastReceiver、Service都采用了自定义类去保存相关信息,从类名上看,
 类结构应该很相似。而ContentProvider只是简单的采用了HashMap键值对去保存了信息? 莫非有错 ? 我们回忆下
 AndroidManifest.xml定义组件信息时,Activity、BroadcastReceiver、Service都可以通过<intent-filter>去隐式匹配的,而
 ContentProvider只需要一个Uri数据即可找到对应的ContentProvider组件信息了。 因此才采用了这两种结构去保存信息。
 
 
       其实我们通过getPackageManager()方法获得的PackageManager对象,只是PackageManagerService的客户端,
  该客户端类是ApplicationPackageManager,它是ContextIml类的内部类,显然该类存在于用户空间中。
 
       源代码(部分)如下:
 
@Overridepublic PackageManager getPackageManager() {   //...   // Doesn't matter if we make more than one instance.   return (mPackageManager = new ApplicationPackageManager(this, pm));   //...}class ContextImpl extends Context {//.../*package*/static final class ApplicationPackageManager extends PackageManager{   //...   @Override       public ActivityInfo getActivityInfo(ComponentName className, int flags){   //...   }   }//...}

      它与PackageManagerService的简单关系如下:

                     
     
  三 、保存数据采用的数据结构和解析算法
 

     1、保存数据采用的数据结构

 
       毫无疑问,保存所有信息是一项很复杂的工程,在具体讲解匹配过程时,我们先看看系统为了保存这些结构定义的一些
   数据结构。
           
       IntentInfo类:继承至IntentFilter

               作用:保存了每个<intent-filter>节点信息

     ActivityIntentInfo类:继承至IntentInfo

               作用:保存了<activity />节点下的< intent-filter>节点信息

      ServiceIntentInfo:继承至IntentInfo

             作用:保存了<service />节点下的< intent-filter >节点信息

     Activity类:保存了<activity />节点信息

     Service类:保存了<service />节点信息


 

    PS:这些都是PackageParser类的内部类 。PackageParser的主要功能就是解析AndroidManifest.xml文件

 

      IntentResolver类:模板类,父类,保存了<activity/><service/><receiver />节点的共同信息。

      ActivityIntentResolver类:继承至IntentResolver类。

            作用:保存了所有<activity/>或者<receiver/>节点信息。(Activity或者BroadcastReceiver信息就是用该自定义类保存的)

     ServiceIntentResolver类:继承至IntentResolver类,保存了

            作用:保存了所有<service/>节点信息。(Service信息就是用该自定义类保存的)。

 

  一个简单的UML图表示如下:  

                                                  

 

   

     2、Intent解析采用的算法

        不同的数据结构决定了不同的算法,而不同的算法又决定着性能,例如时间复杂度以及空间复杂度等。 在具体讲解解析

  采用的算法时,我们先理解下这个情景。

        假设一个女人决定参加一个相亲节目(大家可以理解成《非诚勿扰》),然后她向主办方提出如下条件:       

                1、身高 ?     175cm以上 ;

                        2、 财富 ?     100万 ;

                        3、 学历 ?     本科以上 ;

                           ……

 

     主办发经理一看,你丫的要求还真高。但客户是万能的,该经理也只能去找到满足这些条件的男人咯。

 

       最开始,该经理是这么想的,我把所有男的都给遍历一遍,肯定把满足这些条件的男人给揪出来。他找啊找,觉得这么找下去

  是不是太二B了(呵呵,你也是这么想的吗?)。经理就开始想:“我为什么不能根据这些条件把所有男的给分成三个种群呢?有钱

  的男人在一起,高个子的男人在一起,高学历的男人在一起,这样查找起来不是更快吗 ? “


      于是,有了如下的划分:     PS, 你是属于哪一类额 ? 


                      二B算法(没有分类)                                                                                    高级点的算法(分类后)

                   


 

       可能大家对这种根据关键值分类的好处不能一目了然。我们举个一般例子吧:

           假设当前共有100个男的。 其中有钱的有20人,高个子有30人,高学历男人有10人。


       根据第一种算法分类,我们需要比较100次,而第二种算法我们总共只需要比较60次。从整个基数来分析,算法肯定优化了

       最后,对不同的分类中查询的结果进行组合重新排列下,即可得到我们的满足该女性的要求。

          

        同样的,在进行Intent匹配时,Android也采用了第二种方法来进行算法匹配。它根据一些关键值Action、MimeType、

   Schema字段去进行分类。分类之后的集合大致如下:


                               


       于是在进行具体匹配时,我们只是需要根据关键值从不同集合中获取即可。


       事实上,由于MimeType的通配符(*)的特性,它的匹配可以说是最难的。参考IntentResolver类,真正的关键值如下:

//模板类 F类型可能为ActivityIntentInfo或ServiceIntentInfo,R对象是ResolverInfo类public class IntentResolver<F extends IntentFilter, R extends Object> {//保存了所有<intent-filter>节点信息//All filters that have been registered. private final HashSet<F> mFilters = new HashSet<F>();/** All of the MIME types that have been registered, such as "image/jpeg", * "image/*", or "{@literal *}/*". *///关键值表示MimeType形如:  image/jpeg 、 image/*、/*  类型private final HashMap<String, ArrayList<F>> mTypeToFilter /** * The base names of all of all fully qualified MIME types that have been * registered, such as "image" or "*".  Wild card MIME types such as * "image/*" will not be here. *///关键值表示MimeType形如:image、 image/*、*  类型private final HashMap<String, ArrayList<F>> mBaseTypeToFilter/** * The base names of all of the MIME types with a sub-type wildcard that * have been registered.  For example, a filter with "image/*" will be * included here as "image" but one with "image/jpeg" will not be * included here.  This also includes the "*" for the "{@literal *}/*" * MIME type. *///这个关键字段表示MimeType形如 :image、* 类型private final HashMap<String, ArrayList<F>> mWildTypeToFilter //All of the URI schemes (such as http) that have been registered.//关键值字段表示Schemaprivate final HashMap<String, ArrayList<F>> mSchemeToFilter /** * All of the actions that have been registered, but only those that did * not specify data. *///关键值字段表示:Actionprivate final HashMap<String, ArrayList<F>> mActionToFilter//All of the actions that have been registered and specified a MIME type.//关键值字段表示:Action和MimeType。 即该<intent-filter>节点必须包含action和MimeTypeprivate final HashMap<String, ArrayList<F>> mTypedActionToFilter}

 

      于是,通过这些关键字段我们可以去特定集合去查找,最后将结果在重新组合下,那不就万事大吉了。


      最后,我们通过代码走读的方式,以一个查询Activity的信息的方法,带领大家去熟悉具体流程。该方法原型为:

          //通过给定的intent,查询所有匹配的Activity组件信息

               abstract List<ResolveInfo>   queryIntentActivities(Intent intent, int flags) 

 

 

        PS:其实查询Activity、Service、BroadcastReceiver的流程基本上是相同的。

 

 

       Step 1、获取PackageManager代理对象,调用该方法:

PackageManager mPackageManger = this.getPackageManager() ;//为了说明,这儿我们简单查询一个Intent对象,即所有应用程序的启动ActivityIntent mainIntent = new Intent() ;mainIntent.setAction(Intent.ACTION_MAIN);mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);mPackageManger.queryIntentActivities(mainIntent, 0);   //获取PackageManager对象   @Override   public PackageManager getPackageManager() {   //...   IPackageManager pm = ActivityThread.getPackageManager();       if (pm != null) {           // Doesn't matter if we make more than one instance.           return (mPackageManager = new ApplicationPackageManager(this, pm));       }   }   

       Step 2、该PackageManager代理对象实则为ApplicatonPackageManager 对象,该对象是ContextIml的内部类。

@Override        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {            try {            //mPM对象就是PackageManagerService的客户端                return mPM.queryIntentActivities(                    intent,                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),                    flags);            } catch (RemoteException e) {                throw new RuntimeException("Package manager has died", e);            }        }

 

       Step 3、调用服务端PackageManagerService对象的对应方法。该方法位于PackageManagerService.java类中

    public List<ResolveInfo> queryIntentActivities(Intent intent,            String resolvedType, int flags) {    //是否设置了组件ComponetName 信息    ComponentName comp = intent.getComponent();        if (comp != null) {            List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);            ActivityInfo ai = getActivityInfo(comp, flags);            if (ai != null) {                ResolveInfo ri = new ResolveInfo();                ri.activityInfo = ai;                list.add(ri);            }            return list;        }        synchronized (mPackages) {        //是否设置了包名            String pkgName = intent.getPackage();            if (pkgName == null) {                //调用mActivities去查询            return (List<ResolveInfo>)mActivities.queryIntent(intent,                        resolvedType, flags);            }            PackageParser.Package pkg = mPackages.get(pkgName);            if (pkg != null) {                return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent,                        resolvedType, flags, pkg.activities);            }            return null;        }     }


     首先、该方法判断IntentComponentName是否存在,如果存在则为显示匹配了,直接返回特定组件相关信息;

     其次、判断是否设置了包名,即packageName,如果没有指定包名,则查询所有的应用程序包去找匹配的组件信息。如果

   指定了packageName,则去指定包下去查找;

     接着,调用特定的类继续查找。由于我们找的是Activity组件信息,因此去ActivityIntentResolver类去查找。

 

     Step 4、调用mActivities自定义类去查找

 

//调用父类IntentResolver方法去查找   public List queryIntent(Intent intent, String resolvedType, int flags) {        mFlags = flags;        return super.queryIntent(intent, resolvedType,            (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);   }

        该过程只是简单的调用了父类IntentResolver去查找。

 

     Step5 、进入IntentResolver类去真正的实现查找,该方法为于IntentResolver.java类中。

 

   public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {        String scheme = intent.getScheme();        //用来保存查找到的组件信息,如Activity等        ArrayList<R> finalList = new ArrayList<R>();        //根据关键值去特定集合查询到的一个可能结果        ArrayList<F> firstTypeCut = null;        ArrayList<F> secondTypeCut = null;        ArrayList<F> thirdTypeCut = null;        ArrayList<F> schemeCut = null;        //首先是否制定的数据类型 MimeType        // If the intent includes a MIME type, then we want to collect all of        // the filters that match that MIME type.        if (resolvedType != null) {            int slashpos = resolvedType.indexOf('/');            if (slashpos > 0) {                final String baseType = resolvedType.substring(0, slashpos);                if (!baseType.equals("*")) {                //匹配特定的MimeType                    if (resolvedType.length() != slashpos+2|| resolvedType.charAt(slashpos+1) != '*') {                        firstTypeCut = mTypeToFilter.get(resolvedType);                        secondTypeCut = mWildTypeToFilter.get(baseType);                    }                     //...            }        }        //根据模式去匹配特定的集合        if (scheme != null) {            schemeCut = mSchemeToFilter.get(scheme);        }        //可能的话在去匹配Action所在集合        if (resolvedType == null && scheme == null && intent.getAction() != null) {            firstTypeCut = mActionToFilter.get(intent.getAction());        }        //对我们前面通过关键字查询的一个集合,在此循环遍历匹配,将匹配到的结果保存在finalList集合中        if (firstTypeCut != null) {            buildResolveList(intent, debug, defaultOnly,                    resolvedType, scheme, firstTypeCut, finalList);        }        if (secondTypeCut != null) {            buildResolveList(intent, debug, defaultOnly,                    resolvedType, scheme, secondTypeCut, finalList);        }        if (thirdTypeCut != null) {            buildResolveList(intent, debug, defaultOnly,resolvedType, scheme, thirdTypeCut, finalList);        }        if (schemeCut != null) {            buildResolveList(intent, debug, defaultOnly,                    resolvedType, scheme, schemeCut, finalList);        }        //根据IntentFilter的一些优先级进行排序        sortResults(finalList);        return finalList;    }

     buildResolveList()方法的主要作用是将可能的集合在循环遍历,将匹配的结果值保存在finalList集合中。

   该方法为于IntentResolver.java类中,方法原型如下:

 

//通过前面关键字查找的可能集合,循环遍历进行匹配,匹配成功就加入到dest集合中,即finalList集合中    private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly,            String resolvedType, String scheme, List<F> src, List<R> dest) {        Set<String> categories = intent.getCategories();        final int N = src != null ? src.size() : 0;        boolean hasNonDefaults = false;        int i;        for (i=0; i<N; i++) {            F filter = src.get(i);            int match;            //是否已经加入到匹配结果中去了,不允许重复添加            // Do we already have this one?            if (!allowFilterResult(filter, dest)) {                continue;            }            //调用Intent-filter方法去匹配该Intent信息            match = filter.match(                    intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);            //匹配成功,就存放在finalList集合中            if (match >= 0) {                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {                //调用子类的newResult()方法去返回一个ResolvInfo对象                    final R oneResult = newResult(filter, match);                    if (oneResult != null) {                        dest.add(oneResult);                    }                } else {                    hasNonDefaults = true;                }            } else {                //...            }        }        //...    }   

    这个函数的逻辑判断如下:


        首先、通过给定的关键字去特定集合查询一个可能的匹配集合,然后将这些集合信息保存在如下集合中:

                          ArrayList<F>firstTypeCut =null;

                          ArrayList<F>secondTypeCut =null;

                          ArrayList<F>thirdTypeCut =null;

                          ArrayList<F>schemeCut =null;

      

       然后、连续四次调用buildResolveList()去进行匹配。每次调用结束后,参数finalList保存的是匹配结果的累加值,所以这

    四次调用过程中finalList集合包含的结果是一次累加的过程。当然了,四次连续调用buildResolveList()的次序可以不分先后。

 

       最后、调用sortResults()从匹配集合中进行一些排序等。



         总结:第二部分通过重点介绍了PackageManagerService的功能匹配Intent采用的数据结构和算法,也只是简单入了下门,希

   望大家能参考源码,认真理解Intent匹配过程。







 

 

    

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宇飞来手机锁了怎么办 宇飞来手机忘记密码怎么办 vivo手机突然开不了机怎么办 苹果六关不了机怎么办 微信老是无响应怎么办 打游戏被骂了怎么办 微信运动跳转排行榜失败怎么办 小恩爱账号忘了怎么办 华为账号更换后游戏账号怎么办 注册游戏账号需要身份证怎么办 英雄联盟被裁决了怎么办 百度网盘密码重置不了怎么办 sap密码输入被锁怎么办 dnf二级密码错10怎么办 大网卡网速慢了怎么办 小米路由器无线速度慢怎么办 小米手机无线速度慢怎么办 电脑网卡驱动没了怎么办 电脑显示网卡驱动不正常怎么办 微信别人拒收消息怎么办 电脑无线网卡速度慢怎么办 网吧吃鸡更新慢怎么办 手机号注册不了微信怎么办 小米账号密码忘了怎么办 小米手机账号密码忘了怎么办 华为手机账号密码忘记了怎么办 老年机开不了机怎么办 天谕没有顺网登陆怎么办 苹果密保问题忘了怎么办 密保手机没用了怎么办 qq密保手机没用了怎么办 手机开机按钮坏了怎么办 改了账号游戏角色消失怎么办 华为开机键坏了怎么办 抖音账号已重置怎么办 抖音账号被重置怎么办 吃鸡账号密码忘了怎么办 微信只记得账号忘了手机号怎么办 红米3开机键失灵怎么办 晚自习教室有许多虫子怎么办 泰迪吃草又呕吐怎么办