Android開(kāi)發(fā)中R資源引用注意事項(xiàng)

寫在前面

我們都知道镣丑,在Android打包時(shí)進(jìn)行的aapt是對(duì)Android的資源打包,從而生成一個(gè)R.java文件汗贫,其中包括非assets下的所有資源的id值司蔬,同時(shí)還生成resource.arsc文件沐序,也就是資源索引表琉用。而我們程序在運(yùn)行中,就是從R.java獲取具體的id值策幼,再到resource.arsc檢索獲取相對(duì)應(yīng)的資源邑时。這文章不是講Android資源打包的過(guò)程,這在老羅的 Android應(yīng)用程序資源的編譯和打包過(guò)程分析 一文講的非常詳細(xì)(反正我看得暈頭轉(zhuǎn)向)特姐,而是講我們平時(shí)開(kāi)發(fā)中對(duì)Android資源引用的幾點(diǎn)注意事項(xiàng)晶丘。

由于個(gè)人的電腦渣渣和對(duì)Eclipse的項(xiàng)目結(jié)構(gòu)更了解,所以我本篇文章的demo是在Eclipse上編寫的。但Eclipse上和Android Studio上資源引用是一樣的浅浮,只是R.java的目錄變了沫浆,后面也會(huì)涉及到Android Studio上的一些問(wèn)題。


正文

下面講述對(duì)R資源引用的一些個(gè)人結(jié)論滚秩,也是我曾經(jīng)踩過(guò)的坑专执,對(duì)于SDK開(kāi)發(fā)的小伙伴或經(jīng)常接入第三方SDK的小伙伴可能會(huì)比較了解,希望對(duì)大家有所幫助和借鑒郁油。

先說(shuō)下demo的結(jié)構(gòu):demo有兩個(gè)項(xiàng)目本股,一個(gè)Sample和一個(gè)SampleLib,Sample直接引用SampleLib或?qū)隨ampleLib的jar包桐腌,從而使用SampleLib里所定義好的接口痊末。由于這個(gè)demo只是為了測(cè)試R資源引用的注意事項(xiàng),所以demo非常簡(jiǎn)單哩掺。在SampleLib只有一個(gè)類,類中只包含兩個(gè)接口涩笤,分別是通過(guò)直接引用R和代碼方式返回一張?jiān)赿rawable里的圖片“test”的id:

package com.leo.sample.lib;
![Uploading drawable下的test圖片_504217.jpg . . .]

import android.content.Context;
import com.leo.sample.lib.R;

public class TestUtils {
    
    public static int getImageIdByReference() {
        return R.drawable.test;
    }

    public static int getImageIdByCode(Context context) {
        return getDrawableId(context, "test");
    }
    
    private static int getDrawableId(Context context, String name) {
        return getId(context, name, "drawable");
    }
    
    private static int getId(Context context, String name, String type) {
        return context.getResources().getIdentifier(name, type, context.getPackageName());
    }
}
drawable下的test圖片.jpg

而在Sample中嚼吞,展示一個(gè)Activity,其中有兩個(gè)Button和兩個(gè)ImageView蹬碧,點(diǎn)擊Button會(huì)分別調(diào)用這兩個(gè)SampleLib的接口舱禽,并展示圖片。布局代碼很簡(jiǎn)單恩沽,就不貼了誊稚,下面是Activity的代碼:

package com.leo.sample;

import com.leo.sample.R;
import com.leo.sample.lib.TestUtils;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {

    ImageView ivLogo1;
    ImageView ivLogo2;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ivLogo1 = (ImageView) findViewById(R.id.iv_logo1);
        ivLogo2 = (ImageView) findViewById(R.id.iv_logo2);
    }
    
    public void getImageByRef(View view) {
        int imageId = TestUtils.getImageIdByReference();
        Log.i("TEST", "id = " + "0x" + Integer.toHexString(imageId));
        ivLogo1.setImageResource(imageId);
    }
    
    public void getImageByCode(View view) {
        int imageId = TestUtils.getImageIdByCode(this);
        Log.i("TEST", "id = " + "0x" + Integer.toHexString(imageId));
        ivLogo2.setImageResource(imageId);
    }
}

好,我們先來(lái)試下直接引用罗心。


直接引用.png

運(yùn)行App里伯,分別點(diǎn)擊兩個(gè)按鈕,效果如下圖:

直接引用項(xiàng)目運(yùn)行結(jié)果.png

可以看到渤闷,兩個(gè)接口都沒(méi)問(wèn)題疾瓮,都能正確的獲取到圖片的id。好了飒箭,再試下導(dǎo)入jar包的方式接入狼电,我們先來(lái)導(dǎo)出SampleLib的jar包,只勾選導(dǎo)入src的代碼:

導(dǎo)入jar包.png

記得不要忘了把SampleLib下drawable的test圖片也復(fù)制到Sample項(xiàng)目下弦蹂,再運(yùn)行App肩碟,分別點(diǎn)擊兩個(gè)按鈕,當(dāng)點(diǎn)擊第一個(gè)按鈕凸椿,也就是調(diào)用了 “TestUtils.getImageIdByReference()” 閃退了削祈!原因相信很多小伙伴都知道,因?yàn)?“TestUtils.getImageIdByReference()” 接口中直接引用了 ”R.drawable.test“削饵,而資源的id聲明是在R.java中岩瘦,而R.java是在gen文件夾中對(duì)應(yīng)的包名目錄下自動(dòng)生成的:

R.java生成.png

所以調(diào)用接口時(shí)未巫,找不到對(duì)應(yīng)的資源id,就閃退了启昧。那為什么剛才直接引用SampleLib時(shí)叙凡,Sample能正常運(yùn)行呢?原來(lái)密末,當(dāng)直接引用時(shí)握爷,會(huì)在Sample的gen文件夾下,自動(dòng)生成所引用的項(xiàng)目的R.java严里,并且會(huì)把引用項(xiàng)目的R.java的資源id插入到當(dāng)前項(xiàng)目的R.java中:

自動(dòng)生成引用項(xiàng)目R.java.png
自動(dòng)插入引用項(xiàng)目資源id.png

問(wèn)題定位到了新啼,我們?cè)趯?dǎo)出SampleLib的jar包時(shí)把gen文件夾也一起導(dǎo)出:

導(dǎo)入jar包(包含gen).png

把Sample的jar包換下,重新運(yùn)行App刹碾,分別點(diǎn)擊兩個(gè)按鈕燥撞。咦,沒(méi)閃退了迷帜,但為什么第一張圖片顯示了App圖標(biāo)的圖片:

導(dǎo)入jar包運(yùn)行結(jié)果.png

看下我打印的圖片id值的日志:

兩種方式獲取的id值不相等.png

咦物舒!兩個(gè)id值不一樣!戏锹!分別打開(kāi)看看Sample的R.java文件和SampleLib的R.java文件:

// Sample R.java
package com.leo.sample;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int ic_launcher=0x7f020000;
        public static final int test=0x7f020001;
    }
    public static final class id {
        public static final int iv_logo1=0x7f060000;
        public static final int iv_logo2=0x7f060001;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040000;
        public static final int hello_world=0x7f040001;
    }
    public static final class style {
        public static final int AppBaseTheme=0x7f050000;
        public static final int AppTheme=0x7f050001;
    }
}

// SampleLib R.java
package com.leo.sample.lib;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static int test=0x7f020000;
    }
}

原來(lái)如此冠胯!導(dǎo)入jar包時(shí),在項(xiàng)目中不會(huì)自動(dòng)生成所導(dǎo)入jar包的R.java锦针,jar包包含的資源id也不會(huì)插入到當(dāng)前項(xiàng)目的R.java中荠察,而我們是通過(guò)復(fù)制SampleLib的圖片資源“test"到Sample中,所以在Sample的R.java也會(huì)有圖片資源的id聲明奈搜,但這個(gè)id值并不一定跟SampleLib的R.java中聲明的圖片資源id值一致(或者說(shuō)絕大多數(shù)情況下都不一致)悉盆,當(dāng)拿到id值時(shí),會(huì)到當(dāng)前項(xiàng)目的R.java去索引馋吗,因?yàn)閷?duì)應(yīng)的資源不一致舀瓢,從而導(dǎo)致檢索資源時(shí)發(fā)生不可預(yù)知的錯(cuò)誤。

但為什么通過(guò)代碼獲取資源id值就不會(huì)出錯(cuò)呢耗美?因?yàn)榇a獲取資源id是通過(guò)資源名的京髓,這時(shí)會(huì)直接在當(dāng)前項(xiàng)目(Sample)的R.java文件中檢索,所以獲取到的資源id值肯定正確商架。

另外可以看到堰怨,SampleLib的R.java中圖片“test”的資源id值和Sample的R.java中的App圖標(biāo)“ic_launcher”的資源id值一致,所以第一張圖片顯示了App圖標(biāo)圖片蛇摸。另外需要注意的是备图,demo中剛好對(duì)應(yīng)SampleLib的R.java中圖片“test”的資源id值是一張圖片,如果是一個(gè)Button或其他非圖片的資源id值,這時(shí)App就會(huì)閃退了揽涮。

