原來(lái)Activity跳轉(zhuǎn)還可以這么舒暢

原創(chuàng)博客,如有轉(zhuǎn)載,請(qǐng)注明出處,非常感謝。

(前置說(shuō)明,我這里所說(shuō)的POJO,就是大家認(rèn)為的POJO去掉了getter/setter)
這里給大家?guī)?lái)的是一個(gè)開(kāi)源項(xiàng)目,托管在github上的:SmartKey,喜歡的同學(xué)請(qǐng)給個(gè)星星鼓勵(lì)一下,非常感謝。祝愿大家寫(xiě)代碼寫(xiě)得越來(lái)越舒暢,下班越來(lái)越準(zhǔn)時(shí)。

在開(kāi)源世界索取了這么多年,終于第一次往開(kāi)源界添點(diǎn)小小的磚瓦,甭提有多高興了,先預(yù)覽一下demo代碼感受一下,你見(jiàn)過(guò)這樣startActivity的嗎?不寫(xiě)key,不寫(xiě)Intent,不寫(xiě)putExtra,不寫(xiě)XXXActivity.class;不寫(xiě)key,不寫(xiě)Intent,不寫(xiě)putExtra,不寫(xiě)XXXActivity.class;不寫(xiě)key,不寫(xiě)Intent,不寫(xiě)putExtra,不寫(xiě)XXXActivity.class;重要的事情我要說(shuō)三遍,別攔我:

我有一個(gè)Activity,名字叫ForResultActivity,跳轉(zhuǎn)到他的時(shí)候是這樣調(diào)用的:

// Demo on how to elegantly passing and receiving extras with intent.
SmartTargets.toForResultActivityATarget()
        .params(ForResultReqIBuilder
                .newBuilder()
                .title(App.getStr(R.string.input_something))
                .label(App.getStr(R.string.this_is_label))
                .hint(App.getStr(R.string.this_is_hint)))
        .goForResult(activity, REQ_CODE_FOR_RESULT);

在目標(biāo)ForResultActivity里獲取傳過(guò)來(lái)的參數(shù)(其實(shí)是ForResultVM類(lèi)里,因?yàn)镈emo里用了最簡(jiǎn)單原始的MVVM模式)

// Getting values in this very easy way
ForResultReq req = ForResultReqIBuilder.getSmart(activity.getIntent());

** 套路就是SmartTargets出發(fā),to目標(biāo),帶參數(shù),走起!一個(gè)鏈條一氣呵成。 **
以上代碼里,SmartTargets類(lèi)是自動(dòng)生成的,F(xiàn)orResultReqIBuilder類(lèi)也是自動(dòng)生成的!??!你只需要關(guān)注你的業(yè)務(wù)數(shù)據(jù),參數(shù)究竟怎么放進(jìn)Intent 的,Intent是哪里創(chuàng)建的,extras的key怎么管理的,目標(biāo)Activity的class什么時(shí)候放進(jìn)Intent的,或者action,category什么時(shí)候放進(jìn)Intent的,extras取出來(lái)又是怎么取的,有完沒(méi)完啊,這么多問(wèn)題?。。『昧?,在這里,這些東西通通不需要關(guān)注,真的,你只需要關(guān)注你的業(yè)務(wù)數(shù)據(jù),并且,對(duì)于已有的代碼已有的key管理,你可以保留,這個(gè)庫(kù)直接與原有代碼兼容,無(wú)痛升級(jí)有木有!

好了,啰嗦了這么久,咱們?cè)敿?xì)說(shuō)說(shuō)這個(gè)SmartKey吧,首先得從我們最初是怎么進(jìn)行Activity跳轉(zhuǎn)說(shuō)起,先講點(diǎn)歷史,憶苦思甜嘛,必須的。相信大家一定走過(guò)類(lèi)似的路。
皇上,您還記得那年大明湖畔的夏雨荷嗎?哦,不好意思,大家還記得最初學(xué)Android開(kāi)發(fā)的時(shí)候怎么啟動(dòng)一個(gè)新的Activity嗎?來(lái),上代碼:

Intent in = new Intent(this, ForResultActivity.class);
in.putExtra("title", App.getStr(R.string.input_something));
in.putExtra("label", App.getStr(R.string.this_is_label));
in.putExtra("hint", App.getStr(R.string.this_is_hint));
startActivityForResult(in, REQ_CODE_FOR_RESULT);

