星期一, 九月 22, 2014

如何动态载入外部储存卡上的dex/jar包?

java的概念定制ClassLoader非常强大,Android提供了两个classloader来完成相关的任务.
PathClassLoader: 只能读取已经保存在系统里的jar/dex包
DexClassLoader:可以灵活读取任何权限允许的jar包。
我们设想可以读取保存在外部存储卡上的jar,更加符合实际的逻辑需要:
设计:
将动态jar包保存在/sdcard/temp/testjar.jar 文件里,在app里调用载入该包,并执行特定的方法:

1.Android Activtiy 准备动态载入相关代码:
将该方法加入到app某个菜单项目
onOptionsItemSelected方法里来调用,该app package为home.sunose假设:

public String classLoadFromExtern(Activity acivity)
{
 String result="";
 String sdcard=Environment.getExternalStorageDirectory().getAbsolutePath();
 String jarPath = sdcard
             + "/temp/testjar.jar";
String tmppath=acivity.getDir( "temp", 0).getAbsolutePath();
//注意tmppath是用来将testjar.jar展开的目录,这个目录必须属于当前app的用户id,所以必须使用
//context上下文相关的getDir得到该目录,避免没有权限的错误:
//DexClassLoader is not owned by the current user 这个异常.
//这是private DexFile(String sourceName, String outputName, int flags) throws IOException 
//的判断Libcore.os.getuid() != Libcore.os.stat(parent).st_uid

Log.d (TAG ,tmppath );
 DexClassLoader myClassLoader = new DexClassLoader (jarPath ,tmppath ,null, ClassLoader.getSystemClassLoader());
    try
     {
         Class<?>  clazz = myClassLoader .loadClass("com.test.First");
//         AIObject aio=(AIObject)clazz.newInstance();
         Method method = clazz.getDeclaredMethod("execute" ,String.class);
         method.setAccessible(true);
          result=(String)method .invoke(clazz.newInstance(), "test");
     }
     catch(Exception ex)
     {
         ex.printStackTrace();
     }
 return result;
}

2.准备桌面com.test.First.java类:
package com.test;
public class First
{
public String execute(String term)
{
String result=new java.util.Date()+" term is "+term;  
System.out.println(result);
return result;
}
}
5.mkdir com;mkdir com\test //建立标准java package目录结构,准备
6.cd c:\temp\com\test ,建立First.java,内容为步骤2内容

7.javac  com\test\First.java -d classes //编译java文件,并将编译后的class输出到c:\temp\classes目录

//准备dex打包编译的class
8.D:\Android\android-sdk-windows\build-tools\20.0.0\dx.bat  --dex --output c:\temp\testjar.jar  c:\temp\classes
9.adb push testjar.jar /sdcard/temp/  //推送到android上
10.在app中调用步骤1的方法后,adb logcat 可以看到
后台Logcat的输出:
D/dalvikvm(31581): DexOpt: --- BEGIN 'testjar.jar' (bootstrap=0) ---
D/dalvikvm(31608): DexOpt: load 5ms, verify+opt 1ms, 93356 bytes
D/dalvikvm(31581): DexOpt: --- END 'testjar.jar' (success) ---
D/dalvikvm(31581): DEX prep '/storage/emulated/0/temp/testjar.jar': unzip in 0ms, rewrite 98ms

11.也可以看到testjar.jar 实际展开后的目录:
 adb shell
ls /data/data/home.sunose/app_temp

星期一, 九月 08, 2014

嵌入在ScrollView里的GridView如何动态修改Layout 宽度? GridView In ScrollView programmatically change layout size ?

其实宽度修改都很简答。
以下伪代码:
GridView gv = this.findViewById(R.id.gridview_id);
               gv.getLayoutParams(). width = 1965;//px width
              gv.getLayoutParams(). height = LayoutParams. WRAP_CONTENT;
然后根据需要调用这个UI 刷新即可,一下两行代码可有可无. 
                   View vg =   getWindow().getDecorView().getRootView();
                   vg.invalidate();   
但是如果想给GridView加上水平滚动条则问题就有些麻烦了.需要将GridView嵌入在ScrollView或者HorizontalScrollView

<HorizontalScrollView>
 <GridView></GridView>
</HorizontalScrollView>

此时在代码中修改LayoutParams.width的方法,发现对GridView突然不起作用了.这个时候一个奇怪的技巧就起作用了.
将GridView再包围一个Layout即可。
修改为:
<HorizontalScrollView>
      <LinearLayout  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:orientation="vertical" >
 <GridView></GridView>
</LinearLayout>
</HorizontalScrollView>
这个技巧花费了数天才无意中发现。

星期二, 九月 02, 2014

Android 各种目录的获取。

Android 在内部rom里的存储路径和外部存储卡上的存储目录,可以通过
调用Activity的函数来获取。
Activity.getFilesDir().getAbsolutePath()得到:
   /data/data/<App Package name>/files
Activity.getExternalFilesDir(null).getAbsolutePath()得到:
/storage/emulated/0/Android/data/
<App Package name>/files
Environment.getExternalStorageDirectory().getAbsolutePath()
得到:
/storage/emulated/0

/storage/emulated/0是外部储存卡mount到系统的路劲入口,可以通过检测状态来确认是否有外部存储卡。
读取外部存储卡要在Androidmanifest.xml里加入权限:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>