結(jié)論1:開(kāi)發(fā)SDK時(shí)抠藕,應(yīng)該用代碼獲取資源id,而不是直接引用R蒋困。

public static int getId(Context context, String name, String type) {
    return context.getResources().getIdentifier(name, type, context.getPackageName());
}

結(jié)論2:接入SDK者在條件允許的情況下盾似,可以選擇直接引用的方式接入第三方SDK,這么一來(lái)雪标,即使SDK開(kāi)發(fā)者沒(méi)注意直接引用了R零院,接入也不會(huì)有問(wèn)題。

那在哪些情況下不允許直接引用第三方SDK呢村刨?例如告抄,像我,更喜歡復(fù)制資源嵌牺,導(dǎo)入jar包的方式打洼,這樣項(xiàng)目結(jié)構(gòu)會(huì)更清晰;再例如逆粹,有時(shí)只用到第三方SDK的部分功能拟蜻,并不需導(dǎo)入全部jar包,而導(dǎo)致Apk包過(guò)大枯饿。在這些情況下我們就會(huì)導(dǎo)入jar包的方式來(lái)接入。但如果第三方SDK開(kāi)發(fā)者沒(méi)注意我上面所說(shuō)的問(wèn)題诡必,在代碼中對(duì)R資源直接引用了奢方,怎么辦?別慌爸舒,方法總是有的蟋字!

我們可以新建一個(gè)項(xiàng)目,把包名聲明為跟第三方SDK聲明的包名一致扭勉,然后復(fù)制使用到的jar包鹊奖、資源和布局等,再在Apk項(xiàng)目中直接引用這個(gè)新建的項(xiàng)目涂炎。這樣一來(lái)忠聚,第三方SDK即能索引到資源id,又能正確獲取到資源id值唱捣!

結(jié)論3:在SDK直接引用R資源又不想直接引用SDK項(xiàng)目時(shí)两蟀,可以新建一個(gè)與SDK同包名的項(xiàng)目,導(dǎo)入SDK的jar包震缭、資源和必要文件赂毯,然后Apk項(xiàng)目直接引用這新建的項(xiàng)目,從而解決問(wèn)題。


擴(kuò)展

1. 在Android Studio上的表現(xiàn)

整篇文章都是在Eclipse上測(cè)試的党涕,而現(xiàn)在基本絕大多數(shù)的Android開(kāi)發(fā)者都轉(zhuǎn)到Android Studio上了烦感,所以這篇文章沒(méi)什么價(jià)值?其實(shí)膛堤,經(jīng)我測(cè)試手趣,在Android Studio上原理一樣的,只是生成R.java的目錄不一樣而已骑祟,在Android Studio上是在 “../build/generated/source/r/${pakeage}/” 目錄下生成的回懦。

另外,經(jīng)測(cè)試次企,如果項(xiàng)目直接導(dǎo)入aar包怯晕,也會(huì)在項(xiàng)目中生成arr的R.java文件,并插入到當(dāng)前項(xiàng)目的R.java中缸棵。也就是相當(dāng)于在Eclipse上直接引用SDK項(xiàng)目舟茶。所以不會(huì)出現(xiàn)資源索引錯(cuò)誤問(wèn)題。

2. 二次打包資源引用

二次打包的情況下可能很多小伙伴在實(shí)際開(kāi)發(fā)中很少接觸堵第。什么是二次打包吧凉?二次打包就是會(huì)對(duì)Apk進(jìn)行一些修改或插入資源和功能,如修改包名踏志、簽名阀捅,或插入登錄模塊等,然后再進(jìn)行編譯打包针余。因?yàn)橐迦胭Y源饲鄙,所以用重新進(jìn)行一次aapt,對(duì)插入的資源重新生成R.java和資源索引表resource.arsc文件圆雁。而在aapt后忍级,重新生成R.java中的資源id值可能會(huì)變了,如果代碼中是直接通過(guò)引用R獲取資源id的伪朽,就會(huì)檢索到錯(cuò)誤的資源轴咱。

拿上面的demo舉例,假如烈涮,SampleLib我們通過(guò)直接引用的方式導(dǎo)入庫(kù)朴肺,沒(méi)二次打包前,資源索引是一一對(duì)應(yīng)的坚洽,沒(méi)有任何問(wèn)題宇挫。而二次打包后,Sample的R.java重新生成酪术,其中的資源id值會(huì)改變器瘪,這樣就跟SampleLib的R.java中的資源id值不對(duì)應(yīng)了翠储,所以就會(huì)導(dǎo)致資源檢索錯(cuò)誤娘锁。

