短信验证码作为一种验证方式,已经普遍存在于各种App中,通常由于网络、手机设置等各种原因,用户不能正常接收到App下发给用户的短信验证码,所以需要通过用户上送短信的方式来完成验证。最近在实现这个功能时遇到了有意思的事情,顺便研究下并记录下来。
1. 功能描述:App中提供按钮,点击时跳到短信编辑页面,将之前从服务器获取的目标号码和发送内容填充在该页面。
2. 功能实现:通过Intent跳转方式实现
1)使用Activity.startActivity()
/**
* 跳转至发送短信编辑界面,Activity跳转方式
* @param smsUpNumber
* @param smsContent
*/
private void sendSMS(String smsUpNumber,String smsContent)
{
Log.d("[RLIGHT]","send SMS, number " + smsUpNumber + " content " + smsContent);
Uri smsToUri = Uri.parse("smsto:" + smsUpNumber);
Intent intent = new Intent(Intent.ACTION_SENDTO, smsToUri);
intent.putExtra("sms_body", smsContent);
startActivity(intent);
}
2) 使用Context.startActivity()
/**
* 跳转至发送短信编辑界面,Context跳转方式
* @param smsUpNumber
* @param smsContent
*/
private void sendSMS1(String smsUpNumber,String smsContent)
{
Log.d("[RLIGHT]","send SMS 1, number " + smsUpNumber + " content " + smsContent);
Uri smsToUri = Uri.parse("smsto:" + smsUpNumber);
Intent intent = new Intent(Intent.ACTION_SENDTO, smsToUri);
intent.putExtra("sms_body", smsContent);
getApplicationContext().startActivity(intent);
}
就在使用Context的startActivity时,遇到了些有意思的问题,如下:
首先,在一台6.0的测试机上闪退,报错如下:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.rlight.smstest, PID: 12800
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
at android.app.ContextImpl.startActivity(ContextImpl.java:757)
at android.app.ContextImpl.startActivity(ContextImpl.java:737)
at android.content.ContextWrapper.startActivity(ContextWrapper.java:331)
at com.example.rlight.smstest.MainActivity.sendSMS1(MainActivity.java:82)
at com.example.rlight.smstest.MainActivity.access$200(MainActivity.java:13)
at com.example.rlight.smstest.MainActivity$2.onClick(MainActivity.java:36)
FLAG_ACTIVITY_NEW_TASK,查看API23的源码,发现也是说明了这一点的,如下:
/**
* Launch a new activity. You will not receive any information about when
* the activity exits.
*
* <p>Note that if this method is being called from outside of an
* {@link android.app.Activity} Context, then the Intent must include
* the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag. This is because,
* without being started from an existing Activity, there is no existing
* task in which to place the new activity and thus it needs to be placed
* in its own separate task.
*
* <p>This method throws {@link ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param intent The description of the activity to start.
* @param options Additional options for how the Activity should be started.
* May be null if there are no options. See {@link android.app.ActivityOptions}
* for how to build the Bundle supplied here; there are no supported definitions
* for building it manually.
*
* @throws ActivityNotFoundException
*
* @see #startActivity(Intent)
* @see PackageManager#resolveActivity
*/
public abstract void startActivity(Intent intent, @Nullable Bundle options);
简单的说,即如果是从一个外部的Context调用startActivity方法,对应的Intent必须包含FLAG_ACTIVITY_NEW_TASK启动标识。这是因为如果一个新Activity不是通过已经存在的Activity来启动的,则不存在task用于存放该新Activity,因此需要在自己的独立task中存放。因此解决这个问题,只需要添加如下代码:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK ); // 使用Context的startActivity需要如此设置
调用Activity中的startActivity则可以不用添加该标识,因为新Activity默认存在在调用Activity的task中,在对应源码中也说明了这一点,如下:
/**
* Launch a new activity. You will not receive any information about when
* the activity exits. This implementation overrides the base version,
* providing information about
* the activity performing the launch. Because of this additional
* information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
* required; if not specified, the new activity will be added to the
* task of the caller.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param intent The intent to start.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
* @see {@link #startActivity(Intent)}
* @see #startActivityForResult
*/
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
然而,到这里还没有结束,发现了两个奇怪的地方,如下:
不需要添加FLAG_ACTIVITY_NEW_TASK标识,查看对应API24、API25源码注释,注释中明确注明需要该标识,可是实际运行并不需要,这个问题暂时没找到合理的解释。
第二,为了实现每次调用发送不同验证码的效果,在点击并跳往短信编辑页面时,smsContent传入的内容每次都不同。当在短信界面按返回键返回至App中,再次调用sendSMS1方法跳往短信界面,编辑的内容更新了,然而,如果不是返回键,而是通过home键切换后台的方式切回App,再调用sendSMS1方法跳往短信界面,神奇的发现短信界面保留的是旧的内容而没更新。经过研究发现,这种问题可以通过增加Intent的Flag标识FLAG_ACTIVITY_NO_HISTORY来解决。如下:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY);
该标识的解释如下:
/**
* If set, the new activity is not kept in the history stack. As soon as
* the user navigates away from it, the activity is finished. This may also
* be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
* noHistory} attribute.
*
* <p>If set, {@link android.app.Activity#onActivityResult onActivityResult()}
* is never invoked when the current activity starts a new activity which
* sets a result and finishes.
*/
public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000;