大家還想回到那個(gè)年代么?那個(gè)年代我想回,但是我不想再寫(xiě)這樣的代碼了
putExtra這樣的模板代碼能不能不寫(xiě)?
extra的key能不能不寫(xiě)?看到這樣的“Magic Number/String”我就發(fā)毛,香菇,藍(lán)瘦。
就算寫(xiě),這些key從哪里找阿?每次去目標(biāo)Activity翻么?萬(wàn)一我拷貝的時(shí)候拷漏了一個(gè)字母怎么辦?好害怕啊。我真試過(guò)一個(gè)"nickName"和"nickname"這樣的拼寫(xiě)問(wèn)題,浪費(fèi)了不少時(shí)間,不要笑,說(shuō)的就是你。
于是來(lái)了第一個(gè)升級(jí)版(putExtra還是寫(xiě),畢竟通過(guò)Intent傳參你只有這么一條路可走):
在ForResultActivity的最頭部,我寫(xiě)了以下的代碼:

public static final String EXTRA_TITLE = "title";
public static final String EXTRA_LABEL = "label";
public static final String EXTRA_HINT = "hint";

然后啟動(dòng)ForResultActivity的時(shí)候變成了:

Intent in = new Intent(this, ForResultActivity.class);
in.putExtra(ForResultActivity.EXTRA_TITLE, App.getStr(R.string.input_something));
in.putExtra(ForResultActivity.EXTRA_LABEL, App.getStr(R.string.this_is_label));
in.putExtra(ForResultActivity.EXTRA_HINT, App.getStr(R.string.this_is_hint));
startActivityForResult(in, REQ_CODE_FOR_RESULT);

拼寫(xiě)錯(cuò)誤問(wèn)題解決了,隨著項(xiàng)目的擴(kuò)大,問(wèn)題又來(lái)了,我在MyActivity,YourActivity,HisActivity等等幾個(gè)Activity都用到同樣的key,我每個(gè)Activity都抄一次public static final String EXTRA_XXX嗎?相信沒(méi)誰(shuí)這么笨的。還有,有些activity的啟動(dòng)是通過(guò)action的,天啊,這些通過(guò)action跳轉(zhuǎn)的隱式Intent傳參key應(yīng)該放哪里?于是,又一個(gè)升級(jí)版,這次是集中管理extra的key:

public class MyExtraKeys {
    public static final String EXTRA_TITLE = "title";
    public static final String EXTRA_LABEL = "label";
    public static final String EXTRA_HINT = "hint";
    // 更多省略
}

key的問(wèn)題解決的差不多了,隱式action的問(wèn)題,這個(gè)也好解決,參照前面的MyExtraKeys的做法,我寫(xiě)了一個(gè)MyActionUtils類(lèi),因?yàn)楦厦娼咏?,就不?fù)述了,無(wú)非是通過(guò)一個(gè)相對(duì)友好的,容易調(diào)用的字段對(duì)應(yīng)到action。
下一步,我相信是很多高級(jí)點(diǎn)的工程師喜歡的做法,就是把跳轉(zhuǎn)的方法定義成一個(gè)靜態(tài)的方法,并且把參數(shù)組裝都放在目標(biāo)Activity上面,有多種方法的時(shí)候,寫(xiě)多個(gè)類(lèi)似的方法:

public static void toForResultActivity(Context from, String title, String label, String hint) {
    Intent in = new Intent(from, ForResultActivity.class);
    in.putExtra(MyExtraKeys.EXTRA_TITLE, title);
    in.putExtra(MyExtraKeys.EXTRA_LABEL, label);
    in.putExtra(MyExtraKeys.EXTRA_HINT, hint);
    if(!(from instanceof Activity)) {
        in.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    from.startActivity(in);
}
// 如果還有其他,繼續(xù)寫(xiě)

一般情況,到這一步,大家也就滿意了,然而,對(duì)于我,我還是覺(jué)得我高興的太早,因?yàn)槲业捻?xiàng)目終于大到一定程度了,并且也不止我一個(gè)人寫(xiě),由于經(jīng)過(guò)一段時(shí)間的積累,也由于對(duì)隊(duì)友的約束不嚴(yán),技術(shù)也在變化,剛才解決的是參數(shù)傳進(jìn)來(lái)問(wèn)題,但是還是有各種取出的方法散落在各處,這個(gè)時(shí)候哪怕一丁點(diǎn)的改動(dòng),都需要小心翼翼,什么時(shí)候掉進(jìn)自己挖的坑被埋了都不清楚,于是重構(gòu)提上日程,既然這些傳來(lái)傳去的參數(shù)已經(jīng)散落在了各自發(fā)揮作用的角落,我能不能先把他們集中起來(lái)作為重構(gòu)的第一階段呢?
于是,有一個(gè)升級(jí)版的參數(shù)傳遞方案出來(lái)了,把參數(shù)打包起來(lái)傳遞:

public class ForResultExtras implements Parcelable {
    private String title;
    private String label;
    private String hint;
    
    // getters and setters ...
    // Parcelable implementation methods ...
}

