自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android換膚原理和Android-Skin-Loader框架解析

移動(dòng)開發(fā) Android
Android換膚技術(shù)已經(jīng)是很久之前就已經(jīng)被成熟使用的技術(shù)了,然而我最近才在學(xué)習(xí)和接觸熱修復(fù)的時(shí)候才看到。在看了一些換膚的方法之后,并且對(duì)市面上比較認(rèn)可的Android-Skin-Loader換膚框架的源碼進(jìn)行了分析總結(jié)。再次記錄一下祭奠自己逝去的時(shí)間。

Android換膚技術(shù)已經(jīng)是很久之前就已經(jīng)被成熟使用的技術(shù)了,然而我最近才在學(xué)習(xí)和接觸熱修復(fù)的時(shí)候才看到。在看了一些換膚的方法之后,并且對(duì)市面上比較認(rèn)可的Android-Skin-Loader換膚框架的源碼進(jìn)行了分析總結(jié)。再次記錄一下祭奠自己逝去的時(shí)間。

Android換膚原理和Android-Skin-Loader框架解析

換膚介紹

換膚本質(zhì)上是對(duì)資源的一中替換包括、字體、顏色、背景、圖片、大小等等。當(dāng)然這些我們都有成熟的api可以通過控制代碼邏輯做到。比如View的修改背景顏色 setBackgroundColor ,TextView的 setTextSize 修改字體等等。但是作為程序員我們?cè)趺茨苋淌軐?duì)每個(gè)頁面的每個(gè)元素一個(gè)行行代碼做換膚處理呢?我們需要用最少的代碼實(shí)現(xiàn)最容易維護(hù)和使用效果***(動(dòng)態(tài)切換,及時(shí)生效)的換膚框架。

換膚方式一:切換使用主題Theme

使用相同的資源id,但在不同的Theme下邊自定義不同的資源。我們通過主動(dòng)切換到不同的Theme從而切換界面元素創(chuàng)建時(shí)使用的資源。這種方案的代碼量不多發(fā),而且有個(gè)很明顯的缺點(diǎn)不支持已經(jīng)創(chuàng)建界面的換膚,必須重新加載界面元素。 GitHub Demo

換膚方式二:加載資源包

加載資源包是各種應(yīng)用程序都在使用的換膚方法,例如我們最常用的輸入法皮膚、瀏覽器皮膚等等。我們可以將皮膚的資源文件放入安裝包內(nèi)部,也可以進(jìn)行下載緩存到磁盤上。Android的應(yīng)用程序可以使用這種方式進(jìn)行換膚。GitHub上面有一個(gè)start非常高的換膚框架 Android-Skin-Loader 就是通過加載資源包對(duì)app進(jìn)行換膚。對(duì)這個(gè)框架的分析這個(gè)也是這篇文章主要的講述內(nèi)容。

對(duì)比一下發(fā)現(xiàn)切換Theme可以進(jìn)行小幅度的換膚設(shè)置(比如某個(gè)自定義組件的主題),而如果我們想要對(duì)整個(gè)app做主題切換那么通過加載資源包的這種方式目前應(yīng)該說是比較好的了。

Android換膚知識(shí)點(diǎn)

換膚相應(yīng)的API

