最近由于工作上的需要,研究了一下framework层面的东西。收获良多,感受颇深啊。
在 设置->安全 中选择屏幕锁定,选择屏幕锁定方式为密码,然后勾选显示密码选项,此时应该在输入密码时,先显示输入的密码,过1.5S后将变为小圆点,若取消显示密码则输入直接为小圆点。
而由于“前人”对锁屏的改动造成取消显示密码后,还是先显示密码才跳为小圆点,这是我要解决的BUG。
研究后发现“前人”将 Keyguard 中 com.android.keyguard.KeyguardPasswordView 类 106 行改变了Textview的inputtype,将其恢复后BUG得以解决。
Why?我一头雾水,甚至都不知道是在哪把输入的字符变成小圆点的!接下来就开始了我的求知之旅。
首先找到Settings中对应显示密码的代码。位于 com.android.settings.SecuritySettings ,找到575行
Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,mShowPassword.isChecked() ? 1 : 0);
Settings.System.TEXT_SHOW_PASSWORD,这是存储按钮是否选中的key,于是在Keyguard工程中全局搜索,居然找不到~顿时语塞。
于是回到锁屏页面,发现输入框的EditView调用了一个 setKeyListener(TextKeyListener.getInstance()); 方法,来到 TextKeyListener 末尾看到两个方法
private void updatePrefs(ContentResolver resolver) {
boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
mPrefs = (cap ? AUTO_CAP : 0) |
(text ? AUTO_TEXT : 0) |
(period ? AUTO_PERIOD : 0) |
(pw ? SHOW_PASSWORD : 0);}
/* package */
int getPrefs(Context context) {
synchronized (this) {
if (!mPrefsInited || mResolver.get() == null) {
initPrefs(context);
}
}
return mPrefs;
}
可以看到 mPrefs 的值是会被 System.TEXT_SHOW_PASSWORD 影响的。看来只能到TextView源码中看了。在Textview源码中搜索 mPrefs 无果,再搜索 System.TEXT_SHOW_PASSWORD 还是无果。
最后只能在网站 http://androidxref.com/ 中搜索 mPrefs ,定位到类 PasswordTransformationMethod 中。看了老半天,结合好几个类,大概查出了点思路。
在setting中勾选显示密码后,Settings.System.TEXT_SHOW_PASSWORD 的值将变为1。
回到Textview中,若我们设置 android:inputType=”textPassword” 或在代码中设置 setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);后 |
来到TextView的3987行
public void setInputType(int type) {
final boolean wasPassword = isPasswordInputType(getInputType());
final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
setInputType(type, false);
final boolean isPassword = isPasswordInputType(type);
final boolean isVisiblePassword = isVisiblePasswordInputType(type);
boolean forceUpdate = false;
if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
} else if (isVisiblePassword) {
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
} else if (wasPassword || wasVisiblePassword) {
// not in password mode, clean up typeface and transformation
setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
}
boolean singleLine = !isMultilineInputType(type);
// We need to update the single line mode if it has changed or we
// were previously in password mode.
if (mSingleLine != singleLine || forceUpdate) {
// Change single line mode, but only change the transformation if
// we are not in password mode.
applySingleLine(singleLine, !isPassword, true);
}
if (!isSuggestionsEnabled()) {
mText = removeSuggestionSpans(mText);
}
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) imm.restartInput(this);
}
将判断isPassword为true,于是设置 setTransformationMethod(PasswordTransformationMethod.getInstance()); 看到1702行setTransformationMethod方法,其实是将其赋给mTransformation这个变量。
重点来了,看到setText方法中,类的第3771行起
if (mTransformation == null) {
mTransformed = text;
} else {
mTransformed = mTransformation.getTransformation(text, this);
}
我们可以看到 mTransformation将输入的text转换了一遍,那么这个方法在PasswordTransformationMethod是怎样的呢,源码如下:
public CharSequence getTransformation(CharSequence source, View view) {
if (source instanceof Spannable) {
Spannable sp = (Spannable) source;
/*
* Remove any references to other views that may still be
* attached. This will happen when you flip the screen
* while a password field is showing; there will still
* be references to the old EditText in the text.
*/
ViewReference[] vr = sp.getSpans(0, sp.length(),
ViewReference.class);
for (int i = 0; i < vr.length; i++) {
sp.removeSpan(vr[i]);
}
removeVisibleSpans(sp);
sp.setSpan(new ViewReference(view), 0, 0,
Spannable.SPAN_POINT_POINT);
}
return new PasswordCharSequence(source);
}
最后返回了一个 PasswordCharSequence(source); 进一步跟踪PasswordCharSequence类,方法charAt源码:
public char charAt(int i) {
if (mSource instanceof Spanned) {
Spanned sp = (Spanned) mSource;
int st = sp.getSpanStart(TextKeyListener.ACTIVE);
int en = sp.getSpanEnd(TextKeyListener.ACTIVE);
if (i >= st && i < en) {
return mSource.charAt(i);
}
Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
for (int a = 0; a < visible.length; a++) {
if (sp.getSpanStart(visible[a].mTransformer) >= 0) {
st = sp.getSpanStart(visible[a]);
en = sp.getSpanEnd(visible[a]);
if (i >= st && i < en) {
return mSource.charAt(i);
}
}
}
}
return DOT;
}
发现了没!原来圆点是从这里来的!如果判断Textview的方式为Password时,在SetText方法中就通过PasswordTransformationMethod将其转换为小圆点。
那显示密码过1.5S后变为圆点又是如何实现的呢!?
看到 PasswordTransformationMethod 中的 onTextChanged 方法,重要代码如下:
int pref = TextKeyListener.getInstance().getPrefs(v.getContext());
if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) {
if (count > 0) {
removeVisibleSpans(sp);
if (count == 1) {
sp.setSpan(new Visible(sp, this), start, start + count,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
每次输入时都会调用onTextChanged 方法,并判断显示密码是否选中,若为选中状态,则 removeVisibleSpans ,将之前没有变成点的字符变成点,然后将Visible作为Span添加到末尾,我们来看下Visible是怎么写的
private static class Visible extends Handler
implements UpdateLayout, Runnable{
public Visible(Spannable sp, PasswordTransformationMethod ptm) {
mText = sp;
mTransformer = ptm;
postAtTime(this, SystemClock.uptimeMillis() + 1500);
}
public void run() {
mText.removeSpan(this);
}
private Spannable mText;
private PasswordTransformationMethod mTransformer;
}
一切如此明了,Visible将添加的字符显示出来,并在1.5S后将其remvoe,变回原来设置的小圆点!
至此,锁屏密码中“显示密码”选项是怎样工作的 已经能摸出个大概了。其中还有很多猜测和不了解深入的地方,还请大神多多拍砖指正。
第一次写解析文章,水平有限大家还请见谅~不过能看到这还是很感谢大家的耐心,如有什么问题请留言~人多力量大!