這種方式使用就統(tǒng)一了,不再有那么多的key了,因?yàn)槎际且粋€(gè)類(lèi)傳過(guò)來(lái),每個(gè)組件都只接收一個(gè)自定義的參數(shù),于是MyConstants里面關(guān)于參數(shù)的key只剩下了:

        public static final String EXTRA_KEY = "extra"; // 作為最通用的了

然而,這個(gè)方案有天生的問(wèn)題,既然取出參數(shù)的方法原來(lái)已經(jīng)散落各處了,現(xiàn)在要集中起來(lái)容易么?特別是我得寫(xiě)多少個(gè)類(lèi)似的Parcelable框架性代碼的實(shí)現(xiàn)???每個(gè)集中的類(lèi)都必須寫(xiě)網(wǎng)parcel里寫(xiě)數(shù)據(jù)和讀數(shù)據(jù)的方法,還幾乎一樣的,還要讀寫(xiě)順序一致。當(dāng)然了,這些都是小問(wèn)題了,github上搜索一下parceler你就知道了,有人幫你寫(xiě)好了包裝,你只要寫(xiě)POJO就行,剩余的框架代碼就不需要操心了。然而,集中起來(lái)這個(gè)問(wèn)題沒(méi)辦法解決啊,與framework組件、第三方組件不能改的代碼怎么打交道啊?這個(gè)方案還沒(méi)真正使用一次,就直接被斃掉了,難道就無(wú)解了么?不可能?。。〉亲约鹤鲋笆遣皇菃?wèn)問(wèn)啞巴有沒(méi)有什么好方案先呢?還真給我找到了一個(gè)非常不錯(cuò)的方案:
就是這個(gè)文章:用最優(yōu)雅的方式startActivity
具體就不列出來(lái)了,大家自己點(diǎn)開(kāi)看看,學(xué)習(xí)一下,人家寫(xiě)的很好,當(dāng)然,如果他的方案真的解決了我的問(wèn)題,那么就沒(méi)有這篇文章了,我寫(xiě)這篇文章是因?yàn)樗姆桨覆⒉荒芡瓿晌倚枰鉀Q的問(wèn)題而已,不是說(shuō)他寫(xiě)的不好,相反,我覺(jué)得相當(dāng)好也給我不少啟發(fā),我的寫(xiě)的方案SmartKey也多少受到他的一些影響,當(dāng)然,跟他的方案可以說(shuō)完全不同,先說(shuō)說(shuō)為我用上面那個(gè)文章說(shuō)的AutoGo的經(jīng)驗(yàn)吧,這個(gè)庫(kù)第一眼看到我就眼前一亮,以為終于找到完美的解決方案了,然后翻看源碼,不錯(cuò),著手引入到我的項(xiàng)目里,好了,不到三個(gè)鐘,我發(fā)現(xiàn)很多跟我項(xiàng)目八字不合的問(wèn)題了,簡(jiǎn)單說(shuō)來(lái)解決不了的問(wèn)題有以下,改成疑問(wèn)的語(yǔ)氣來(lái)寫(xiě)吧:

  1. 我原有的代碼怎么辦?我已經(jīng)有上百個(gè)activity了,與原有方式能共存么?也就是說(shuō),我有些extra的獲取是散落在各處的,一下子要集中并且取到字段里不容易,由于項(xiàng)目緊,一下子抽不出時(shí)間全改怎么辦?其實(shí)時(shí)間是借口,主要問(wèn)題是原邏輯非常復(fù)雜,貿(mào)然修改風(fēng)險(xiǎn)大,能緩一下,新寫(xiě)的跳轉(zhuǎn)進(jìn)來(lái)的代碼用新的方式寫(xiě),但是目標(biāo)activity不改動(dòng)行么?這個(gè)問(wèn)題沒(méi)解決。
  2. 跳轉(zhuǎn)到安卓framework提供的組件,或者第三方庫(kù)提供的組件,他們的key我改不了,他們的類(lèi)也是現(xiàn)成的,所以也生成不了方法鏈,怎么辦?這個(gè)問(wèn)題沒(méi)解決。
  3. 有些activity我需要用action來(lái)跳轉(zhuǎn),但我也想要用這個(gè)便利的方法鏈該怎么辦?一樣解決不了,因?yàn)樗淖⒔庑枰獙?xiě)在目標(biāo)Activity上,用action本來(lái)就是為了不明確知道目標(biāo)Activity的隱式情況的。
  4. 有些activity的跳轉(zhuǎn),事先是不知道activity類(lèi)名的,是根據(jù)服務(wù)器返回或其他配置值定的,我還是想用這個(gè)方法鏈怎么辦?還是沒(méi)有解決方法,因?yàn)樘D(zhuǎn)鏈條的開(kāi)頭就在目標(biāo)Activity。
  5. AutoGo有代管SharedPreferences的方法,但是不靈活,每個(gè)字段必須要注解,并且調(diào)用AutoGo.save和AutoGo.restore方法的時(shí)候,對(duì)應(yīng)對(duì)象的所有被托管的SharedPreferences都會(huì)被保存或者獲取,我能只保存更改了的那個(gè)么?或者只獲取我想要的那個(gè)么?畢竟多余的執(zhí)行都是浪費(fèi)資源啊。其實(shí)Intent也有同樣的問(wèn)題,需要把全部用到的需要傳遞字段注解,并且基本上幾個(gè)Activity有同樣參數(shù)的時(shí)候,必須寫(xiě)幾份,也就是說(shuō)寫(xiě)的代碼不能復(fù)用。
  6. 因?yàn)镾haredPreferences并沒(méi)有集中管理,散落在各個(gè)使用的角落,key重復(fù)了怎么辦?有方法自動(dòng)檢測(cè)出來(lái)么?答案是沒(méi)辦法知道,因?yàn)檎l(shuí)也不知道你的key重復(fù)是場(chǎng)景需要還是錯(cuò)誤。