我們先來看一下Android提供的一些基本的api,通過使用這些api可以在App內(nèi)部進(jìn)行資源對(duì)象的替換。

 

  1. public class Resources{ 
  2.     public String getString(int id)throws NotFoundException { 
  3.         CharSequence res = mAssets.getResourceText(id); 
  4.         if (res != null) { 
  5.             return res; 
  6.         } 
  7.         throw new NotFoundException("String resource ID #0x" 
  8.                                     + Integer.toHexString(id)); 
  9.     } 
  10.     public Drawable getDrawable(int id)throws NotFoundException { 
  11.         /********部分代碼省略*******/ 
  12.     } 
  13.     public int getColor(int id)throws NotFoundException {{ 
  14.         /********部分代碼省略*******/ 
  15.     } 
  16.     /********部分代碼省略*******/ 

這個(gè)是我們常用的Resources類的api,我們通??梢允褂迷谫Y源文件中定義的 @+id String類型,然后在編譯出的R.java中對(duì)應(yīng)的資源文件生產(chǎn)的id(int類型),從而通過這個(gè)id(int類型)調(diào)用Resources提供的這些api獲取到對(duì)應(yīng)的資源對(duì)象。這個(gè)在同一個(gè)app下沒有任何問題,但是在皮膚包中我們?cè)趺传@取這個(gè)id值呢。

 

  1. public class Resources{ 
  2.     /********部分代碼省略*******/ 
  3.     /** 
  4. * 通過給的資源名稱返回一個(gè)資源的標(biāo)識(shí)id。 
  5. *@paramname 描述資源的名稱 
  6. *@paramdefType 資源的類型 
  7. *@paramdefPackage 包名 
  8. *@return返回資源id,0標(biāo)識(shí)未找到該資源 
  9. */ 
  10.     public int getIdentifier(String name, String defType, String defPackage){ 
  11.         if (name == null) { 
  12.             throw new NullPointerException("name is null"); 
  13.         } 
  14.         try { 
  15.             return Integer.parseInt(name); 
  16.         } catch (Exception e) { 
  17.             // Ignore 
  18.         } 
  19.         return mAssets.getResourceIdentifier(name, defType, defPackage); 
  20.     } 

Resources提供了可以通過 @+id 、Type、PackageName這三個(gè)參數(shù)就可以在AssetManager中尋找相應(yīng)的PackageName中有沒有Type類型并且id值都能與參數(shù)對(duì)應(yīng)上的id,進(jìn)行返回。然后我們可以通過這個(gè)id再調(diào)用Resource的獲取資源的api就可以得到相應(yīng)的資源。

這里我們需要注意的一點(diǎn)是 getIdentifier(String name, String defType, String defPackage) 方法和 getString(int id) 方法所調(diào)用Resources對(duì)象的mAssets對(duì)象必須是同一個(gè),并且包含有PackageName這個(gè)資源包。

AssetManager構(gòu)造

怎么構(gòu)造一個(gè)包含特定packageName資源的AssetManager對(duì)象實(shí)例呢?

 

  1. public final class AssetManagerimplements AutoCloseable{ 
  2.     /********部分代碼省略*******/ 
  3.     /** 
  4. Create a new AssetManager containing only the basic system assets. 
  5. * Applications will not generally use this method, instead retrieving the 
  6. * appropriate asset manager with {@linkResources#getAssets}. Not for 
  7. * use by applications. 
  8. * {@hide} 
  9. */ 
  10.     public AssetManager(){ 
  11.         synchronized (this) { 
  12.             if (DEBUG_REFS) { 
  13.                 mNumRefs = 0; 
  14.                 incRefsLocked(this.hashCode()); 
  15.             } 
  16.             init(false); 
  17.             if (localLOGV) Log.v(TAG, "New asset manager: " + this); 
  18.             ensureSystemAssets(); 
  19.         } 
  20.     } 

從AssetManager的構(gòu)造函數(shù)來看有 {@hide} 的朱姐,所以在其他類里面是直接創(chuàng)建AssetManager實(shí)例。但是不要忘記Java中還有反射機(jī)制可以創(chuàng)建類對(duì)象。

  1. AssetManager assetManager = AssetManager.class.newInstance(); 

讓創(chuàng)建的assetManager包含特定的PackageName的資源信息,怎么辦?我們?cè)贏ssetManager中找到相應(yīng)的api可以調(diào)用。

 

  1. public final class AssetManagerimplements AutoCloseable{ 
  2.     /********部分代碼省略*******/ 
  3.     /** 
  4. Add an additional set of assets to the asset manager. This can be 
  5. * either a directory or ZIP file. Not for use by applications. Returns 
  6. * the cookie of the added asset, or 0 on failure. 
  7. * {@hide} 
  8. */ 
  9.     public final int addAssetPath(String path){ 
  10.         synchronized (this) { 
  11.             int res = addAssetPathNative(path); 
  12.             if (mStringBlocks != null) { 
  13.                 makeStringBlocks(mStringBlocks); 
  14.             } 
  15.             return res; 
  16.         } 
  17.     } 

同樣改方法也不支持外部調(diào)用,我們只能通過反射的方法來調(diào)用。

 

  1. /** 
  2. * apk路徑 
  3. */ 
  4. String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk"
  5. AssetManager assetManager = null
  6. try { 
  7.     AssetManager assetManager = AssetManager.class.newInstance(); 
  8.     AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath); 
  9. } catch (Throwable th) { 
  10.     th.printStackTrace(); 

至此我們可以構(gòu)造屬于自己換膚的Resources了。

換膚Resources構(gòu)造

 

  1. public Resources getSkinResources(Context context){ 
  2.     /** 
  3. * 插件apk路徑 
  4. */ 
  5.     String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk"
  6.     AssetManager assetManager = null
  7.     try { 
  8.         AssetManager assetManager = AssetManager.class.newInstance(); 
  9.         AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath); 
  10.     } catch (Throwable th) { 
  11.         th.printStackTrace(); 
  12.     } 
  13.     return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); 

使用資源包中的資源換膚

我們將上述所有的代碼組合在一起就可以實(shí)現(xiàn),使用資源包中的資源對(duì)app進(jìn)行換膚。

 

  1. public Resources getSkinResources(Context context){ 
  2.     /** 
  3. * 插件apk路徑 
  4. */ 
  5.     String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk"
  6.     AssetManager assetManager = null
  7.     try { 
  8.         AssetManager assetManager = AssetManager.class.newInstance(); 
  9.         AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath); 
  10.     } catch (Throwable th) { 
  11.         th.printStackTrace(); 
  12.     } 
  13.     return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); 
  14. @Override 
  15. protected void onCreate(Bundle savedInstanceState){ 
  16.     super.onCreate(savedInstanceState); 
  17.     setContentView(R.layout.activity_main); 
  18.     ImageView imageView = (ImageView) findViewById(R.id.imageView); 
  19.     TextView textView = (TextView) findViewById(R.id.text); 
  20.     /** 
  21. * 插件資源對(duì)象 
  22. */ 
  23.     Resources resources = getSkinResources(this); 
  24.     /** 
  25. * 獲取圖片資源 
  26. */ 
  27.     Drawable drawable = resources.getDrawable(resources.getIdentifier("night_icon""drawable","com.tzx.skin")); 
  28.     /** 
  29. * 獲取文本資源 
  30. */ 
  31.     int color = resources.getColor(resources.getIdentifier("night_color","color","com.tzx.skin")); 
  32.  
  33.     imageView.setImageDrawable(drawable); 
  34.     textView.setText(text); 
  35.  

通過上述介紹,我們可以簡(jiǎn)單的對(duì)當(dāng)前頁面進(jìn)行換膚了。但是想要做出一個(gè)一個(gè)成熟換膚框架那么僅僅這些還是不夠的,提高一下我們的思維高度,如果我們?cè)赩iew創(chuàng)建的時(shí)候就直接使用皮膚資源包中的資源文件,那么這無疑就使換膚更加的簡(jiǎn)單已維護(hù)。

LayoutInflater.Factory

看過我前一篇 遇見LayoutInflater&Factory 文章的這部分可以省略掉.

很幸運(yùn)Android給我們?cè)赩iew生產(chǎn)的時(shí)候做修改提供了法門。

 

  1. public abstract class LayoutInflater{ 
  2.     /***部分代碼省略****/ 
  3.     public interface Factory{ 
  4.         public View onCreateView(String name, Context context, AttributeSet attrs); 
  5.     } 
  6.  
  7.     public interface Factory2extends Factory{ 
  8.         public View onCreateView(View parent, String name, Context context, AttributeSet attrs); 
  9.     } 
  10.     /***部分代碼省略****/ 

我們可以給當(dāng)前的頁面的Window對(duì)象在創(chuàng)建的時(shí)候設(shè)置Factory,那么在Window中的View進(jìn)行創(chuàng)建的時(shí)候就會(huì)先通過自己設(shè)置的Factory進(jìn)行創(chuàng)建。Factory使用方式和相關(guān)注意事項(xiàng)請(qǐng)移位到 遇見LayoutInflater&Factory ,關(guān)于Factory的相關(guān)知識(shí)點(diǎn)盡在其中。

Android-Skin-Loader解析

初始化

初始化換膚框架,導(dǎo)入需要換膚的資源包(當(dāng)前為一個(gè)apk文件,其中只有資源文件)。

 

  1. public class SkinApplicationextends Application{ 
  2.     public void onCreate(){ 
  3.         super.onCreate(); 
  4.         initSkinLoader(); 
  5.     } 
  6.     /** 
  7. * Must call init first 
  8. */ 
  9.     private void initSkinLoader(){ 
  10.         SkinManager.getInstance().init(this); 
  11.         SkinManager.getInstance().load(); 
  12.     } 

構(gòu)造換膚對(duì)象

導(dǎo)入需要換膚的資源包,并構(gòu)造換膚的Resources實(shí)例。

 

  1. /** 
  2. Load resources from apk in asyc task 
  3. *@paramskinPackagePath path of skin apk 
  4. *@paramcallback callback to notify user 
  5. */ 
  6. public void load(String skinPackagePath,final ILoaderListener callback){ 
  7.      
  8.     new AsyncTask<String, Void, Resources>() { 
  9.  
  10.         protected void onPreExecute(){ 
  11.             if (callback != null) { 
  12.                 callback.onStart(); 
  13.             } 
  14.         }; 
  15.  
  16.         @Override 
  17.         protected Resources doInBackground(String... params){ 
  18.             try { 
  19.                 if (params.length == 1) { 
  20.                     String skinPkgPath = params[0]; 
  21.                      
  22.                     File file = new File(skinPkgPath);  
  23.                     if(file == null || !file.exists()){ 
  24.                         return null
  25.                     } 
  26.                      
  27.                     PackageManager mPm = context.getPackageManager(); 
  28.                     //檢索程序外的一個(gè)安裝包文件 
  29.                     PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES); 
  30.                     //獲取安裝包報(bào)名 
  31.                     skinPackageName = mInfo.packageName; 
  32.                     //構(gòu)建換膚的AssetManager實(shí)例 
  33.                     AssetManager assetManager = AssetManager.class.newInstance(); 
  34.                     Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); 
  35.                     addAssetPath.invoke(assetManager, skinPkgPath); 
  36.                     //構(gòu)建換膚的Resources實(shí)例 
  37.                     Resources superRes = context.getResources(); 
  38.                     Resources skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration()); 
  39.                     //存儲(chǔ)當(dāng)前皮膚路徑 
  40.                     SkinConfig.saveSkinPath(context, skinPkgPath); 
  41.                      
  42.                     skinPath = skinPkgPath; 
  43.                     isDefaultSkin = false
  44.                     return skinResource; 
  45.                 } 
  46.                 return null
  47.             } catch (Exception e) { 
  48.                 e.printStackTrace(); 
  49.                 return null
  50.             } 
  51.         }; 
  52.  
  53.         protected void onPostExecute(Resources result){ 
  54.             mResources = result; 
  55.  
  56.             if (mResources != null) { 
  57.                 if (callback != null) callback.onSuccess(); 
  58.                 //更新多有可換膚的界面 
  59.                 notifySkinUpdate(); 
  60.             }else
  61.                 isDefaultSkin = true
  62.                 if (callback != null) callback.onFailed(); 
  63.             } 
  64.         }; 
  65.  
  66.     }.execute(skinPackagePath); 

