2014-03-13 54 views
2

我需要列出strings.xml中的所有值(對於給定的語言環境), 基本上獲得應用程序中所有字符串的動態列表。strings.xml中的列表值

我在這裏的目的是列出我的(根深蒂固的)手機內的所有應用程序的所有字符串,以加快翻譯工作。

我沒有問題,訪問其他應用程序的AssetManager:

- 要獲得我使用所有的應用程序的列表:

List<PackageInfo> packs = getPackageManager().getInstalledPackages(0); 

- 要訪問的包管理器使用:

PackageManager manager = getPackageManager(); 
Resources mApk1Resources = manager.getResourcesForApplication(pname); 
AssetManager a = mApk1Resources.getAssets(); 

但我不太確定該從哪裏出發。

很明顯,這不是爲了生產目的,只是爲了幫助我的客戶的翻譯團隊(您瞭解中國原始設備製造商......),所以歡迎使用手提鑽石的解決方案:-P(反思,動態的國外背景,炸藥等......)

謝謝!

編輯1:我已經知道B.A.R.T它不適合我的需要我需要在現場手機(而不是一個壓縮的ROM),如「現場翻譯檢查應用程序」。特別是,我沒有立即訪問應用程序的源代碼,因爲我必須檢查大量的電話,有些電話幾歲。如果需要的話,我可以花時間去解決所有這些問題,但沒有更多。

編輯2:我真的需要運行在真實手機上的東西,而不需要電腦。我無法修改各個應用程序的源代碼,也無法反編譯ROM或使用外部工具,如B.A.R.T我需要全Java解決方案。

回答

0

好吧,我想我找到了。倒影做到了! 根甚至不需要。瘋狂知道你的手機上的任何應用程序可以看到所有其他的ressources ...

public void listAllStrings(){ 
    List<PackageInfo> packs = getPackageManager().getInstalledPackages(0); 
    for(int i=0;i<packs.size();i++) { 
     PackageInfo p = packs.get(i); 
     String pname = p.packageName; 

     Resources packageResources=null; 
     Context packageContext=null; 
     try 
     { 

      packageResources = MainActivity.this.getPackageManager().getResourcesForApplication(pname); 
      packageContext = MainActivity.this.createPackageContext(pname, Context.CONTEXT_INCLUDE_CODE + Context.CONTEXT_IGNORE_SECURITY);     
     } 
     catch(NameNotFoundException excep) 
     { 
      // the package does not exist. move on to see if another exists. 
      Log.e(pname, "Package not found!"); 
     } 

     Class<?> stringClass=null; 
     try 
     { 
       // using reflection to get the string class inside the R class of the package 
       stringClass = packageContext.getClassLoader().loadClass(pname + ".R$string"); 
     } 
     catch (ClassNotFoundException excep1) 
     { 
      // Less chances that class won't be there. 
      Log.e(pname, "R.string not found!"); 
     } 
     if(stringClass!=null){ 
      //For every fields of the string class 
      for(Field stringID : stringClass.getFields()) 
      { 
      try 
      { 
       //We get the id 
       int id = stringID.getInt(stringClass); 
       //We get the string value itself 
       String xmlResourceLayout = packageResources.getString(id); 

       Log.v(pname, xmlResourceLayout); 
      } 
      catch (Exception excep) 
      { 
      Log.e(pname, "Can't access : "+stringID.getName()); 
      continue; 
      } 
     } 
     } 

    } 
} 

希望這可以幫助別人:-)

編輯:對於一些的APK,R類不在所以我不得不寫一個find-R函數

String findR(Context packageContext){ 
    try { 
     //First case, the R class is easy to find ! 
     packageContext.getClassLoader().loadClass(packageContext.getPackageName() + ".R"); 
     return packageContext.getPackageName() + ".R"; 
    } catch (Exception e) { 
     //Second case, it is not at the root of the package, we will list all classes... 
     Log.v(packageContext.getPackageName(),"R not found... Continue searching !"); 
     try { 
     final PackageManager pm = getApplicationContext().getPackageManager(); 
     ApplicationInfo ai = pm.getApplicationInfo(packageContext.getPackageName(), 0); 

     DexFile dx = DexFile.loadDex(ai.sourceDir, File.createTempFile("opt", "dex", 
        getCacheDir()).getPath(), 0); 
     String pathToR=null; 
      // Search inside each and every class in the dex file 
      for(Enumeration<String> classNames = dx.entries(); classNames.hasMoreElements();) { 
       String className = classNames.nextElement(); 
       //for every single class, we will see if one of the fields is called app_name 
       //Log.v(className, "Class detail : "+className); 
       if(className.contains("R$string")) pathToR=className; 


      } 
      if(pathToR==null) throw new ClassNotFoundException(); 
      pathToR=pathToR.replaceAll("$string", ""); 
      Log.v(packageContext.getPackageName(), "R FOUND ! "+packageContext.getPackageName()); 
      return pathToR; 
     } catch (Exception exc) { 
      Log.e(packageContext.getPackageName(), "ERROR ! R NOT FOUND ..."); 
      return null; 
     } 

    } 

} 