好吧,自己試試能不能造個(gè)輪子,這個(gè)輪子需要解決以上所有問(wèn)題,并且至少要和AutoGo一樣用的順暢,不但Activity的跳轉(zhuǎn),SharedPreferences的問(wèn)題也要一并解決了。
于是針對(duì)上面的問(wèn)題,擼起袖子Code,先提前說(shuō)一說(shuō)SmartKey的使用套路:
SmartTargets開(kāi)始,然后調(diào)用to目標(biāo)Activity/Service,然后調(diào)用params方法放參數(shù),這個(gè)params方法使用的參數(shù)類(lèi)是自動(dòng)生成的IBuilder類(lèi),方法鏈?zhǔn)秸{(diào)用非常舒暢,然后調(diào)用對(duì)應(yīng)的go、goForResult、goWithAction方法或者start方法或者bind方法結(jié)尾,整個(gè)過(guò)程一條鏈,一氣呵成。

  1. 另外設(shè)計(jì)一個(gè)入口,不再依賴(lài)目標(biāo)Activity上面做改動(dòng),這樣就避免了改動(dòng)時(shí)必須在跳轉(zhuǎn)開(kāi)始和目標(biāo)上都同時(shí)要做的問(wèn)題了,那么,另外的入口通過(guò)什么東西來(lái)生成比較方便呢?AndroidManifest文件是所有Activity都需要在這里注冊(cè)的,好,我就從這里入手去生成對(duì)應(yīng)的跳轉(zhuǎn)代碼!然后對(duì)于extra的問(wèn)題,我另外用一個(gè)POJO來(lái)處理這個(gè)問(wèn)題,看代碼:
@SmartManifest(manifestPath = "app/src/main/AndroidManifest.xml")
public class App extends Application {
    // App的其他代碼,
}

根據(jù)上面代碼,我可以生成一個(gè)SmartTargets的類(lèi),里面有所有Activity和Service的入口,每當(dāng)要跳轉(zhuǎn)或者啟動(dòng)Service的時(shí)候,就從SmartTargets出發(fā),一直點(diǎn)到目標(biāo),另外,再設(shè)計(jì)了兩個(gè)toNotDeterminedActivityTarget和toNotDeterminedServiceTarget方法給SmartTargets類(lèi),這樣,無(wú)論怎樣的Activity和Service都可以過(guò)去了。
對(duì)于POJO,以一直作為例子的ForResultActivity為例,我寫(xiě)了兩個(gè),一個(gè)是為了請(qǐng)求過(guò)來(lái)的時(shí)候傳遞參數(shù)的,一個(gè)是為了onActivityResult用的,兩個(gè)類(lèi)代碼如下:

@SmartIntent
public class ForResultReq {
    public String title;
    public String label;
    public String hint;
}
@SmartIntent
public class ForResultRes {
    public String input;
    public String input2;
}

然后根據(jù)這兩個(gè)類(lèi),APT工具自動(dòng)生成兩個(gè)對(duì)應(yīng)的類(lèi),F(xiàn)orResultReqIBuilder和ForResultResIBuilder,使用方式就簡(jiǎn)單了,注意到兩個(gè)類(lèi)的注解么?對(duì),只要加上這樣的注解,代碼就會(huì)自動(dòng)生成,跳轉(zhuǎn)代碼(重復(fù)一次開(kāi)頭那段代碼,_):

SmartTargets.toForResultActivityATarget()
        .params(ForResultReqIBuilder
                .newBuilder()
                .title(App.getStr(R.string.input_something))
                .label(App.getStr(R.string.this_is_label))
                .hint(App.getStr(R.string.this_is_hint)))
        .goForResult(activity, REQ_CODE_FOR_RESULT);

接收參數(shù)代碼:

ForResultReq req = ForResultReqIBuilder.getSmart(activity.getIntent());

在ForResultActivity的返回時(shí),這樣返回:

BackResult
        .newBackResult()
        .params(ForResultResIBuilder
                .newBuilder()
                .input(input)
                .input2(input2))
        .finishWithResult(activity);

BackResult是我設(shè)計(jì)的另外一個(gè)助手類(lèi),專(zhuān)門(mén)為了在activity返回參數(shù)的時(shí)候用的,非常的簡(jiǎn)單舒暢,上面的IBuilder類(lèi)生成后的代碼,看一個(gè)吧,連他的接口和父類(lèi)一并列出了(去掉了注釋?zhuān)?qǐng)直接看SmartKey):