定義基類

換膚頁面的基類的通用代碼實(shí)現(xiàn)基本換膚功能。

 

  1. public class BaseFragmentActivityextends FragmentActivityimplements ISkinUpdate,IDynamicNewView{ 
  2.      
  3.     /***部分代碼省略****/ 
  4.      
  5.     //自定義LayoutInflater.Factory 
  6.     private SkinInflaterFactory mSkinInflaterFactory; 
  7.      
  8.     @Override 
  9.     protected void onCreate(Bundle savedInstanceState){ 
  10.         super.onCreate(savedInstanceState); 
  11.      
  12.         try { 
  13.             //設(shè)置LayoutInflater的mFactorySet為true,表示還未設(shè)置mFactory,否則會(huì)拋出異常。 
  14.             Field field = LayoutInflater.class.getDeclaredField("mFactorySet"); 
  15.             field.setAccessible(true); 
  16.             field.setBoolean(getLayoutInflater(), false); 
  17.             //設(shè)置LayoutInflater的MFactory 
  18.             mSkinInflaterFactory = new SkinInflaterFactory(); 
  19.             getLayoutInflater().setFactory(mSkinInflaterFactory); 
  20.  
  21.         } catch (NoSuchFieldException e) { 
  22.             e.printStackTrace(); 
  23.         } catch (IllegalArgumentException e) { 
  24.             e.printStackTrace(); 
  25.         } catch (IllegalAccessException e) { 
  26.             e.printStackTrace(); 
  27.         }  
  28.          
  29.     } 
  30.  
  31.     @Override 
  32.     protected void onResume(){ 
  33.         super.onResume(); 
  34.         //注冊(cè)皮膚管理對(duì)象 
  35.         SkinManager.getInstance().attach(this); 
  36.     } 
  37.      
  38.     @Override 
  39.     protected void onDestroy(){ 
  40.         super.onDestroy(); 
  41.         //反注冊(cè)皮膚管理對(duì)象 
  42.         SkinManager.getInstance().detach(this); 
  43.     } 
  44.     /***部分代碼省略****/ 

SkinInflaterFactory

  • SkinInflaterFactory進(jìn)行View的創(chuàng)建并對(duì)View進(jìn)行換膚。

構(gòu)造View

 

  1. public class SkinInflaterFactoryimplements Factory{ 
  2.     /***部分代碼省略****/ 
  3.     public View onCreateView(String name, Context context, AttributeSet attrs){ 
  4.         //讀取View的skin:enable屬性,false為不需要換膚 
  5.         // if this is NOT enable to be skined , simplly skip it 
  6.         boolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false); 
  7.         if (!isSkinEnable){ 
  8.                 return null
  9.         } 
  10.         //創(chuàng)建View 
  11.         View view = createView(context, name, attrs); 
  12.         if (view == null){ 
  13.             return null
  14.         } 
  15.         //如果View創(chuàng)建成功,對(duì)View進(jìn)行換膚 
  16.         parseSkinAttr(context, attrs, view); 
  17.         return view
  18.     } 
  19.     //創(chuàng)建View,類比可以查看LayoutInflater的createViewFromTag方法 
  20.     private View createView(Context context, String name, AttributeSet attrs){ 
  21.         View view = null
  22.         try { 
  23.             if (-1 == name.indexOf('.')){ 
  24.                 if ("View".equals(name)) { 
  25.                     view = LayoutInflater.from(context).createView(name"android.view.", attrs); 
  26.                 }  
  27.                 if (view == null) { 
  28.                     view = LayoutInflater.from(context).createView(name"android.widget.", attrs); 
  29.                 }  
  30.                 if (view == null) { 
  31.                     view = LayoutInflater.from(context).createView(name"android.webkit.", attrs); 
  32.                 }  
  33.             }else { 
  34.                 view = LayoutInflater.from(context).createView(namenull, attrs); 
  35.             } 
  36.  
  37.             L.i("about to create " + name); 
  38.  
  39.         } catch (Exception e) {  
  40.             L.e("error while create 【" + name + "】 : " + e.getMessage()); 
  41.             view = null
  42.         } 
  43.         return view
  44.     } 

對(duì)生產(chǎn)的View進(jìn)行換膚

 

  1. public class SkinInflaterFactoryimplements Factory{ 
  2.     //存儲(chǔ)當(dāng)前Activity中的需要換膚的View 
  3.     private List<SkinItem> mSkinItems = new ArrayList<SkinItem>(); 
  4.     /***部分代碼省略****/ 
  5.     private void parseSkinAttr(Context context, AttributeSet attrs, View view){ 
  6.         //當(dāng)前View的所有屬性標(biāo)簽 
  7.         List<SkinAttr> viewAttrs = new ArrayList<SkinAttr>(); 
  8.          
  9.         for (int i = 0; i < attrs.getAttributeCount(); i++){ 
  10.             String attrName = attrs.getAttributeName(i); 
  11.             String attrValue = attrs.getAttributeValue(i); 
  12.              
  13.             if(!AttrFactory.isSupportedAttr(attrName)){ 
  14.                 continue
  15.             } 
  16.             //過濾view屬性標(biāo)簽中屬性的value的值為引用類型 
  17.             if(attrValue.startsWith("@")){ 
  18.                 try { 
  19.                     int id = Integer.parseInt(attrValue.substring(1)); 
  20.                     String entryName = context.getResources().getResourceEntryName(id); 
  21.                     String typeName = context.getResources().getResourceTypeName(id); 
  22.                     //構(gòu)造SkinAttr實(shí)例,attrname,id,entryName,typeName 
  23.                     //屬性的名稱(background)、屬性的id值(int類型),屬性的id值(@+id,string類型),屬性的值類型(color) 
  24.                     SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName); 
  25.                     if (mSkinAttr != null) { 
  26.                         viewAttrs.add(mSkinAttr); 
  27.                     } 
  28.                 } catch (NumberFormatException e) { 
  29.                     e.printStackTrace(); 
  30.                 } catch (NotFoundException e) { 
  31.                     e.printStackTrace(); 
  32.                 } 
  33.             } 
  34.         } 
  35.         //如果當(dāng)前View需要換膚,那么添加在mSkinItems中 
  36.         if(!ListUtils.isEmpty(viewAttrs)){ 
  37.             SkinItem skinItem = new SkinItem(); 
  38.             skinItem.view = view
  39.             skinItem.attrs = viewAttrs; 
  40.  
  41.             mSkinItems.add(skinItem); 
  42.             //是否是使用外部皮膚進(jìn)行換膚 
  43.             if(SkinManager.getInstance().isExternalSkin()){ 
  44.                 skinItem.apply(); 
  45.             } 
  46.         } 
  47.     } 

資源獲取

通過當(dāng)前的資源id,找到對(duì)應(yīng)的資源name。再從皮膚包中找到該資源name所對(duì)應(yīng)的資源id。

 

  1. public class SkinManagerimplements ISkinLoader{ 
  2.     /***部分代碼省略****/ 
  3.     public int getColor(int resId){ 
  4.         int originColor = context.getResources().getColor(resId); 
  5.         //是否沒有下載皮膚或者當(dāng)前使用默認(rèn)皮膚 
  6.         if(mResources == null || isDefaultSkin){ 
  7.             return originColor; 
  8.         } 
  9.         //根據(jù)resId值獲取對(duì)應(yīng)的xml的的@+id的String類型的值 
  10.         String resName = context.getResources().getResourceEntryName(resId); 
  11.         //更具resName在皮膚包的mResources中獲取對(duì)應(yīng)的resId 
  12.         int trueResId = mResources.getIdentifier(resName, "color", skinPackageName); 
  13.         int trueColor = 0; 
  14.         try{ 
  15.             //根據(jù)resId獲取對(duì)應(yīng)的資源value 
  16.             trueColor = mResources.getColor(trueResId); 
  17.         }catch(NotFoundException e){ 
  18.             e.printStackTrace(); 
  19.             trueColor = originColor; 
  20.         } 
  21.          
  22.         return trueColor; 
  23.     } 
  24.     public Drawable getDrawable(int resId){...} 

其他

除此之外再增加以下對(duì)于皮膚的管理api(下載、監(jiān)聽回調(diào)、應(yīng)用、取消、異常處理、擴(kuò)展模塊等等)。

總結(jié)

換膚就是這么簡(jiǎn)單~!~!

文章到這里就全部講述完啦,若有其他需要交流的可以留言哦~!~!

責(zé)任編輯:未麗燕 來源: 下雨天要逛街
相關(guān)推薦

2017-01-11 19:05:45

AndroidAndroid Loa詳解

2024-06-27 08:26:10

LooperAndroid內(nèi)存

2010-01-28 17:18:08

Android模擬器s

2010-02-05 18:04:36

Android程序框架

2013-12-06 10:35:28

Android游戲引擎libgdx教程

2024-01-18 08:31:22

go實(shí)現(xiàn)gorm框架

2010-03-02 14:24:00

Android應(yīng)用程序

2021-09-09 06:55:43

AndroidViewDragHel原理

2021-08-20 07:53:07

Android動(dòng)態(tài)換膚

2021-05-31 05:36:43

WebpackJavaScript 前端

2017-09-30 16:06:28

代碼注解分析

2009-10-27 11:16:20

VB.NET應(yīng)用框架

2013-07-05 14:41:27

Android

2017-02-21 12:20:20

Android事件分發(fā)機(jī)制實(shí)例解析

2013-07-29 16:22:21

Android緩存框架

2017-05-18 15:02:36

AndroidGC原理JVM內(nèi)存回收

2013-06-08 11:04:18

Android開發(fā)Pull解析XMLAndroid XML

2013-11-26 16:32:47

Android關(guān)機(jī)移動(dòng)編程

2010-08-04 15:12:54

Flex開發(fā)

2018-12-13 10:37:13

Android開發(fā)框架
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)