但它仍然不能在任何情況下工作。所以我最終劫持apktools庫,以便它可以在手機上直接運行,...(我用的罐子從1.5.2 http://code.google.com/p/android-apktool/downloads/detail?name=apktool1.5.2.tar.bz2&can=2&q=)這是我的劫持類:

public class Decoder { 
    public void decode(ResTable resTable, ExtFile apkFile, File outDir) 
      throws AndrolibException { 
     Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder(); 
     ResFileDecoder fileDecoder = duo.m1; 
     ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); 

     attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator() 
       .next()); 

     Directory inApk, in = null, out; 
     try { 
      inApk = apkFile.getDirectory(); 
      out = new FileDirectory(outDir); 

      Log.v(MainActivity.SMALL_TAG,"Decoding AndroidManifest.xml with resources..."); 

      fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, 
        "AndroidManifest.xml"); 

      // fix package if needed 
      adjust_package_manifest(resTable, outDir.getAbsolutePath() 
        + "/AndroidManifest.xml"); 

      if (inApk.containsDir("res")) { 
       in = inApk.getDir("res"); 
      } 
      out = out.createDir("res"); 
     } catch (DirectoryException ex) { 
      throw new AndrolibException(ex); 
     } 

     ExtMXSerializer xmlSerializer = getResXmlSerializer(); 
     for (ResPackage pkg : resTable.listMainPackages()) { 
      attrDecoder.setCurrentPackage(pkg); 

      //Log.v(MainActivity.SMALL_TAG,"Decoding file-resources..."); 
      //for (ResResource res : pkg.listFiles()) { 
      // fileDecoder.decode(res, in, out); 
      //} 

      Log.v(MainActivity.SMALL_TAG,"Decoding values */* XMLs..."); 
      for (ResValuesFile valuesFile : pkg.listValuesFiles()) { 
       generateValuesFile(valuesFile, out, xmlSerializer); 
      } 
      generatePublicXml(pkg, out, xmlSerializer); 
      Log.v(MainActivity.SMALL_TAG,"Done."); 
     } 

     AndrolibException decodeError = duo.m2.getFirstError(); 
     if (decodeError != null) { 
      throw decodeError; 
     } 
    } 

    public Duo<ResFileDecoder, AXmlResourceParser> getResFileDecoder() { 
     ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); 
     decoders.setDecoder("raw", new ResRawStreamDecoder()); 
     //decoders.setDecoder("9patch", new Res9patchStreamDecoder()); 
     //TODO THIS DECODER CREATES ALL PROBLEMS ! 
     AXmlResourceParser axmlParser = new AXmlResourceParser(); 
     axmlParser.setAttrDecoder(new ResAttrDecoder()); 
     decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, 
       getResXmlSerializer())); 

     return new Duo<ResFileDecoder, AXmlResourceParser>(new ResFileDecoder(
       decoders), axmlParser); 
    } 
    public static final String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; 
    public static final String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; 
    public static final String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING"; 
    public ExtMXSerializer getResXmlSerializer() { 
     ExtMXSerializer serial = new ExtMXSerializer(); 
     serial.setProperty(PROPERTY_SERIALIZER_INDENTATION, 
       " "); 
     serial.setProperty(PROPERTY_SERIALIZER_LINE_SEPARATOR, 
       System.getProperty("line.separator")); 
     serial.setProperty(PROPERTY_DEFAULT_ENCODING, "utf-8"); 
     serial.setDisabledAttrEscape(true); 
     return serial; 
    } 

    public void adjust_package_manifest(ResTable resTable, String filePath) 
      throws AndrolibException { 

     // check if packages different, and that package is not equal to 
     // "android" 
     Map<String, String> packageInfo = resTable.getPackageInfo(); 
     if ((packageInfo.get("cur_package").equalsIgnoreCase(
       packageInfo.get("orig_package")) || ("android" 
       .equalsIgnoreCase(packageInfo.get("cur_package")) || ("com.htc" 
       .equalsIgnoreCase(packageInfo.get("cur_package")))))) { 

      Log.v(MainActivity.SMALL_TAG,"Regular manifest package..."); 
     } else { 
      try { 

       Log.v(MainActivity.SMALL_TAG,"Renamed manifest package found! Fixing..."); 
       DocumentBuilderFactory docFactory = DocumentBuilderFactory 
         .newInstance(); 
       DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 
       Document doc = docBuilder.parse(filePath.toString()); 

       // Get the manifest line 
       Node manifest = doc.getFirstChild(); 

       // update package attribute 
       NamedNodeMap attr = manifest.getAttributes(); 
       Node nodeAttr = attr.getNamedItem("package"); 
       mPackageRenamed = nodeAttr.getNodeValue(); 
       nodeAttr.setNodeValue(packageInfo.get("cur_package")); 

       // re-save manifest. 
       TransformerFactory transformerFactory = TransformerFactory 
         .newInstance(); 
       Transformer transformer = transformerFactory.newTransformer(); 
       DOMSource source = new DOMSource(doc); 
       StreamResult result = new StreamResult(new File(filePath)); 
       transformer.transform(source, result); 

      } catch (ParserConfigurationException ex) { 
       throw new AndrolibException(ex); 
      } catch (TransformerException ex) { 
       throw new AndrolibException(ex); 
      } catch (IOException ex) { 
       throw new AndrolibException(ex); 
      } catch (SAXException ex) { 
       throw new AndrolibException(ex); 
      } 
     } 
    } 

    private void generatePublicXml(ResPackage pkg, Directory out, 
      XmlSerializer serial) throws AndrolibException { 
     try { 
      OutputStream outStream = out.getFileOutput("values/public.xml"); 
      serial.setOutput(outStream, null); 
      serial.startDocument(null, null); 
      serial.startTag(null, "resources"); 

      for (ResResSpec spec : pkg.listResSpecs()) { 
       serial.startTag(null, "public"); 
       serial.attribute(null, "type", spec.getType().getName()); 
       serial.attribute(null, "name", spec.getName()); 
       serial.attribute(null, "id", 
         String.format("0x%08x", spec.getId().id)); 
       serial.endTag(null, "public"); 
      } 

      serial.endTag(null, "resources"); 
      serial.endDocument(); 
      serial.flush(); 
      outStream.close(); 
     } catch (IOException ex) { 
      throw new AndrolibException("Could not generate public.xml file", 
        ex); 
     } catch (DirectoryException ex) { 
      throw new AndrolibException("Could not generate public.xml file", 
        ex); 
     } 
    } 

    private void generateValuesFile(ResValuesFile valuesFile, Directory out, 
      ExtXmlSerializer serial) throws AndrolibException { 
     try { 
      OutputStream outStream = out.getFileOutput(valuesFile.getPath()); 
      serial.setOutput((outStream), null); 
      serial.startDocument(null, null); 
      serial.startTag(null, "resources"); 

      for (ResResource res : valuesFile.listResources()) { 
       if (valuesFile.isSynthesized(res)) { 
        continue; 
       } 
       ((ResValuesXmlSerializable) res.getValue()) 
         .serializeToResValuesXml(serial, res); 
      } 

      serial.endTag(null, "resources"); 
      serial.newLine(); 
      serial.endDocument(); 
      serial.flush(); 
      outStream.close(); 
     } catch (IOException ex) { 
      throw new AndrolibException("Could not generate: " 
        + valuesFile.getPath(), ex); 
     } catch (DirectoryException ex) { 
      throw new AndrolibException("Could not generate: " 
        + valuesFile.getPath(), ex); 
     } 
    } 

    private String mPackageRenamed = null; 

} 