// 接口,已經(jīng)寫(xiě)好,不需要關(guān)注
public interface IntentKeyMapper {
    Intent buildIntent();
}
// 父類(lèi),已經(jīng)寫(xiě)好,有一些比較方便的共用方法可以直接使用,在生成的IBuilder類(lèi)里使用
public abstract class BaseIntentKeyMapper<B, S> implements IntentKeyMapper {
    protected S smart;
    public S getSmart() {
        return smart;
    }
    public B replaceSmart(@NonNull S smart) {
        if(smart == null)
            throw new RuntimeException("Smart wrapper object should not be null.");

        this.smart = smart;
        return (B) this;
    }
    public abstract B fillFromSource(Intent source);
    public B fillFromSource(Bundle source) {
        if (source == null) return (B) this;
        return fillFromSource(new Intent().putExtras(source));
    }
    public Bundle buildBundle() {
        return buildIntent().getExtras();
    }
}
// 然后就是生成的類(lèi)了
// req
public final class ForResultReqIBuilder extends BaseIntentKeyMapper<ForResultReqIBuilder, ForResultReq> {
  public ForResultReqIBuilder() {
    smart = new ForResultReq();
  }
  public static ForResultReqIBuilder newBuilder() {
    return new ForResultReqIBuilder();
  }
  public static ForResultReqIBuilder newBuilder(ForResultReq fromSource) {
    return new ForResultReqIBuilder().replaceSmart(fromSource);
  }
  public static ForResultReq getSmart(Intent source) {
    return new ForResultReqIBuilder().fillFromSource(source).getSmart();
  }
  public static ForResultReq getSmart(Bundle source) {
    return new ForResultReqIBuilder().fillFromSource(source).getSmart();
  }
  @Override
  public Intent buildIntent() {
    Intent in = new Intent();
    in.putExtra("title", smart.title);
    in.putExtra("label", smart.label);
    in.putExtra("hint", smart.hint);
    return in;
  }
  public ForResultReqIBuilder fillFromSource(Intent source) {
    if (source == null) { return this; }
    smart.title = source.getStringExtra("title");
    smart.label = source.getStringExtra("label");
    smart.hint = source.getStringExtra("hint");
    return this;
  }
  public ForResultReqIBuilder title(String value) {
    smart.title = value;
    return this;
  }
  public ForResultReqIBuilder label(String value) {
    smart.label = value;
    return this;
  }
  public ForResultReqIBuilder hint(String value) {
    smart.hint = value;
    return this;
  }
}
// res
public final class ForResultResIBuilder extends BaseIntentKeyMapper<ForResultResIBuilder, ForResultRes> {
  public ForResultResIBuilder() {
    smart = new ForResultRes();
  }
  public static ForResultResIBuilder newBuilder() {
    return new ForResultResIBuilder();
  }
  public static ForResultResIBuilder newBuilder(ForResultRes fromSource) {
    return new ForResultResIBuilder().replaceSmart(fromSource);
  }
  public static ForResultRes getSmart(Intent source) {
    return new ForResultResIBuilder().fillFromSource(source).getSmart();
  }
  public static ForResultRes getSmart(Bundle source) {
    return new ForResultResIBuilder().fillFromSource(source).getSmart();
  }
  public Intent buildIntent() {
    Intent in = new Intent();
    in.putExtra("input", smart.input);
    in.putExtra("input2", smart.input2);
    return in;
  }
  public ForResultResIBuilder fillFromSource(Intent source) {
    if (source == null) { return this; }
    smart.input = source.getStringExtra("input");
    smart.input2 = source.getStringExtra("input2");
    return this;
  }
  public ForResultResIBuilder input(String value) {
    smart.input = value;
    return this;
  }
  public ForResultResIBuilder input2(String value) {
    smart.input2 = value;
    return this;
  }
}

