Tips from Android SDK Articles

本文总结了Android开发中的一些实用技巧,包括避免内存泄漏的方法、提高布局效率的技术、使用ViewStub和Merge标签优化UI,以及非UI线程更新UI的最佳实践等。

本文的内容均来自Android SDK文档Resources下的Articles,本文对一些比较有用、常用的知识做一个简要的总结。

1、避免内存泄露

Context经常用来加载访问各种Resources,这就是为什么很多控件在创建的时候都要传入一个Context的原因。我们的程序大多数会使用到两种Context:Activity和Application,而且我们大多数使用前一个。

在一个Context-Activity的全局变量中不要保存Drawable、View、Context-Activity的引用,因为Drawable和View各自保存Context的引用,如果在全局变量中保存,那么当Activity销毁重新创建后会造成内存泄露。如果要在上下文中保存全局变量,请用Context-Application。

中文参考:http://www.cnblogs.com/xirihanlin/archive/2010/04/09/1707986.html

2、向后兼容性

Android各个版本存在着差异,为了编写出兼容性比较强的程序,除了设置<uses-sdk android:minSdkVersion="3" />之外,我们可以通过反射机制去实现这种兼容性,如,使用android.os.Debug.dumpHprofData(String filename),该方法只在Android1.0中去实现,但在Android1.5中并不支持,我们可以通过反射机制实现:


   private static void initCompatibility() {
       try {
           mDebug_dumpHprofData = Debug.class.getMethod(
                   "dumpHprofData", new Class[] { String.class } );
           /* success, this is a newer device */
       } catch (NoSuchMethodException nsme) {
           /* failure, must be older device */
       }
   }

   private static void dumpHprofData(String fileName) throws IOException {
       try {
           mDebug_dumpHprofData.invoke(null, fileName);
       } catch (InvocationTargetException ite) {
           /* unpack original exception when possible */
           Throwable cause = ite.getCause();
           if (cause instanceof IOException) {
               throw (IOException) cause;
           } else if (cause instanceof RuntimeException) {
               throw (RuntimeException) cause;
           } else if (cause instanceof Error) {
               throw (Error) cause;
           } else {
               /* unexpected checked exception; wrap and re-throw */
               throw new RuntimeException(ite);
           }
       } catch (IllegalAccessException ie) {
           System.err.println("unexpected " + ie);
       }
   }



3、判断Intent服务是否存在

Android为我们提供了非常多的Intent,也有第三方提供了很多Intent供我们使用,但由于版本、用户是否安装等原因,我们不清楚该Intent是否在本机上能否使用,为此,就要进行判断。代码如下:

public static boolean isIntentAvailable(Context context, String action) {
    final PackageManager packageManager = context.getPackageManager();
    final Intent intent = new Intent(action);
    List<ResolveInfo> list =
            packageManager.queryIntentActivities(intent,
                    PackageManager.MATCH_DEFAULT_ONLY);
    return list.size() > 0;
}


4、DrawableMutations

Drawable允许我们在不同的View之间共享资源,具体共享的实现机制是通过一个叫作"constant state"的方式去实现的,它保存了一个资源的所有的属性,比如,在一个Button中,constant state保存了Button的bitmap,整个程序默认的Button都是共享使用这个bitmap,从而节省了内存空间。constant state简单的理解如下图:

constant state的方式避免了很多内存的浪费,但是在实际的应用过程中如:试图修改Drawable的constate state,也遇到一些问题,比如,我们用一个ListView显示书籍列表,每个Book Item由一个星形的Drawable和Text构成,当该书是我们喜欢的书籍时,就显示该星形,反之,就使这个星形半透明。这个实现的大致代码如下:

Book book = ...;
TextView listItem = ...;

listItem.setText(book.getTitle());

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.setAlpha(255); // opaque
} else {
  star.setAlpha(70); // translucent
}


不幸的是,我们发现这个代码无效,所有的星形Drawable都是一样的。如下面左图:

如果我们使用了一个新的Drawable,但是因为BookItem的Drawable都引用到了同一个Constant state,因此我们尝试对Constatn state进行修改的时候,引用该Constant state的Drawable也会相应的改变。

Android 1.5以上版本提供了解决的办法:使用mutate()方法,使用该方法时,Drawable的Constant state会被复制,从而不会影响其他的Drawable,使用方式如下:


Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.mutate().setAlpha(255); // opaque
} else {
  star. mutate().setAlpha(70); // translucent
}

调用后效果如下图:




5、更快的屏幕方向变换