,這裏是我如何使用它:

String pname = p.packageName; 

    Context packageContext = MainActivity.this.createPackageContext(pname, Context.CONTEXT_INCLUDE_CODE + Context.CONTEXT_IGNORE_SECURITY);  
    ApplicationInfo ai = packageContext.getApplicationInfo(); 

    Log.v(TAG,"Analysing : "+pname+" "+ai.sourceDir); 
    if(new File(destination.getAbsolutePath()+"/"+pname+".strings.xml").exists()){ 
     Log.v(TAG,"Already translated..."); 
     continue; 
    } 

    ApkDecoder decoder = new ApkDecoder(); 
    decoder.setApkFile(new File(ai.sourceDir)); 

    File directory = new File(Environment.getExternalStorageDirectory()+File.separator+"tempApk"); 
    directory.mkdirs(); 
    DeleteRecursive(directory); 
    directory.mkdirs(); 

    decoder.setOutDir(directory); 

    decoder.setForceDelete(true); 

    File frmwrk = new File(Environment.getExternalStorageDirectory()+File.separator+"framework"); 
    frmwrk.mkdirs(); 

    decoder.setFrameworkDir(Environment.getExternalStorageDirectory()+File.separator+"framework"); 

    decoder.setDecodeSources((short)0x0000); 

    decoder.setKeepBrokenResources(true); 

    try{ 
    //decoder.decode(); 
     new Decoder().decode(decoder.getResTable(), new ExtFile(new File(ai.sourceDir)), directory); 
    } catch (Throwable e) { 
     e.printStackTrace(); 
    } 

