android intent以及Dart&Henson

一、显式Intent和隐式Intent

参考
Android理解:显式和隐式Intent
Android中Intent概述及使用

显式的Intent:如果Intent中明确包含了要启动的组件的完整类名(包名及类名),那么这个Intent就是explict的,即显式的。使用显式Intent最典型的情形是在你自己的App中启动一个组件,因为你自己肯定知道自己的要启动的组件的类名。比如,为了响应用户操作通过显式的Intent在你的App中启动一个Activity或启动一个Service下载文件。
隐式的Intent:如果Intent没有包含要启动的组件的完整类名,那么这个Intent就是implict的,即隐式的。虽然隐式的Intent没有指定要启动的组件的类名,但是一般情况下,隐式的Intent都要指定需要执行的action。一般,隐式的Intent只用在当我们想在自己的App中通过Intent启动另一个App的组件的时候,让另一个App的组件接收并处理该Intent。例如,你想在地图上给用户显示一个位置,但是你的App又不支持地图展示,这时候你可以将位置信息放入到一个Intent中,然后给它指定相应的action,通过这样隐式的Intent请求其他的地图型的App(例如Google Map、百度地图等)来在地图中展示一个指定的位置。隐式的Intent也体现了Android的一种设计哲学:我自己的App无需包罗万象所有功能,可以通过与其他App组合起来,给用户提供很好的用户体验。而连接自己的App与其他App的纽带就是隐式Intent。

  • 显式Intent
    第一个参数是活动上下文,第二个是目标活动的class
    FirstActivity:
button.setOnClickListener(new OnClickListener(){
   public void onClick(View v){
      Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
      startActivity(intent);
   }
});
  • 隐式Intent
    由系统分析Intent,并找出合适的activity去启动。
    FirstActivity
button.setOnClickListener(new OnClickListener(){
   public void onClick(View v){
     Intent intent = new Intent("com.example.activitytest.ACTION_START");
     //setAction方式是一样的
     //Intent intent = new Intent();
     //intent.setAction("com.example.activitytest.ACTION_START");
     intent.addCategory("com.example.activitytest.MY_CATEGORY");
     startActivity(intent);
   }
});

SecondActivity

<activity android:name="SecondActivity">
   <intent-filter>
     <!-- 意图过滤器, 只要满足了action(动作), 和 category(分类),那么就启动这个界面 -->
     <action android:name="com.example.activitytest.ACTION_START"/>
     <category android:name="com.example.activitytest.MY_CATEGORY"/>
     <category android:name="android.intent.category.DEFAULT"/>
     //default category一定要有,这是默认的
</intent-filter>

通过设置Action字符串,表明自己的意图,即我想干嘛,需要由系统解析,找到能够处理这个Intent的Activity并启动。比如我想打电话,则可以设置Action为"android.intent.action.DIAL"字符串,表示打电话的意图,系统会找到能处理这个意图的Activity,例如调出拨号面板。

有几点需要注意:
1、这个Activity其他应用程序也可以调用,只要使用这个Action字符串。这样应用程序之间交互就很容易了,例如手机QQ可以调用QQ空间,可以调用腾讯微博等。因为如此,为了防止应用程序之间互相影响,一般命名方式是包名+Action名,例如命名"abcdefg"就很不合理了,就应该改成"com.example.app016.MyTest"。
2、当然,你可以在自己的程序中调用其他程序的Action。
例如可以在自己的应用程序中调用拨号面板:

Intent intent = new Intent(Intent.ACTION_DIAL);  
// 或者Intent intent = new Intent("android.intent.action.DIAL");  
// Intent.ACTION_DIAL是内置常量,值为"android.intent.action.DIAL"  
startActivity(intent);

3、一个Activity可以处理多种Action
只要你的应用程序够牛逼,一个Activity可以看网页,打电话,发短信,发邮件。。。当然可以。
Intent的Action只要是其中之一,就可以打开这个Activity。

<activity  
    android:name="com.example.app016.SecondActivity">  
    <intent-filter>  
        <!-- 可以处理下面三种Intent -->  
        <action android:name="com.example.app016.SEND_EMAIL"/>  
        <action android:name="com.example.app016.SEND_MESSAGE"/>  
        <action android:name="com.example.app016.DAIL"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</activity>  

对于一个Action字符串,系统有可能会找到一个Activity能处理这个Action,也有可能找到多个Activity,也可能一个都找不到。
1、找到一个Activity
很简单,直接打开这个Activity。这个不需要解释。
2、找到多个Acyivity
系统会提示从多个activity中选择一个打开。
例如我们自己开发一个拨号面板应用程序,可以设置activity的<intent-filter>中Action name为"android.intent.action.DIAL",这样别的程序调用拨号器时,用户可以从Android自带的拨号器和我们自己开发的拨号器中选择。

<activity  
    android:name="com.example.app016.SecondActivity">  
    <intent-filter>  
        <action android:name="android.intent.action.DIAL"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</activity>  

这也就是当Android手机装上UC浏览器后,打开网页时会弹出选择Android自带浏览器还是UC浏览器,可能都会遇到过。
3、一个Activity都没找到
一个都没找到的话,程序就会出错,会抛出ActivityNotFoundException。比如随便写一个action字符串:

Intent intent = new Intent("asasasas");  
startActivity(intent);  

所以应该注意try catch异常。

Intent intent = new Intent("asasasas");  
try  
{  
    startActivity(intent);  
}  
catch(ActivityNotFoundException e)  
{  
    Toast.makeText(this, "找不到对应的Activity", Toast.LENGTH_SHORT).show();  
}  

或者也可以使用Intent的resolveActivity方法判断这个Intent是否能找到合适的Activity,如果没有,则不再startActivity,或者可以直接禁用用户操作的控件。

Intent intent = new Intent(Intent.ACTION_DIAL);  
if(intent.resolveActivity(getPackageManager()) == null)  
{  
    // 设置控件不可用  
}  

注意resolveActivity方法返回值就是显式Intent上面讲到的ComponentName对象,一般情况下也就是系统找到的那个Activity。但是如果有多个Activity可供选择的话,则返回的Component是com.android.internal.app.ResolverActivity,也就是用户选择Activity的那个界面对应的Activity,这里不再深究。

Intent intent = new Intent(Intent.ACTION_DIAL);  
ComponentName componentName = intent.resolveActivity(getPackageManager());  
if(componentName != null)  
{  
    String className = componentName.getClassName();  
    Toast.makeText(this, className, Toast.LENGTH_SHORT).show();  
}  

当创建了一个显式Intent去启动Activity或Service的时候,系统会立即启动Intent中所指定的组件。

当创建了一个隐式Intent去使用的时候,Android系统会将该隐式Intent所包含的信息与设备上其他所有App中manifest文件中注册的组件的Intent Filters进行对比过滤,从中找出满足能够接收处理该隐式Intent的App和对应的组件。如果有多个App中的某个组件都符合条件,那么Android会弹出一个对话框让用户选择需要启动哪个App。

Intent Filter,即Intent过滤器,一个组件可以包含0个或多个Intent Filter。Intent Filter是写在App的manifest文件中的,其通过设置action或uri数据类型等指明了组件能够处理接收的Intent的类型。如果你给你的Activity设置了Intent Filter,那么这就使得其他的App有可能通过隐式Intent启动你的这个Activity。反之,如果你的Activity不包含任何Intent Filter,那么该Activity只能通过显式Intent启动,由于我们一般不会暴露出我们组件的完整类名,所以这种情况下,其他的App基本就不可能通过Intent启动我们的Activity了(因为他们不知道该Activity的完整类名),只能由我们自己的App通过显式Intent启动。

需要注意的是,为了确保App的安全性,我们应该总是使用显式Intent去启动Service并且不要为该Service设置任何的Intent Filter。通过隐式的Intent启动Service是有风险的,因为你不确定最终哪个App中的哪个Service会启动起来以响应你的隐式Intent,更悲催的是,由于Service没有UI的在后台运行,所以用户也不知道哪个Service运行了。

二、传递数据
  • 向下一个活动传递数据
    FirstActivity:
button.setOnClickListener(new OnClickListener(){
   public void onClick(View v){
     Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
     ***intent.putExtra("extra_data","Hello");***
     startActivity(intent);
   }
});

SecondActivity

protected void onCreate(Bundle savedInstanceState){
   super.onCreate(savedInstanceState);
   Intent intent = getIntent();
   String data = intent.getStringExtra("extra_data");
   //getIntExtra  getBooleanExtra 取值方式与传递的数据要一致
}
  • 返回数据给上一个活动
    startActivityForResult方法也是用于启动活动的,但这个方法期望在活动销毁时能够返回结果给上一个活动。
    FirstActivity:
button.setOnClickListener(new OnClickListener(){
   public void onClick(View v){
     Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
     startActivityForResult(intent,1);//请求码
   }
});
//使用startActivityForResult启动活动,会在销毁后回调onActivityResult方法
protected void onActivityResult(int requestCode,int resultCode,Intent data){
   switch(requestCode){
   case 1:
     if(resultCode == RESULT_OK){ 
       String returnData = data.getStringExtra("data_return");
       ....
   }
   }
}

SecondActivity

   Intent intent = new Intent();
   ***intent.putExtra("data_return","Hello");***
   setResult(RESULT_OK,intent);//专门用于向上一个活动返回数据
}

利用Intent的Extra部分来存储我们想要传递的数据,可以传送int, long, char等一些基础类型,对复杂的对象就无能为力了。

传复杂对象参考
Android Bundle总结
Android五种数据传递方法汇总
Intent传递对象——Serializable和Parcelable区别
Android系统中Parcelable和Serializable的区别

1.使用Serializable很简单,指定Person类implements Serializable即可。注意,要指定serialVersionUID,否则反序列化会出问题。
可参考如何让Android Studio 自动生成 serialVersionUID