在手机屏幕方向变换的时候,会销毁当前Activity,并重新Create,此时Activity的所有数据都必须重新获取。那当这些数据是比较大的数据的时候,如比较大的图片、声音、视频,重新获取的方式是一种非常不明智的做法。我们可以通过设置configChanges把这个屏蔽掉,但这种做法并不推荐,具体原因请参考后面的扩展阅读。因此,为了解决这个问题,我们用到了一个方法:onRetainNonConfigurationInstance()该方法允许你在屏幕变换的时候进行数据的传递,你可以在这里传输所需要的数据,然后在onCreate里调用getLastNonConfigurationInstance()获取该数据。简要代码如下:

@Override
public Object onRetainNonConfigurationInstance() {
    final LoadedPhoto[] list = new LoadedPhoto[numberOfPhotos];
    keepPhotos(list);
    return list;
}

onCreate下加载数据:

private void loadPhotos() {
    final Object data = getLastNonConfigurationInstance();
    
    // The activity is starting for the first time, load the photos from Flickr
    if (data == null) {
        mTask = new GetPhotoListTask().execute(mCurrentPage);
    } else {
        // The activity was destroyed/created automatically, populate the grid
        // of photos with the images loaded by the previous activity
        final LoadedPhoto[] photos = (LoadedPhoto[]) data;
        for (LoadedPhoto photo : photos) {
            addPhoto(photo);
        }
    }
}

扩展阅读:https://devmaze.wordpress.com/2011/07/18/activities-and-background-threads-part-1/(可能需)


6、Layout 小技巧

  • 创建可复用的布局

Android为我们提供了布局的重复利用,如<requestFocus/>, <merge/>, <include/>,本文只介绍<include/>,<merge>会在稍后介绍。<include/>就如它的名字所提示的,它用于包含另外一个XML布局,如:
<com.android.launcher.Workspace
    android:id="@+id/workspace"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    launcher:defaultScreen="1">

    <include android:id="@+id/cell1" layout="@layout/workspace_screen" />
    <include android:id="@+id/cell2" layout="@layout/workspace_screen" />
    <include android:id="@+id/cell3" layout="@layout/workspace_screen" />

</com.android.launcher.Workspace>

在该布局里,include了3个layout,都是workspace_screen的layout,而且在<include/>标签中,只有3个属性:id, layout_width,layout_height,用于对原布局的覆盖。

  • 创建高效的布局

我们在创建UI的时候经常没有使用一个更有效的方式去实现UI。比较典型的例子就是LinearLayout的例子,它使用层成嵌套的方式去实现布局,但是每个View或者Layout都需要初始化、绘制等,如果是很多的嵌套的会,将会降低系统性能。而且当我们在是使用LinearLayout的weight属性时,都会使child view,重新measure两次。以下以一个实例来说明,我们要实现这样的一种效果,左边有一个图标,而标题在上方,下面是说明文字,如下面左图,简单的布局概括如下图右图。

该布局的源码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
    
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
                    
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>

</LinearLayout>
当我们使用该布局作为ListView的list item的layout时,就显得很浪费,我们可以通过RelativeLayout实现相同的效果,但更高效:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"

        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
                
        android:gravity="center_vertical"
        android:text="My Application" />

</RelativeLayout>
为什么使用RelativeLayout实现布局比LinearLayout更高效呢,我们可以通过 HierarchyViewer 去查看:


如上图所示,用RelativeLayout减少了View的个数,也就减少了系统初始化View的消耗。另外,消除了LinearLayout的weight所带来的额外消耗。

但是在使用RelativeLayout的过程中,也会带来一些不便,比如,我们要根据情况把说明文字隐藏起来,当我们使用RelativeLayout的时候,我们把说明文字放置在Parent的底部,把标题放置在说明文字的上方。但是当说明文字消失的时候,那标题就失去了相对的位置,那它要如何放置呢?我们可以通过设置layout_alignWithParentIfMissing属性去实现。

  • 使用ViewStubs

我们前面介绍了<include/>标签去共享不同的布局,现在介绍另外一种方式:ViewStubs,该方式与<include/>最大的区别就在于ViewStubs是在需要的时候才inflate,而<include/>在初始化的时候就inflate。利用ViewStubs的lazy include特点,我们可以把比较不常用的UI放在ViewStubs里面。以 Shelves为例,在进入主界面的界面以及导入书籍的界面如下:


上面右图下面的按钮就是使用ViewStubs实现的,整个布局的前后比较如下:
初始化界面:

导入书籍界面:

我们,可以看到,只有在导入书籍的时候该ViewStubs才inflate,在那之前一直都没inflate,这样就节省了系统的资源。
关于ViewStub的属性,做下简单介绍:
<ViewStub
  android:id="@+id/stub_import"
  android:inflatedId="@+id/panel_import"

  android:layout="@layout/progress_overlay"

  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_gravity="bottom" />