注意,以上所有代碼都不需要你寫(xiě),你只要寫(xiě)你的POJO和使用他們,上面的兩個(gè)POJO我起名字一個(gè)叫ForResultReq, 另外一個(gè)是ForResultRes,帶上Req和Res的目的是為了自己看方便,表明一個(gè)是為了傳遞數(shù)據(jù)用的,一個(gè)是為返回?cái)?shù)據(jù)用,參照HTTP請(qǐng)求的做法而已,這里POJO類(lèi)的起名沒(méi)有任何要求,只要是Java類(lèi),直接使用public字段,對(duì)于android,一般情況下,還是這樣更省CPU時(shí)間和內(nèi)存資源,能省就省吧,對(duì)于一般的POJO,并不存在問(wèn)題。對(duì)于遺留key問(wèn)題,如果目標(biāo)activity不想改,那么簡(jiǎn)單,直接把對(duì)應(yīng)字段標(biāo)注一下,說(shuō)明key是啥就行了,比如:

// 標(biāo)注的字段
@Key(Intent.EXTRA_TEXT)
    public String text;
// 對(duì)應(yīng)生成的代碼
in.putExtra("android.intent.extra.TEXT", smart.text);
smart.text = source.getStringExtra("android.intent.extra.TEXT");

這樣一來(lái)目標(biāo)activity可以不做任何修改,入口可以是全新的了。

  1. 對(duì)于framework或者第三方庫(kù)的兼容問(wèn)題,跟前面遺留的key管理一樣,只需要一個(gè)標(biāo)注@Key(系統(tǒng)或庫(kù)提供的key),就這樣愉快的兼容了。
  2. 還是在第1點(diǎn)上說(shuō)的,你的Action都會(huì)在manifest上面聲明,所以@SmartManifest(manifestPath = "app/src/main/AndroidManifest.xml")可以知道這些所有信息,直接生成了對(duì)應(yīng)的goWithAction/goForResultWithAction方法。
  3. NotDeterminedActivityTarge和NotDeterminedServiceTarget兩個(gè)類(lèi)專(zhuān)門(mén)處理這樣的問(wèn)題,出發(fā)點(diǎn)也是SmartTargets,有對(duì)應(yīng)的toNotDeterminedActivityTarge/NotDeterminedServiceTarget方法。
    5和6. 對(duì)于SharedPreferences,集中管理,并且使用一個(gè)POJO類(lèi)進(jìn)行配置,java的字段不允許重復(fù),有重復(fù)字段直接編譯不通過(guò)!!!就這樣,key重復(fù)的問(wèn)題一定不會(huì)出現(xiàn),并且每個(gè)字段有各自的get/set方法,get就是從SharedPreferences獲取,set就是保存到SharedPreferences,至于key,忘掉吧,我會(huì)另外寫(xiě)一個(gè)博客文章專(zhuān)門(mén)介紹SharedPreferences的管理,相關(guān)的博客文章即將完成了。

說(shuō)了這么多,咱么開(kāi)始具體看看SmartKey提供了什么和怎么用吧。由于本人試了一大圈,注冊(cè)了一大堆帳號(hào),還是未能發(fā)布到mavencentral,所以,用起來(lái)有點(diǎn)麻煩,需要大家拷貝代碼進(jìn)項(xiàng)目,有成功發(fā)布上mavencentral或者jcenter的朋友可以教我一下,非常感謝。
使用方法如下:

第一步,去SmartKey下載源代碼。
第二步,拷貝annotation、apt和sdks三個(gè)模塊到你的項(xiàng)目里
第三步,在根項(xiàng)目的build.gradle添加以下配置到dependencies配置項(xiàng)內(nèi):

        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

第四步,在你的項(xiàng)目下對(duì)應(yīng)的那個(gè)主模塊的build.gradle下添加以下配置,具體參考下載的代碼的app模塊:

// 這句放在前面
apply plugin: 'com.neenbedankt.android-apt'
// 這三句放在dependencies配置項(xiàng)內(nèi)
compile project(':annotation')
compile project(':sdks')
apt project(':apt')

配置就完成了,rebuild一下代碼系統(tǒng)就會(huì)調(diào)用apt工具生成對(duì)應(yīng)的代碼。更多內(nèi)容請(qǐng)參照源碼中的app模塊,這個(gè)模塊是一個(gè)怎樣使用這個(gè)庫(kù)的demo。

