背景
我们平时开发安卓时候使用资源都是使用R文件,用类似R.id.xxx的形式获取资源id
public class SdkActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sdk);
ImageView avatarIv = findViewById(R.id.fatty_avatar);
TextView helloTv = findViewById(R.id.fatty_hello);
avatarIv.setImageResource(R.drawable.ic_baseline_emoji_emotions_24);
helloTv.setText(R.string.hello);
}
}
但是,在SDK开发中,我们常会遇到这个问题:在R文件中找不到指定ID。
因为代码编译完之后就用R文件里面的id覆盖了原本的R.xx.xx引用, 而有些时候因为一些外界原因, (比如对接方使用eclipse等古老的工具去接入,或者我们反编译后二次打包重新用aapt生成了R文件而原本的代码中的引用的id却没有修改) 可能导致R文件ID查找失败。
要解决这个问题可以从以下几个方面入手
- 使用aar与android studio,aar带有自己的包名与r文件,不会出现r文件的错乱
- 从细节层面修复R的id问题,即保证每个id的引用都正确
- 使用代码动态获取id
实际的sdk开发中,更多使用第三种方法,于是我们使用如下方案
public class Util_Resource {
public static int getLayoutIdentifier(Context context, String name) {
return getResourceId(context, name, "layout");
}
public static int getIdIdentifier(Context context, String name) {
return getResourceId(context, name, "id");
}
public static int getDrawableIdentifer(Context context, String name) {
return getResourceId(context, name, "drawable");
}
public static int getMipmapIdentifer(Context context, String name) {
return getResourceId(context, name, "mipmap");
}
public static int getStyleIdentifer(Context context, String name) {
return getResourceId(context, name, "style");
}
public static int getStringIdentifer(Context context, String name) {
return getResourceId(context, name, "string");
}
public static int getAttrIdentifer(Context context, String name) {
return getResourceId(context, name, "attr");
}
public static int getAnimIdentifer(Context context, String name) {
return getResourceId(context, name, "anim");
}
public static int getColorIdentifer(Context context, String name) {
return getResourceId(context, name, "color");
}
public static int getDimenIdentifer(Context context, String name) {
return getResourceId(context, name, "dimen");
}
private static int getResourceId(Context context, String name, String defType) {
return context.getResources().getIdentifier(name, defType, context.getPackageName());
}
}
public class SdkActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(Util_Resource.getLayoutIdentifier(this, "activity_sdk"));
ImageView avatarIv = findViewById(Util_Resource.getIdIdentifier(this, "fatty_avatar"));
TextView helloTv = findViewById(Util_Resource.getIdIdentifier(this, "fatty_hello"));
avatarIv.setImageResource(Util_Resource.getDrawableIdentifer(this, "ic_baseline_emoji_emotions_24"));
helloTv.setText(Util_Resource.getStringIdentifer(this, "hello"));
}
}
以上代码是我接触过的很多sdk使用的,这种方式是比较安全的,但是不难看出,代码层次存在太多弊端了
- 代码太长,每次使用都要写一堆重复代码
- 太多字符串魔法值,完全不利于资源的管理,也容易因为写错字符导致没必要的错误
- id 或者res 文件改名时候要更新必须全局搜索,太不规范
- 同样的,代码可读性也是大受影响
优化
然后就是本文的重点了,我们要怎么去优化这些代码才能使其看起来优雅一些呢?
消灭魔法值
首先就是id的引用,应该是用常量而不是字符串,建立一个文件专门存放
public final class ResConst {
public static String activity_sdk = "activity_sdk";
public static String fatty_avatar = "fatty_avatar";
public static String fatty_hello = "fatty_hello";
public static String hello_id = "fatty_hello";
}
这个文件看着没什么意义,但我们将所有与R相关的引用都跟这个文件关联起来, 那我们查询某个id的使用情况或者修改id,只要编辑这个文件就行了,对维护来说简直是事半功倍。
自制R文件
既然我们把跟R操作相关的常量都放到了一个文件,那么,
Util_Resource.getLayoutIdentifier(context,resId)这样的寻值操作为什么不也交给这个文件去处理呢,
然后我们只需要使用类似ResConst.activity_sdk这样的代码就可以获取id的目的。
机智的朋友可能发现了,我们这个操作跟R文件有点异曲同工。
于是,我想,为什么不保留我们R的使用习惯。同样的,用R.id.xxx这种方式调用。
我们看看R文件的代码
public final class R {
public static final class anim {
public static final int abc_fade_in = 2130771968;
...
private anim() {
}
}
public static final class attr {
public static final int actionBarDivider = 2130837504;
...
private attr() {
}
}
public static final class bool {
public static final int abc_action_bar_embed_tabs = 2130903040;
...
private bool() {
}
}
public static final class color {
public static final int abc_background_cache_hint_selector_material_dark = 2130968576;
...
private color() {
}
}
public static final class dimen {
public static final int abc_action_bar_content_inset_material = 2131034112;
...
private dimen() {
}
}
public static final class drawable {
...
/* added by JADX */
public static final int $ic_launcher_foreground__0 = 2131099648;
private drawable() {
}
}
public static final class id {
public static final int fatty_avatar = 2131165241;
public static final int fatty_hello = 2131165242;
public static final int fill = 2131165243;
...
private id() {
}
}
public static final class integer {
...
private integer() {
}
}
public static final class layout {
public static final int activity_main = 2131296284;
public static final int activity_sdk = 2131296285;
...
private layout() {
}
}
public static final class mipmap {
public static final int ic_launcher = 2131361792;
public static final int ic_launcher_round = 2131361793;
private mipmap() {
}
}
public static final class string {
...
public static final int app_name = 2131427367;
public static final int fatty_hello = 2131427368;
private string() {
}
}
public static final class style {
...
private style() {
}
}
public static final class styleable {
...
private styleable() {
}
}
private R() {
}
}
这里我用LR来命名我们的文件,然后在里面建立一个id的静态内部类。(我们需要一个context,可以设置全局的applicationContext)
public final class LR {
public final static class id {
public static int fatty_avatar = getId("fatty_avatar");
public static int fatty_hello = getId("fatty_hello");
private final static int getId(String str) {
return Util_Resource.getIdIdentifier(MainApplication.getAppContext(), str);
}
}
}
然后就可以实现类似R的引用效果:LR.id.fatty_avatar
对于string,可以把给getString()方法也封装进来
public final static class string {
public static int hello_id = getStringId("fatty_hello");
public static String hello = getString(hello_id);
private static int getStringId(String str) {
return Util_Resource.getStringIdentifer(MainApplication.getAppContext(), str);
}
private static String getString(int id) {
return MainApplication.getAppContext().getResources().getString(id);
}
}
最终的LR文件
public final class LR {
/*********************************dimen***********************************************************/
public final static class dimen {
private static int getDimen(String str) {
return Util_Resource.getDimenIdentifer(MainApplication.getAppContext(), str);
}
}
/**********************************layout********************************************************************/
public final static class layout {
public static int activity_sdk = getLayout("activity_sdk");
private static int getLayout(String str) {
return Util_Resource.getLayoutIdentifier(MainApplication.getAppContext(), str);
}
}
/**********************************id********************************************************************/
public final static class id {
/***************** activity_sdk ****************/
public static int fatty_avatar = getId("fatty_avatar");
public static int fatty_hello = getId("fatty_hello");
private final static int getId(String str) {
return Util_Resource.getIdIdentifier(MainApplication.getAppContext(), str);
}
}
/**********************************style********************************************************************/
public final static class style {
private static int getStyle(String str) {
return Util_Resource.getStyleIdentifer(MainApplication.getAppContext(), str);
}
}
/**********************************style********************************************************************/
public final static class color {
private static int getColor(String str) {
return Util_Resource.getColorIdentifer(MainApplication.getAppContext(), str);
}
}
/**********************************anim********************************************************************/
public final static class anim {
private static int getAnim(String str) {
return Util_Resource.getAnimIdentifer(MainApplication.getAppContext(), str);
}
}
/**********************************mipmap********************************************************************/
public final static class mipmap {
private static int getMipmap(String str) {
return Util_Resource.getMipmapIdentifer(MainApplication.getAppContext(), str);
}
}
/**********************************drawable********************************************************************/
public final static class drawable {
public static int ic_baseline_emoji_emotions_24 = getDrawable("ic_baseline_emoji_emotions_24");
private static int getDrawable(String str) {
return Util_Resource.getDrawableIdentifer(MainApplication.getAppContext(), str);
}
}
public final static class string {
public static int hello_id = getStringId("fatty_hello");
public static String hello = getString(hello_id);
private static int getStringId(String str) {
return Util_Resource.getStringIdentifer(MainApplication.getAppContext(), str);
}
private static String getString(int id) {
return MainApplication.getAppContext().getResources().getString(id);
}
}
}
效果
最终可以用以下方式调用
public class SdkActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(LR.layout.activity_sdk);
ImageView avatarIv = findViewById(LR.id.fatty_avatar);
TextView helloTv = findViewById(LR.id.fatty_hello);
avatarIv.setImageResource(LR.drawable.ic_baseline_emoji_emotions_24);
helloTv.setText(LR.string.hello);
}
}
可以看到,代码变得跟一开始R文件调用的一样简洁,我们甚至可以将调用R文件的代码可以轻松切换到LR