android開發有時候會令人頭痛。你不得不為諸如建立fragment這樣簡單的事情寫很多代碼。幸運的是java支援一個強大的工具:注釋處理器(annotation processors)。
fragment的問題是你不得不設定很多參數,進而讓它正常運作。很多android開發新手通常這樣寫:
<code>01</code>
<code>public</code> <code>class</code> <code>myfragment </code><code>extends</code> <code>fragment</code>
<code>02</code>
<code>{</code>
<code>03</code>
<code>private</code> <code>int</code> <code>id;</code>
<code>04</code>
<code>private</code> <code>string title;</code>
<code>05</code>
<code>06</code>
<code>public</code> <code>static</code> <code>myfragment newinstance(</code><code>int</code> <code>id, string title)</code>
<code>07</code>
<code>08</code>
<code>myfragment f = </code><code>new</code> <code>myfragment();</code>
<code>09</code>
<code>f.id = id;</code>
<code>10</code>
<code>f.title = title;</code>
<code>11</code>
<code>return</code> <code>f;</code>
<code>12</code>
<code>}</code>
<code>13</code>
<code>14</code>
<code>@override</code>
<code>15</code>
<code>public</code> <code>view oncreateview(layoutinflater inflater, viewgroup container,</code>
<code>16</code>
<code>bundle savedinstancestate)</code>
<code>17</code>
<code>18</code>
<code>toast.maketext(getactivity(), </code><code>"hello "</code> <code>+ title.substring(</code><code>0</code><code>, </code><code>3</code><code>),</code>
<code>19</code>
<code>toast.length_short).show();</code>
<code>20</code>
<code>21</code>
這樣做怎麼了?我已經在自己的裝置上嘗試過了,它很好用?
它的卻能工作,但是你有沒有試過把你的裝置從豎向改為橫向?你的app将會因為nullpointerexception而崩潰,當你試圖通路id或title時。
我的app是正常的,因為我把app設定為豎向。是以我從來沒遇到過這個問題。
随便你!android是一個真正的多任務作業系統。多個app在同一時間運作,同時如果需要記憶體android系統将會銷毀activity(和其中包含的fragment)。可能你在日常的app開發中不會注意這些問題。然而,當你在play store中釋出後,你将會注意到你的app崩潰了,但你不知道什麼原因。使用你app的使用者可能同時間使用多個app,很有可能你的app在背景被銷毀了。例如:a 使用者打開你的app,myfragment在螢幕上顯示。下一步你的使用者按了home鍵(這是你的app在背景運作),并且打開了其它應用。你的app可能會因為釋放記憶體而被銷毀。之後,使用者傳回你的app,例如通過多任務按鈕。是以,android現在會怎麼做?android會恢複之前的app狀态,同時恢複myfragment,這就是問題所在。fragment試圖通路title,但title是null,因為它不是被永久儲存的。
我知道了,是以我需要把它們儲存在onsaveinstancestate(bundle)中?
不是。官方的文檔有一些不清楚,但是onsaveinstancestate(bundle)的使用方法應該跟你用activity.onsaveinstancestate(bundle)一樣:你使用這個方法儲存執行個體的“臨時”狀态,例如去處理螢幕的方向(從豎向到橫向,反之亦然)。是以說當app在背景被殺掉時fragment的執行個體狀态并不能被儲存成持久資料,它的作用是再一次傳回前台時恢複資料。它的作用跟activity.onsaveinstancestate(bundle)在activity中的作用相同,它們用于“臨時”儲存執行個體狀态。然而,持久的參數是通過intent外部資料傳輸的。
是以我應該在activity中得intent儲存fragment的參數?
不需要,fragment有它自己的機制。有兩個方法:fragment.setarguments(bundle)和fragment.getarguments(),你必須通過這兩個方法來確定參數被持久儲存。這就是我上面提到的痛苦之處。需要有大量的代碼這樣寫。第一,你要建立一個bundle,然後你需要放入鍵值對,最後調用fragment.setarguments()。不幸的是,你的工作還沒有結束,你必須通過fragment.getarguments()來讀出bundle。一些這樣的工作:
<code>private</code> <code>static</code> <code>string key_id = </code><code>"key.id"</code><code>;</code>
<code>private</code> <code>static</code> <code>string key_title = </code><code>"key.title"</code><code>;</code>
<code>bundle b = </code><code>new</code> <code>bundle();</code>
<code>b.putint(key_id, id);</code>
<code>b.putstring(key_title, title);</code>
<code>f.setarguments(b);</code>
<code>public</code> <code>void</code> <code>oncreate(bundle savedinstancestate)</code>
<code>// oncreate it's a good point to read the arguments</code>
<code>22</code>
<code>bundle b = getarguments();</code>
<code>23</code>
<code>this</code><code>.id = b.getint(key_id);</code>
<code>24</code>
<code>this</code><code>.title = b.getstring(key_title);</code>
<code>25</code>
<code>26</code>
<code>27</code>
<code>28</code>
<code>public</code> <code>view oncreate(layoutinflater inflater, viewgroup container,</code>
<code>29</code>
<code>30</code>
<code>31</code>
<code>// no nullpointer here, because oncreate() is called before this</code>
<code>32</code>
<code>33</code>
<code>34</code>
<code>35</code>
我希望你現在能明白我所說的“痛苦”。在你的應用中你将會為每一個fragment寫很多代碼。如果有人為你寫這樣的代碼,這将不讓人滿意。注釋處理允許你在編譯的時候生成java代碼。注意我們并不是在讨論評價在運作時間使用反射的注釋。
fragmentargs是一個輕量的包,用于為你的fragment生成精确的java代碼。
<code>import</code> <code>com.hannesdorfmann.fragmentargs.fragmentargs;</code>
<code>import</code> <code>com.hannesdorfmann.fragmentargs.annotation.arg;</code>
<code>@arg</code>
<code>int</code> <code>id;</code>
<code>string title;</code>
<code>super</code><code>.oncreate(savedinstancestate);</code>
<code>fragmentargs.inject(</code><code>this</code><code>); </code><code>// read @arg fields</code>
<code>toast.maketext(getactivity(), </code><code>"hello "</code> <code>+ title, toast.length_short)</code>
<code>.show();</code>
隻需要在你的fragment類中加入注釋字段,fragmentargs就會生成引用代碼。在你的activity中你将使用生成的builder類(你的fragment的字尾是”builder”),而不是使用new myfragment()或靜态的myfragment.newinstance(int id,string title)方法。
例如:
<code>public</code> <code>class</code> <code>myactivity </code><code>extends</code> <code>activity</code>
<code>int</code> <code>id = </code><code>123</code><code>;</code>
<code>string title = </code><code>"test"</code><code>; </code><code>// using the generated builder</code>
<code>fragment fragment = </code><code>new</code> <code>myfragmentbuilder(id, title).build(); </code><code>// fragment transaction</code>
<code>getfragmentmanager().begintransaction().replace(r.id.container,fragment).commit();</code>
你可能已經注意到在fragment.oncreate(bundle)中聲明的fragmentargs.inject(this)。這個調用使你的fragment獲得了生成代碼的連接配接。你可能會問你自己:“我需不需要在每一個fragment中的oncreate(bundle)中加入inject()方法”。答案是no。你隻需要在你的fragment基類中插入這一句就可以,并且在所有的fragment中繼承它。
<code>public</code> <code>class</code> <code>basefragment </code><code>extends</code> <code>fragment</code>
<code>public</code> <code>class</code> <code>myfragment </code><code>extends</code> <code>basefragment</code>
信用:一部分的注釋生成代碼是基于hugo visser的bundles項目。