下面是具體的使用說(shuō)明:
annotation模塊共有7個(gè)注解
@Code注解
這個(gè)注解有兩個(gè)用途,一個(gè)用于管理SharedPreferences的時(shí)候,有些泛型需要管理的時(shí)候標(biāo)注,另外一個(gè)用途是用于標(biāo)注那些由于繼承結(jié)構(gòu)比較復(fù)雜,apt工具由于不在android環(huán)境下運(yùn)行,所以并不容易正確生成的代碼,例如使用android系統(tǒng)自帶的分享功能時(shí),有uri這么一個(gè)字段,這個(gè)字段用來(lái)表示多個(gè)文件的路徑,類(lèi)型是ArrayList<Uri>,由于apt工具并不知道uri實(shí)現(xiàn)了Parcelable接口,所以生成的代碼并不是最好的,可以這樣來(lái)直接指定生成的代碼:

@Key(Intent.EXTRA_STREAM)
@Code(get = "getParcelableArrayListExtra(%1$s)", set = "putParcelableArrayListExtra(%1$s, %2$s)")
public ArrayList<Uri> uri;

上面get表示獲取的時(shí)候用的代碼,%1$s是key的占位符,apt會(huì)使用真實(shí)的key展開(kāi)
set表示放進(jìn)Intent時(shí)用的代碼,%1$s繼續(xù)是key的占位符,%2$s是值的占位符,apt會(huì)自動(dòng)匹配真實(shí)的值,注意的是,get和set兩個(gè)值必須同時(shí)存在apt才會(huì)使用他們,生成代碼如下:

smart.uri = source.getParcelableArrayListExtra("android.intent.extra.STREAM");
in.putParcelableArrayListExtra("android.intent.extra.STREAM", smart.uri);

詳細(xì)例子請(qǐng)參考SmartKey的app模塊里面的share包下面的MultiFileShare類(lèi),這個(gè)類(lèi)用來(lái)描述使用系統(tǒng)自帶Intent分享多個(gè)圖片功能。

@Key注解
這個(gè)注解用于指定字段使用的key,一般情況下,新代碼根本就不用關(guān)注key的問(wèn)題,這個(gè)時(shí)候key這個(gè)注解就不必要了,當(dāng)然,如果有遺留代碼,又不想改,或者是framework內(nèi)的比如使用android提供的Intent分享內(nèi)容,或者使用第三方的庫(kù)人家的key已經(jīng)固定了,又或者你就是覺(jué)得自己制定的key更好聽(tīng)(起個(gè)好字段名不就得了么,默認(rèn)是直接使用字段名_),就可以用到這個(gè)注解了,例如這個(gè)用到framework里面現(xiàn)成改不了的key:

// POJO中的用法:
@Key(Intent.EXTRA_EMAIL)
public String[] email;
// 對(duì)應(yīng)生成的代碼:
smart.email = (String[]) IntentValueGetter.getValue(source, "android.intent.extra.EMAIL", java.lang.String[].class);
in.putExtra("android.intent.extra.EMAIL", smart.email);

@Required注解
這個(gè)注解純粹給自己看的,在POJO的字段上標(biāo)識(shí)一下,表明這個(gè)字段不應(yīng)該為空,對(duì)于這個(gè)注解,SmartKey現(xiàn)階段并不會(huì)做任何的處理,目前僅僅為自己看代碼服務(wù)的。

@SmartIntent注解
用這個(gè)注解標(biāo)注過(guò)的POJO類(lèi),apt工具會(huì)自動(dòng)掃描所有字段,生成對(duì)應(yīng)的IBuilder類(lèi),用于傳遞數(shù)據(jù)到目標(biāo)組件或者從目標(biāo)組件返回值,屬于核心注解,使用SmartKey必用的注解之一,例如前面說(shuō)到的ForResultReq和ForResultRes類(lèi),由于添加了這個(gè)注解,所以apt工具為他們生成了ForResultReqIBuilder和ForResultResIBuilder類(lèi),直接用這兩個(gè)builder就可以非常方便的使用方法鏈傳遞數(shù)據(jù)了。

@SmartManifest注解
這個(gè)注解一個(gè)模塊只能出現(xiàn)一次,建議放在對(duì)應(yīng)的主模塊,專(zhuān)門(mén)用來(lái)指定AndroidManifest.xml文件所在的位置,用于生成SmartTargets這個(gè)類(lèi)的,有了這個(gè)注解后,apt工具會(huì)解析對(duì)應(yīng)的xml文件,把所有activity和service提取出來(lái),生成對(duì)應(yīng)于每一個(gè)activity/service的Target描述類(lèi),SmartTargets.to方法得到的就是這些生成的Target實(shí)例,里面有關(guān)于對(duì)應(yīng)activity/service的所有跳轉(zhuǎn)方式方法,是activity跳轉(zhuǎn)/service啟動(dòng)綁定的總?cè)肟凇?/p>

@SmartSharedPreferences注解
這個(gè)注解用于集中管理SharedPreferences,具體用法將會(huì)用另外一篇文章說(shuō)明,這里就不詳細(xì)展開(kāi)了。