這種情況如何解決呢野宜?方法總是有的!很簡(jiǎn)單队贱,通過(guò)上面的分析欣除,我們知道住拭,直接引用R資源時(shí),程序會(huì)先到R的包名下(如SampleLib:com.leo.sample.lib.R)去檢索历帚,獲取到資源id值滔岳,然后通過(guò)資源id值去資源索引表resource.arsc文件獲取正確的資源文件。而資源檢索表resource.arsc對(duì)應(yīng)的是Sample的R.java挽牢,程序發(fā)生錯(cuò)誤的步驟是在于SampleLib的R.java和Sample的R.java中資源id值不一致谱煤!嘻嘻,說(shuō)到這里禽拔,應(yīng)該懂了吧刘离?是的,直接把Sample的R.java文件復(fù)制覆蓋掉SampleLib的R.java文件睹栖,并把包名改成SampleLib的包名硫惕!哈哈,這樣兩個(gè)R.java的資源id值就一一對(duì)應(yīng)了野来。

可能有小伙伴會(huì)疑問(wèn)怎么對(duì)R.java文件進(jìn)行操作恼除。因?yàn)槎未虬鼤?huì)對(duì)Apk先進(jìn)行反編譯,對(duì)Apk進(jìn)行修改后曼氛,再對(duì)Apk進(jìn)行編譯打包豁辉,這樣我們就能對(duì)Apk的文件進(jìn)行操作了,而aapt也在這個(gè)過(guò)程中搪锣。對(duì)于不了解Apk編譯打包的小伙伴,可以搜下“Android打包過(guò)程”來(lái)了解下彩掐,因?yàn)檫@不是本篇文章的討論范圍构舟,就不展開(kāi)了。


寫在最后

通過(guò)上面的分析堵幽,我們對(duì)R資源引用有了一定的了解狗超,以后在開(kāi)發(fā)中遇到R資源引用問(wèn)題就能自己解決。上面說(shuō)了這么多朴下,需要重點(diǎn)強(qiáng)調(diào)的是:

開(kāi)發(fā)者在開(kāi)發(fā)SDK時(shí)努咐,用代碼獲取資源id,就萬(wàn)事大吉了E闺省渗稍!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佩迟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竿屹,更是在濱河造成了極大的恐慌报强,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱燃,死亡現(xiàn)場(chǎng)離奇詭異秉溉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碗誉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門召嘶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人哮缺,你說(shuō)我怎么就攤上這事弄跌。” “怎么了蝴蜓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵碟绑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我茎匠,道長(zhǎng)格仲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任诵冒,我火速辦了婚禮凯肋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汽馋。我一直安慰自己侮东,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布豹芯。 她就那樣靜靜地躺著悄雅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铁蹈。 梳的紋絲不亂的頭發(fā)上宽闲,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音握牧,去河邊找鬼容诬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沿腰,可吹牛的內(nèi)容都是我干的览徒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颂龙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼习蓬!你這毒婦竟也來(lái)了纽什?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤友雳,失蹤者是張志新(化名)和其女友劉穎稿湿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體押赊,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饺藤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了流礁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涕俗。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖神帅,靈堂內(nèi)的尸體忽然破棺而出再姑,到底是詐尸還是另有隱情,我是刑警寧澤找御,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布元镀,位于F島的核電站,受9級(jí)特大地震影響霎桅,放射性物質(zhì)發(fā)生泄漏栖疑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一滔驶、第九天 我趴在偏房一處隱蔽的房頂上張望遇革。 院中可真熱鬧,春花似錦揭糕、人聲如沸萝快。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揪漩。三九已至,卻和暖如春吏口,著一層夾襖步出監(jiān)牢的瞬間奄容,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工锨侯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫩海,地道東北人冬殃。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓囚痴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親审葬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子深滚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,528評(píng)論 25 707
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理奕谭,服務(wù)發(fā)現(xiàn),斷路器痴荐,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342
  • 麻衣是日本一個(gè)知名博主血柳,自稱“扔?xùn)|西的變態(tài)”,博客名叫“啥都沒(méi)有的博客”生兆,所有的網(wǎng)紅都教你買買買难捌,而她卻教你扔扔扔...
    趙黎丹閱讀 530評(píng)論 0 1
  • 大家好,我是日記星球217號(hào)星寶寶裙子鸦难,我正在參加日記星球第五期21天的蛻變之旅根吁,這是我的第一篇原創(chuàng)日記! 年年過(guò)...
    環(huán)保達(dá)人羅群閱讀 327評(píng)論 1 4