很多頭痛弦數;-)的

0

看到你說你接受「手持式鑿岩機,骯髒的解決方案」,這給一試:

在你Strings.xml,圍繞與<string-array>標籤所有的字符串值,並內化所有string標籤item的標記,如所以(使用簡單的查找和替換更換標籤名):

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <string name="hello">Hello World!</string> 
    <string name="app_name">YourAppNAme</string> 
    <string-array name="yourTitle"> 
     <item>item1</item> //used to be <string name"blabla">.... 
     <item>item2</item> 
     <item>item3</item> 
    </string-array> 
</resources> 

在你的XML佈局文件,你有你的ListView,提出:

<ListView 
     android:id=」@+id/yourListView」 
     android:layout_width=」match_parent」 
     android:layout_height=」wrap_content」 
     android:entries="@array/yourTitle" 
    /> 

現在,你的活動,你有你的列表,只需調用它應該已經從strings.xml中的物品填充的ListView

不要忘記當你完成恢復在strings.xml中的變化(並且備份總是一個好主意)!

更多Here

希望這有助於!

+0

嗯......問題是,我必須這樣做直播的手機上,我不能改變個別應用程序的strings.xml檔案,然後重新編譯ROM。我的客戶需要我檢查他現有30部手機的所有應用程序中的所有非翻譯字符串。 – Taiko

0

既然你已經紮根電話,在電話訪問每個APK,如何只編譯他們,並獲得了字符串的方法,請參見教程這裏:http://www.miui-au.com/add-ons/apktool/

+0

我完全可以爲一部手機做這個,但我需要在20-30部手機上這樣做,所以我真的需要一個適用於現場手機的應用程序。 – Taiko

+0

@Taiko您可以編寫自動腳本來自動執行其中大部分操作。我個人不知道任何方法只使用應用程序從手機上的每個應用程序檢索每個字符串。 – Kai

1

我可以看到:通常應用程序標籤的字符串將獲得資源字符串中的第一個id。 (未找到的正式文件尚未這個)

所以計劃是:讓一個應用程序的標籤的ID,每次增加的ID來獲得下一個資源字符串,直到我們得到Resources.NotFoundException

try { 
    List<ApplicationInfo> apps = getPackageManager() 
      .getInstalledApplications(0); 
    for (ApplicationInfo appInfo : apps) { 
     Resources mApk1Resources = getPackageManager() 
       .getResourcesForApplication(appInfo.packageName); 
     int id = appInfo.labelRes; 
     try { 
      Log.e("Test", "*******************************************"); 
      Log.e("Test", apps.get(0).packageName); 
      while (true) { 
       Log.e("Test", 
         "String resource: " 
          + mApk1Resources.getString(id)); 
       id++; 
      } 
     } catch (Resources.NotFoundException e) { 
      // Handle exception 
     } 
    } 
} catch (NameNotFoundException e) { 
    // Handle exception 
} 
+0

是我還是ressources ID始終以「0x7f0xxxxx」開頭? – Taiko

+0

有沒有辦法訪問另一個應用程序的R類?並使用反射來提取這些ID? – Taiko