@SmartTarget注解
這個(gè)注解用在Activity/Service類(lèi)上面,目的有兩個(gè),一是為了代碼閱讀的人更輕易的看到目標(biāo)activity/service用的是哪個(gè)POJO作為參數(shù),第二個(gè)目的是在前面@SmartManifest注解下生成的對(duì)應(yīng)的Target下,生成一個(gè)獲取對(duì)應(yīng)POJO的IBuilder實(shí)例的方法,例如:

@SmartTarget(req="link.anyauto.smartkey.demo.extras.ForResultReq")
public class ForResultActivity extends BaseActivity {
// 其他activity代碼
}
//因?yàn)檫@個(gè)注解而生成的代碼(在自動(dòng)生成的你不需要關(guān)注的ForResultActivityATarget類(lèi)里)
public ForResultReqIBuilder newMapperBuilder() {
    return ForResultReqIBuilder.newBuilder();
}
// 傳參數(shù)的時(shí)候可以這樣獲取這個(gè)builder
ForResultReqIBuilder builder = SmartTargets.toForResultActivityATarget().newMapperBuilder();

但其實(shí)所有activity/service的Target描述類(lèi)的params方法都不是指定具體Builder類(lèi)的,可以傳遞任何的IntentKeyMapper的實(shí)現(xiàn)類(lèi)。

sdk模塊下面的一些類(lèi)的說(shuō)明:
BackResult類(lèi),用于封裝activity結(jié)束的時(shí)候向調(diào)用者返回值的細(xì)節(jié)工作,用法套路:
BackResult.newBackResult()/.newBackResult(resultCode).params(參數(shù)).finishWithResult(activity);
跟前面的activity跳轉(zhuǎn)一樣樣的:
** BackResult出發(fā),帶上resultCode和參數(shù),走起! **
OnPrepareIntentCallback接口,這個(gè)接口基本上不怎么需要,這個(gè)接口在activity Target的go相關(guān)方法調(diào)用時(shí)會(huì)被調(diào)用,用于給大家一個(gè)最后調(diào)整Intent的機(jī)會(huì)。
GsonHelper類(lèi),用于包裝json與pojo間的相互轉(zhuǎn)換,如果你需要處理一些非標(biāo)準(zhǔn)格式的日期等字段,需要調(diào)用他的replaceGson方法替換成你應(yīng)用需要的gson對(duì)象。
其他都是一些基礎(chǔ)性代碼,可以完全不關(guān)注,當(dāng)然想知道一些原理,看看還是有用的。

對(duì)于未確定的Activity目標(biāo),比如根據(jù)服務(wù)器返回的內(nèi)容進(jìn)行跳轉(zhuǎn),或者根據(jù)配置進(jìn)行跳轉(zhuǎn)時(shí),可以這樣使用,一樣的套路:

SmartTargets.toNotDeterminedActivityTarget()
                .activityClass(clz) // 或者 .action(action).addCategories(cats).addCategory(cat)
                .params(params)
                .go(activity);

關(guān)于Activity的跳轉(zhuǎn),目前還有一些工作在做,就是引入Activity路由,相關(guān)工作正在進(jìn)行中,為了讓大家可以非常簡(jiǎn)單流暢的使用,我會(huì)仔細(xì)設(shè)計(jì),并且融合現(xiàn)有的參數(shù)傳遞方式,我會(huì)完善了再commit到github,讓大家的跳轉(zhuǎn)更加舒暢,特別是多人協(xié)作,根據(jù)服務(wù)器內(nèi)容進(jìn)行跳轉(zhuǎn)時(shí),使用Activity路由將會(huì)更加舒暢。

前面說(shuō)了這么多,實(shí)際用法還是建議大家直接參考app這個(gè)模塊吧,對(duì)于程序員,show me the code比啥都管用。寫(xiě)完文章剛好元宵節(jié),祝愿大家元宵快樂(lè),每天準(zhǔn)時(shí)下班。喜歡的朋友記得在github上給個(gè)星星,多謝了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,554評(píng)論 0 17
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,561評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評(píng)論 19 139
  • 旋風(fēng)APP是深圳開(kāi)拍網(wǎng)運(yùn)營(yíng)的影視內(nèi)容孵化和藝人成長(zhǎng)平臺(tái) ◆產(chǎn)品簡(jiǎn)介 旋風(fēng)App是深圳開(kāi)拍網(wǎng)科技有限公司(深圳文化所...
    piegroups閱讀 485評(píng)論 0 0
  • 實(shí)現(xiàn)這樣的效果,所有文字包括返回按鈕都為白色 自定義返回按鈕 記得要寫(xiě)在上一個(gè)頁(yè)面上 全局修改UINavigati...
    Arxu閱讀 572評(píng)論 0 1