android:id是当前布局下的viewStub的id,而inflatedId是做是用来覆盖所include的root布局的id,android:layout之来指定所include的layout。
另外,我们可以通过inflate()或者显示设置ViewStubs为VISIBLE或者INVISIBLE初始化该ViewStubs。

  • Merging Layouts

<merge/>是另外一种复用布局的标签,它提供了布局的优化并且减少View tree的level数量。说的不容易理解,直接讲实例吧。我们用下面左边的布局实现其下方的效果:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <ImageView  
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
    
        android:scaleType="center"
        android:src="@drawable/golden_gate" />
    
    <TextView
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginBottom="20dip"
        android:layout_gravity="center_horizontal|bottom"

        android:padding="12dip"
        
        android:background="#AA000000"
        android:textColor="#ffffffff"
        
        android:text="Golden Gate" />

</FrameLayout>


我们使用HierarchyViewer观察里面布局情况,就能发现问题了:

我们可以发现,在这个View Tree里,有两个FrameLayout,我们的FrameLayout都是使用fill_parent,这明显就造成了浪费。系统多初始化了一个FrameLayout了。

这就是<merge/>派上用场的时候了,当LayoutInflater遇到<merge/>标签的时候,它会略过它,直接添加它的children到parent上。举个例子,我们用<merge/>代替了上面的<FrameLayout>:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView  
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
    
        android:scaleType="center"
        android:src="@drawable/golden_gate" />
    
    <TextView
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginBottom="20dip"
        android:layout_gravity="center_horizontal|bottom"

        android:padding="12dip"
        
        android:background="#AA000000"
        android:textColor="#ffffffff"
        
        android:text="Golden Gate" />

</merge>

我们可以发现,ImageView和TextView直接添加在上一级的FrameLayout里了:



有几点必须注意:

1、merge只能用在布局文件的root标签上

2、merge只能使用在root标签为FrameLayout的情况下

3、当我们inflate一个<merge/>标签开始的布局时,我们必须指定一个parent的ViewGroup,并且设置attachToRoot为True(请参考文档的inflate(int, android.view.ViewGroup, boolean)方法)


7、Painless Threading

非UI线程不能用于去修改UI,否则会报异常。非UI线程要修改UI,可使用如下方法:

或者也可以使用AsyncTask


8、Window Backgrounds & UI Speed

大多数的应用的Activity会在视觉上覆盖掉Windows的Background,而在这之中就隐藏了其中的一个额外的资源消耗存在:系统必须得为Windows的Background做初始化、渲染等。因此,我们可以把Windows的Background去掉,节约系统资源。效果比较为如下两图,左图为未去掉效果前的fps,右图为去掉效果后的fps


具体如何去掉?我们可以通过设置自定义的Theme去实现:

<resources>
    <style name="Theme.NoBackground" parent="android:Theme">
        <item name="android:windowBackground">@null</item>
    </style>
</resources>




### UniApp Android SDK Version 4.29 Documentation and Information For detailed documentation specific to UniApp's Android SDK version 4.29, it is recommended to refer directly to official resources provided by DCloud or other authorized distributors of UniApp tools. Official channels typically offer comprehensive guides including setup instructions, API references, release notes, and troubleshooting tips. Given the rapid evolution of software development kits (SDKs), especially those associated with cross-platform frameworks like UniApp, accessing up-to-date materials from primary sources ensures accuracy and relevance[^1]. When seeking such specialized documentation: - Visit the [official UniApp website](https://uniapp.dcloud.net.cn/) where announcements about new releases are made. - Explore dedicated sections within forums or community platforms related to UniApp discussions which may contain user-contributed insights on working specifically with version 4.29. - Utilize GitHub repositories linked closely with UniApp projects; these often host not only source code but also supplementary documents pertinent to various versions of both iOS and Android SDKs. In addition, developers should consider joining relevant online communities or subscribing to newsletters that focus on updates concerning this particular framework. This approach facilitates staying informed regarding any changes impacting functionality available through different iterations of the SDK. ```python import webbrowser def open_uniapp_documentation(): url = "https://uniapp.dcloud.net.cn/" webbrowser.open(url) open_uniapp_documentation() ``` --related questions-- 1. What major features were introduced in UniApp Android SDK version 4.29? 2. How does one integrate third-party libraries into a project using UniApp Android SDK v4.29? 3. Are there notable performance improvements between previous versions and UniApp Android SDK 4.29? 4. Can you provide examples of applications built successfully utilizing UniApp Android SDK 4.29?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值