Person person = new Person();
person.setName("tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);

SecondActivity:
Person person = (Person)getIntent().getSerializableExtra("person_data");

2.Parcelable稍微复杂一些,要实现几个方法。

public class Person implements Parcelable{
   private String name;
   private int age;
   
   public int describeContents(){
      return 0;
   }
   
   public void writeToParcel(Parcel dest,int flags){
      dest.writeString(name);
      dest.writeInt(age);
   }
   
   public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){
      public Person createFromParcel(Parcel source){
         Person person = new Person();
         person.name = source.readString();
         person.age = source.readInt();
         return person;
      }
      
      public Person[] newArray(int size){
         return new Person[size];
      }
   };
SecondActivity:
Person person = (Person)getIntent().getPacelableExtra("person_data");
三、Dart&Henson

参考
【译】使用 Dart & Henson 改进 Android Intents
用Dart&Henson玩转Activity跳转
上面传递数据的方式很好地处理了组件的创建与通信,但仍然有一些问题需要注意:

  • 目标组件作为一个实体,对输入没有任何控制权。在我们的例子里,itemId 是必需的,但如果没有传递它,DetailActivity 最好的处理方式也只能是抛出异常。
  • Intent 的创建(完全)不够健壮,并没有对 extra 中的 key 或 value 做任何检查。

解决这些问题的一个可能的方案是 Intent 工厂模式。它主要由一些工厂方法组成,包含了应用程序里用到的各种 Intent。比如像下面这样的 Intent 工厂:

public class IntentFactory {
  public Intent newDetailActivityIntent(Context context, String itemId, boolean showMap) {
    Intent intent = new Intent(context, DetailActivity.class);
    intent.putExtra(EXTRA_ITEM_ID, itemId);
    intent.putExtra(EXTRA_SHOW_MAP, showMap);
    return intent;
  }
  ...
}

然而,这种解决方案有一些局限,并不是一个很好的办法。

  • Intent 工厂是一个集中类,这个类可能会变得很大且复杂。
  • 它违背了开放/闭合原则。对修改并没有关闭,我们将总是需要给每个新的 Activity 添加一个新方法。
  • 目标组件应该是唯一知晓参数细节与逻辑的地方。
  • 可选参数处理。同一个组件有不同的需求,是否应该写不同的方法?还是写一个方法并使用默认值?
    它会诱使后续的开发人员模仿,进而产生其他的 Intent 工厂,最终演变成大泥球模式,使得代码越来越糟。

有一个类似的策略可以分散这些工厂方法到各自的目标组件。也就是指,每个组件可以包含一个(或多个)静态方法,用于生成这些启动它自身的 Intent。这个办法可以解决开放/闭合原则的问题,分解 Intent 工厂,也许还可以避免大泥球模式。尽管如此,关于可选参数的问题依然存在。有人说 builder 模式可以?我们自己实现它?...

Dart 是一个 Android 开源库(Henson实际是Dart项目的子项目)。它绑定 Activity 字段到 Intent extra,Butter Knife 也是用类似的方案,关联 Activity 与 XML 布局中的View。

这里我们假设要从MainActivity跳转到DetailActivity,DetailActivity中要接受三个参数分别是String name,int age和User user。DetailActivity需要接受上述三个参数,仅仅通过@InjectExtra注解即可,然后在onCreate
中执行Dart.inject(this),详细的代码为:

public class DetailActivity extends AppCompatActivity {
    @InjectExtra
    String name = "default name";

    @InjectExtra
    int age = 0;

    @Nullable
    @InjectExtra
    User user;

    @BindView(R.id.tvName)
    TextView tvName;

    @BindView(R.id.tvAge)
    TextView tvAge;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        ButterKnife.bind(this);
        Dart.inject(this);
        initView();
    }

    private void initView() {
        // 使用name,age,user
    }
}

String name = "default name";这句话给了name一个默认值,但是当Dart.inject执行后会被传递过来的数据覆盖。
@Nullable加在了user上说明这个数据可以不用传递。接受端就这么多代码,下面让我们看看发送端如果发送数据,如何跳转到DetailActivity。

注意编写上述代码后,我们要先编译下项目,编译好后Henson会通过注解处理器生成可以跳转到DetailActivity的DSL(领域特定语言)段,方便其他组件对DetailActivity的跳转,我们在MainActivity上只要写上下列代码,即可完成界面跳转和数据传递:

User user = new User();
user.setAge(Integer.parseInt(age.getText().toString()));
user.setName(name.getText().toString());
startActivity(
        Henson.with(this)
                .gotoDetailActivity()
                .age(27)
                .name("jason")
                .user(user)
                .build()
);

当你写完Henson.with(this)后代码提示会自动弹出.gotoDetailActivity(),Henson帮助你提示你DetailActivity是可以被跳转的;随后你继续写下.gotoDetailActivity()后,又自动弹出.age()方法,提示你传入一个int类型给age,写好后,又自动弹出.name()方法,以此类推,最后以一个build()收场。
注意,这里你可以不写.user(),因为在DetailActivity中我们指定它是nullable的,可以不传。但是.age()和.name()都是会强制弹出让你填写数据的。如果我偏向要对调name和age的传入顺序呢?发现不行~如果你写完.gotoDetailActivity()后发现只有.age()的提示,却没有.name()的,即强制要求你先传递age。这里作者说明后才知道他们是按照字幕顺序来排数据传入的,目前没有更好的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容