Android Training Note
google为了让广大Android开发者能够高效地创建优质的app,专门针对开发者提供了Training板块,这个板块的学习资料是最一手的,来自google android工程师之手的。这个资料是每一个Android开发者都应该学习的手册,并且它是不断更新的。链接:
https://developer.android.com/training/index.html
中文版:
http://hukai.me/android-training-course-in-chinese/index.html
版本适配
Tip:为了能在几个Android版本中都能提供最好的特性和功能,你应该在你的app中使用Android Support Library,它能使你的app能在旧平台上使用最近的几个平台的APIs。
适配不同的系统版本俩种方法:
- 指定最小和目标API级别,具体来说,<uses-sdk> 元素中的 minSdkVersion和 targetSdkVersion属性,标明在设计和测试app时,最低兼容API的级别和最高适用的API级别(这个最高的级别是需要通过你的测试的)。随着新版本Android的发布,一些风格和行为可能会改变,为了能使你的app能利用这些变化,而且能适配不同风格的用户的设备,你应该设置 targetSdkVersion 的值去匹配最新的可用Android版本。
- 在运行时检查系统版本。
@SuppressLint("NewApi") @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); // 如果运行的环境 (部署到什么版本的手机 )大于3.0 if (android.os.Build.VERSION.SDK_INT > 11) { SearchView searchView = (SearchView) menu.findItem( R.id.action_search).getActionView(); searchView.setOnQueryTextListener(this);// 搜索的监听 } return true; }
Note:当解析XML资源时,Android会忽略当前设备不支持的XML属性。所以你可以安全地使用较新版本的XML属性
而不需要担心旧版本Android遇到这些代码时会崩溃。
国际化
为了支持多国语言,在 res/ 中创建一个额外的 values 目录以连字符和ISO国家代码结尾命名,比如 values-es/是为语言代码为"es"的区域设置的简单的资源文件的目录。Android会在运行时根据设备的区域设置,加载相应的资源。详见Providing
app/build.gradle
Android Studio使用Gradle 编译运行Android工程. 工程的每个模块以及整个工程都有一个build.gradle文件。通常你只需要关
注模块的build.gradle文件,该文件存放编译依赖设置,包括defaultConfig设置:
- compiledSdkVersion 是你的应用将要编译的目标Android版本,此处默认为你的SDK已安装的最新Android版本(目前应该是4.1或更高版本,如果你没有安装一个可用Android版本,就要先用SDKManager来完成安装),你仍然可以使用较老的版本编译项目,但把该值设为最新版本,使你可以使用Android的最新特性,同时可以在最新的设备上优化应用来提高用户体验。
- targetSdkVersion 表示你测试过你的应用支持的最高Android版本(同样用API level)表示).当Android发布最新版本后,你应该在最新版本的Android测试你的应用同时更新target sdk到Android最新版本,以便充分利用Android新版本的特性。
- 可选的布局文件:在XML中定义界面布局而不是在运行时去动态生成布局是有多个原因的,其中最重要的一个原因是这样可以使得你为不同大小的屏幕创建不同的布局文件。例如,你可以创建创建2个版本的布局文件,告诉系统在小的屏幕上使用其中一个布局文件,在大的屏幕上使用另外一个布局文件。
- 于字符串 @string/edit_message,该字符串资源与id使用了相同的名称(edit_message)。然而,对于资源的引用是区分类型的(比如id和字符串),因此,使用相同的名称不会引起冲突
- 当你在用户界面定义一个文本的时候,你应该把每一个文本字符串列入资源文件。对于所有字符串值,字符串资源能够单独的修改,在资源文件里你可以很容易的找到并且做出相应的修改。通过选择定义每个字符串,还允许您对不同语言本地化应用程序。
- android:layout_weight,“两份伏特加酒,一份咖啡利口酒”,意思就是这个酒中伏特加酒占三分之二。(请注意,使用权重的前提一般是给View的宽或者高的大小设置为0dp,然后系统根据上面的权重规则来计算View应该占据的空间。但是很多情况下,如果给View设置了match_parent的属性,那么上面计算权重时则不是通常的正比,而是反比,也就是权重值大的反而占据空间小)
- 代码中:String hello = getResources().getString(R.string.hello_world);
- 布局中:android:text="@string/hello_world"
屏幕适配
有4种普遍尺寸:小(small),普通(normal),大(large),超大(xlarge)
4种普遍分辨率:低精度(ldpi), 中精度(mdpi), 高精度(hdpi), 超高精度(xhdpi)
res/layout-large/
xhdpi: 2.0
hdpi: 1.5
mdpi: 1.0 (基准)
ldpi: 0.75
Note:低密度(ldpi)资源是非必要的,当你提供了hdpi的图像,系统会把hdpi的图像按比例缩小一半,去适配ldpi的屏幕。
Activity
为让新启动的activity能查询,定义key为一个public型的常量,通常使用应用程序包名作为前缀来定义意图键是很好的做法。在应用程序与其他应用程序进行交互时仍可以确保意图键唯一。
public final static String EXTRA_MESSAGE ="com.mycompany.myfirstapp.MESSAGE"; Intent intent = new Intent(this, DisplayMessageActivity.class); EditText editText = (EditText) findViewById(R.id.edit_message); String message = editText.getText().toString(); intent.putExtra(EXTRA_MESSAGE,message); TextView textView = new TextView(this); textView.setTextSize(40); textView.setText(message); // Set the text view as the activity layout setContentView(textView);
使你的activity有一个透明背景:<activity android:theme="@android:style/Theme.Translucent">
不像其他编程范式一样:程序从 main() 方法开始启动。Android系统根据生命周期的不同阶段唤起对应的回调函数来执行代码。系统存在启动与销毁一个activity的一套有序的回调函数。
如何实现一个符合用户期待的app,你需要注意下面几点:
- 当使用你的app的时候,不会因为有来电通话或者切换到其他app而导致程序crash。
- 当用户没有激活某个组件的时候不要消耗宝贵的系统资源。
- 当离开你的app并且一段时间后返回,不要丢失用户的使用进度。
- 当设备发送屏幕旋转的时候,不会crash或者丢失用户的使用进度。
activity生命周期
- onCreate里面尽量少做事情,避免程序启动太久都看不到界面
- 通常,你不应该使用onPause()来保存用户改变的数据 (例如填入表格中的个人信息) 到永久存储(File或者DB你确认用户期待那些改变能够被自动保存的时候(例如正在撰写邮件草稿),你可以把那些数据存到永久存储 。应该避免在onPause()时执行CPU-intensive 的工作,例如写数据到DB,因为它会导致切换到下一个activity变应该把那些heavy-load的工作放到onStop()去做)。如果你的activity实际上是要被Stop,那么你应该为了切换的顺畅而减少在OnPause()方法里面的工作量。
- 请注意,系统每次调用这个方法(onResume)时,activity都处于最前台,包括第一次创建的时候。所以,你应该实现onResume()来初始化那些你在onPause方法里面释放掉的组件,并执行那些activity每次进入Resumed state都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件)
- 因为系统在activity停止时会在内存中保存了Activity实例。有些时候你不需要实现onStop(),onRestart()甚至是onStart()方法.因为大多数的activity相对比较简单,activity会自己停止与重启,你只需要使用onPause()来停止正在运行的动作并断开系统资源链接。
重新创建Activity
- 通常来说,跳转到其他的activity或者是点击Home都会导致当前的activity执行onSaveInstanceState,因为这种情况下的activity都是有可能会被destory并且是需要保存状态以便后续恢复使用的,而从跳转的activity点击back回到前一个activity,那么跳转前的activity是执行退栈的操作,所以这种情况下是不会执行onSaveInstanceState的,因为这个activity不可能存在需要重建的操作。
- 当系统开始停止你的Activity时,只有在Activity实例会需要重新创建的情况下才会调用到onSaveInstanceState()(1) ,在这个方法里面可以指定额外的状态数据到Bunde中。如果这个Activity被destroyed然后这个实例又需要被重新创建时,系统会传递在 (1) 中的状态数据到 onCreate()与 onRestoreInstanceState().
- onRestoreInstanceState()方法会在 onStart() 方法之后执行. 系统仅仅会在存在需要恢复的状态信息时才会调用 onRestoreInstanceState() ,因此你不需要检查 Bundle 是否为null。
public void onSaveInstanceState(Bundle savedInstanceState) { // Save the user's current game state savedInstanceState.putInt(STATE_SCORE, mCurrentScore); savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel); super.onSaveInstanceState(savedInstanceState); }
public void onRestoreInstanceState(Bundle savedInstanceState) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState); // Restore state members from saved instance mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); }
建立隐式的Intent
尽管Android系统会确保每一个确定的intent会被系统内置的app(such as the Phone, Email, or Calendar app)之一接收,但是你还是应该在触发一个intent之前做验证是否有App接受这个intent的步骤。
Caution: 如果你触发了一个intent,而且没有任何一个app会去接收这个intent,那么你的app会crash。
为了验证是否有合适的activity会响应这个intent,需要执行queryIntentActivities() 来获取到能够接收这个intent的所有activity的list。如果返回的List非空,那么你才可以安全的使用这个intent。例如:
PackageManager packageManager = getPackageManager(); List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0); boolean isIntentSafe = activities.size() > 0;
如果 isIntentSafe 是 true , 那么至少有一个app可以响应这个intent。如果是 false 则说明没有app可以handle这个intent。Note:你必须在第一次使用之前做这个检查,若是不可行,则应该关闭这个功能。如果你知道某个确切的app能够handle这个intent,你也应该提供给用户去下载这个app的链接。?
一个完整的例子,演示了如何创建一个intent来查看地图,验证有app可以handle这个intent,然后启动它。
// Build the intent Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California"); Intent mapIntent = new Intent(Intent.ACTION_VIEW, location); // Verify it resolves PackageManager packageManager = getPackageManager(); List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0); boolean isIntentSafe = activities.size() > 0; // Start an activity if it's safe if (isIntentSafe) { startActivity(mapIntent); }
如果用户希望每次都弹出选择界面,而且每次都不确定会选择哪个app启动,例如分享功能,用户选择分享到哪个app都是不确定的,这个时候,需要强制弹出选择的对话框。(这种情况下用户不能选择默认启动的app)。为了显示chooser, 需要使用createChooser()来创建Intent
Intent intent = new Intent(Intent.ACTION_SEND); ... // Always use string resources for UI text. This says something like "Share this photo with" String title = getResources().getText(R.string.chooser_title); // Create and start the chooser Intent chooser = Intent.createChooser(intent, title); startActivity(chooser);
这样就列出了可以响应 createChooser() 中Intent的app,并且指定了标题。
被别的应用启动
如果你的app的功能对别的app也有用,那么你的app应该做好响应的准备。例如,如果你创建了一个social app,它可以分享messages 或者 photos 给好友,那么最好你的app能够接收 ACTION_SEND 的intent,这样当用户在其他app触发分享功能的时候,你的app能够出现在待选对话框。
为了使得其他的app能够启动你的activity,你需要在你的manifest文件的 <activity> 标签下添加 <intent-filter> 的属性。
例如,这个有intent filter的activity,当数据类型为文本或图像时会处理 ACTION_SEND 的intent。
<activity android:name="ShareActivity"> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain"/> <data android:mimeType="image/*"/> </intent-filter> </activity>
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Get the intent that started this activity Intent intent = getIntent(); Uri data = intent.getData(); // Figure out what to do based on the intent type if (intent.getType().indexOf("image/") != -1) { // Handle intents with image data ... } else if (intent.getType().equals("text/plain")) { // Handle intents with text ... } }
详情看:Intent过滤(92P)
Fragment
- 你可以把fragment想象成activity中一个模块化的部分,它拥有自己的生命周期,接收自己的输入事件,可以在acvitity运行过程中添加或者移除(有点像"子activity",你可以在不同的activities里面重复使用)可以使用包含action bar的 v7 appcompat library。v7 appcompat library 兼容Android2.1(API level 7),也包含了Fragment APIs。
- 和activity其中一个区别是当你创建Fragment的时候,你必须重写onCreateView()回调方法来定义你的布局。事实上,这是使Fragment运行起来,唯一一个需要你重写的回调方法
<fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" />
- 当你用XML布局文件的方式将Fragment添加进activity时,你的Fragment是不能被动态移除的
- 运用fragment(特别是那些你在运行时添加的)的一个很重要的规则就是在布局中你必须有一个容器view,fragment的layout将会放在这个view里面。
- 用addToBackStack(),当你移除或者替换一个fragment并把它放入返回栈中时,被移除的fragment的生命周期是stopped(不是destoryed).当用户返回重新恢复这个fragment,它的生命周期是restarts。如果你没把fragment放入返回栈中,那么当他被移除或者替换时,它的生命周期是destoryed。
- addToBackStack()方法提供了一个可选的String参数为事务指定了一个唯一的名字。这个名字不是必须的,除非你打算用FragmentManager.BackStackEntry APIs来进行一些高级的fragments操作。
- 经常地,你想fragment之间能相互交互,比如基于用户事件改变fragment的内容。所有fragment之间的交互需要通过他们关联的activity,两个fragment之间不应该直接交互。
定义一个接口
为了让fragment与activity交互,你可以在Fragment 类中定义一个接口,并且在activity中实现这个接口。Fragment在他们生命周期的onAttach()方法中捕获接口的实现,然后调用接口的方法来与Activity交互。
public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // Container Activity must implement this interface public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } ... }
现在Fragment就可以通过调用 OnHeadlineSelectedListener 接口实例的 mCallback 中的 onArticleSelected() (也可以是其它方法)方法与activity传递消息。举个例子,在fragment中的下面的方法在用户点击列表条目时被调用,fragment 用回调接口来传递事件给父Activity.
@Override public void onListItemClick(ListView l, View v, int position, long id) { mCallback.onArticleSelected(position); }
实现接口
为了接收回调事件,宿主activity必须实现在Fragment中定义的接口。举个例子,下面的activity实现了上面例子中的接口。
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener { ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article } }
数据存储
写Shared Preference:通过类似putInt()与putString()方法来传递keys与values。然后执行commit() 来提交改变. (后来有建议除非是出于线程同步的需要,否则请使用apply()方法来替代commit(),因为后者有可能会卡到UI Thread.)
存储在内部还是外部
所有的Android设备都有两个文件存储区域:"internal" 与 "external" 存储。 那两个名称来自于早先的Android系统中,当时的大多设备都内置了不可变的内存(internal storage),然后再加上一个类似SD card(external storage)这样可以卸载的存储部件。后来有一些设备把"internal" 与"external" 的部分都做成不可卸载的内置存储了,虽然如此,但是这一整块还是从逻辑上有被划分为"internal"与"external"的。只是现在不再以是否可以卸载来区分了。 下面列出了两者的区别:
Internal storage:
- 总是可用的
- 这里的文件默认是只能被你的app所访问的。
- 当用户卸载你的app的时候,系统会把internal里面的相关文件都清除干净。
- Internal是在你想确保不被用户与其他app所访问的最佳存储区域。
External storage:
- 并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。
- 是大家都可以访问的,因此你可能会失去保存在这里的文件的访问控制权。
- 当用户卸载你的app时,系统仅仅会删除external根目录(getExternalFilesDir())下的相关文件。
- External是在你不需要严格的访问权限并且你希望这些文件能够被其他app所共享或者是允许用户通过电脑访问时的最佳存储区域。
Tip: 尽管app是默认被安装到internal storage的,你还是可以通过在程序的manifest文件中声明android:installLocation 属性来指定程序也可以被安装到external storage。当某个程序的安装文件很大且用户的internal storage空间大于external storage时,他们会倾向与将该程序安装到external storage。
你可以执行openFileOutput() 来获取一个 FileOutputStream 用于写文件到internal目录
String filename = "myfile"; String string = "Hello world!"; FileOutputStream outputStream; try{ outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close(); } catch(Exception e){ e.printStackTrace(); }
如果,你需要缓存一些文件,你可以使用createTempFile()。例如:下面的方法从URL中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。
public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(fileName, null, context.getCacheDir()); catch (IOException e) { // Error while creating file } return file; }
你使用MODE_PRIVATE ,那么这些文件就不可能被其他app所访问
尽管external storage对于用户与其他app是可修改的,那么你可能会保存下面两种类型的文件。
- Public files :这些文件对与用户与其他app来说是public的,当用户卸载你的app时,这些文件应该保留。例如,那些被你的app拍摄的图片或者下载的文件。
- Private files: 这些文件应该是被你的app所拥有的,它们应该在你的app被卸载时删除掉。尽管由于存储在external storage,那些文件从技术上而言可以被用户与其他app所访问,但实际上那些文件对于其他app是没有意义的。因此,当用户卸载你的app时,系统会删除你的app的private目录。例如,那些被你的app下载的缓存文件。
如果你想要保存文件为public形式的,请使用getExternalStoragePublicDirectory()方法来获取一个 File 对象来表示存储在external storage的目录。这个方法会需要你带有一个特定的参数来指定这些public的文件类型,以便于与其他public文件进行分类。参数类型包括DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES. 如下:
public File getAlbumStorageDir(String albumName) { // Get the directory for the user's public pictures directory. File file = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; }
如果你想要保存文件为私有的方式,你可以通过执行getExternalFilesDir() 来获取相应的目录,并且传递一个指示文件类型的参数。每一个以这种方式创建的目录都会被添加到external storage封装你的app目录下的参数文件夹下(如下则是albumName)。这下面的文件会在用户卸载你的app时被系统删除。如下示例:
public File getAlbumStorageDir(Context context, String albumName) { // Get the directory for the app's private pictures directory. File file = new File(context.getExternalFilesDir( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; }
请记住,getExternalFilesDir() 方法会创建的目录会在app被卸载时被系统删除。如果你的文件想在app被删除时仍然保留,请使用getExternalStoragePublicDirectory().
不管你是使用 getExternalStoragePublicDirectory() 来存储可以共享的文件,还是使用 getExternalFilesDir() 来储存那些对与你的app来说是私有的文件,有一点很重要,那就是你要使用那些类似 DIRECTORY_PICTURES 的API的常量。那些目录类型参数可以确保那些文件被系统正确的对待。例如,那些以 DIRECTORY_RINGTONES 类型保存的文件就会被系统的media scanner认为是ringtone而不是音乐。
查询剩余空间
如果你事先知道你想要保存的文件大小,你可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生IOException。
那些方法提供了当前可用的空间还有存储系统的总容量。然而,系统并不能保证你可以写入通过 getFreeSpace() 查询到的容量文件, 如果查询的剩余容量比你的文件大小多几MB,或者说文件系统使用率还不足90%,这样则可以继续进行写的操作,否则你最好不要写进去。
Note:你并没有被强制要求在写文件之前一定有要去检查剩余容量。你可以尝试先做写的动作,然后通过捕获IOException 。这种做法仅适合于你事先并不知道你想要写的文件的确切大小。例如,如果在把PNG图片转换成JPEG之前,你并不知道最终生成的图片大小是多少。
删除文件
你应该在不需要使用某些文件的时候,删除它。删除文件最直接的方法是直接执行文件的 delete() 方法。
myFile.delete();
如果文件是保存在internal storage,你可以通过 Context 来访问并通过执行 deleteFile() 进行删除
myContext.deleteFile(fileName);
Note: 当用户卸载你的app时,android系统会删除以下文件:
- 所有保存到internal storage的文件。
- 所有使用getExternalFilesDir()方式保存在external storage的文件。
然而,通常来说,你应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。
数据库
就像保存文件到设备的internal storage 一样,Android会保存db到你的程序的private的空间上。你的数据是受保护的,因为那些区域默认是私有的,不可被其他程序所访问。
Note:因为那些操作可能是很耗时的,请确保你在background thread(AsyncTask or IntentService)里面去执行 getWritableDatabase() 或者 getReadableDatabase() 。
和查询信息一样,删除数据同样需要提供一些删除标准。DB的API提供了一个防止SQL注入的机制来创建查询与删除标准。
SQL Injection:(随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但是由于程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入)
这个机制把查询语句划分为选项条款与选项参数两部分。条款部分定义了查询的列的特征,参数部分用来测试是否符合前面的条款。因为处理的结果与通常的SQL语句不同,这样可以避免SQL注入问题。
Android图像和动画
高效显示Bitmap
- 移动设备的系统资源有限。Android设备对于单个程序至少需要16MB的内存。程序应该在这个最低内存限制下最优化程序的效率。当然,大多数设备的都有更高的限制需求。
- 在过去, 一个比较流行的内存缓存的实现方法是使用软引用(SoftReference)或弱引用(WeakReference)bitmap缓存, 然而这是不推荐的。从Android 2.3 (API Level 9) 开始,GC变得更加频繁的去释放soft/weak references,这使得他们就显得效率低下。而且在Android 3.0 (API Level 11)之前,备份的bitmap是存放在native memory 中,它不是以可预知的方式被释放,这样可能导致程序超出它的内存限制而崩溃。
- 在上面的例子中, 有1/8的程序内存被作为Cache. 在一个常见的设备上(hdpi),最小大概有4MB (32/8). 如果一个填满图片的GridView组件放置在800x480像素的手机屏幕上,大概会花费1.5MB (800x480x4 bytes), 因此缓存的容量大概可以缓存2.5页的图片内容.、
磁盘缓存可以用来保存那些已经处理好的位图,并且在那些图片在内存缓存中不可用时减少加载的次数。当然从磁盘读取图片会比从内存要慢,而且读取操作需要在后台线程中处理,因为磁盘读取操作是不可预期的。
Note:如果图片被更频繁的访问到,也许使用 ContentProvider 会更加的合适,比如在Gallery程序中。
内存缓存的检查是可以在UI线程中进行的,磁盘缓存的检查需要在后台线程中处理。磁盘操作永远都不应该在UI线程中发生。当图片处理完成后,最后的位图需要添加到内存缓存与磁盘缓存中,方便之后的使用。
处理配置改变(Handle Configuration Changes):详情请看P162
为了测试上面的效果,尝试在保留Fragment与没有这样做的情况下旋转屏幕。你会发现当你保留缓存时,从内存缓存中重新绘制几乎没有延迟的现象. 内存缓存中没有的图片可能在存在磁盘缓存中.如果两个缓存中都没有,则图像会像平时一样被处理。
Android管理bitmap memory的演变进程
- 在Android 2.2 (API level 8)以及之前, 当GC发生时, 你的应用的线程是会stopped的. 这导致了一个滞后,它会降低效率. 在Android 2.3上,添加了并发GC的机制, 这意味着在一个bitmap不再被引用到之后,内存会被立即回收.
- 在Android 2.3.3 (API level 10)以及之前, 一个bitmap的像素级数据是存放在native内存中的. 这些数据与bitmap本身是隔离的, bitmap本身是被存放在Dalvik heap中。在native内存中的pixel数据的释放是不可预测的,这意味着有可能导致一个程序容易超过它的内存限制并Crash。 自Android 3.0 (API Level 11)起, pixel数据则是与bitmap本身一起存放在dalvik heap中。
在Android 3.0及以上版本管理内存:详情请看P165
在Android 3.0 (API Level 11) 引进了BitmapFactory.Options.inBitmap. 如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 (API level 19)之前,只支持同等大小的位图。详情请查看inBitmap文档.
后面的不是很懂,日后再看
添加动画
滥用动画或者在错误时机使用动画也是有害的,例如:他们造成了延迟。这节课程告诉你如何应用常用动画类型来提升易用性,在不给用户用户增加烦恼的前提下提升性能。
俩个view间渐变
渐变动画(也叫消失)通常指渐渐的淡出某个 UI 组件,同时同步地淡入另一个。在你 App 想切换内容或 view的情况下,这种动画很有用。渐变简短不易察觉,它也能提供从一个界面到下一个之间流畅的转换。当你不使用它们,不管怎么样转换经常感到生硬而仓促。
将 config_shortAnimTime 系统属性暂存到一个成员变量里。这个属性为动画定义了一个标准的“短”持续时间。对于微妙或者快速发生的动画,这是个很理想的时间段。config_longAnimTime 和 config_mediumAnimTime 也行,如果你想用的话。
// 获取并缓存系统默认的“短”时长 mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime);
- 对于淡入的 view,设置 alpha 值为 0 并且设置 visibility 为 VISIBLE(要记得他起初被设置成了 GONE)。这让iew 可见了但是它是透明的。
- 对于淡入的 view,把 alpha 值从 0 动态改变到 1。同时,对于淡出的 view,把 alpha 值从 1 动态变到 0。
- 使用 Animator.AnimatorListener 中的 onAnimationEnd(),设置淡出 view 的 visibility 为 GONE。即使 alpha 值为0,也要把 view 的 visibility 设置成 GONE 来防止 view 占据布局空间,还能把它从布局计算中忽略,加速处理过程。
// 设置内容View为0%的不透明度,但是状态为“可见”, // 因此在动画过程中是一直可见的(但是为全透明)。 mContentView.setAlpha(0f); mContentView.setVisibility(View.VISIBLE); // 开始动画内容View到100%的不透明度,然后清除所有设置在View上的动画监听器。 mContentView.animate() .alpha(1f) .setDuration(mShortAnimationDuration) .setListener(null); // 加载View开始动画逐渐变为0%的不透明度, // 动画结束后,设置可见性为GONE(消失)作为一个优化步骤 //(它将不再参与布局的传递等过程) mLoadingView.animate() .alpha(0f) .setDuration(mShortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoadingView.setVisibility(View.GONE); } });
viewpager
@Override public void onBackPressed() { if (mPager.getCurrentItem() == 0) { // 如果用户当前正在看第一步(也就是第一页),那就要让系统来处理返回按钮。 //这个是结束(finish())当前活动并弹出回退栈。 super.onBackPressed(); } else { // 否则,返回前一页 mPager.setCurrentItem(mPager.getCurrentItem() - 1); } }
viewpager滑动动画用PageTransformer自定义动画
Android网络连接和云服务
管理网络
在执行网络操作之前检查设备当前连接的网络连接信息是个好习惯。这样可以防止你的程序在无意间连接使用了非意向的网络频道。如果网络连接不可用,你的应用应该优雅的做出响应。为了检测网络连接,我们需要使用到下面两个类:
- ConnectivityManager: 它会回答关于网络连接状态的查询,并在网络连接改变时通知应用程序。
- NetworkInfo: 描述一个给定网络类型(就本节而言是移动网络或Wi-Fi)的网络接口的状态。
public boolean isOnline() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); } private static final String DEBUG_TAG = "NetworkStatusExample"; ... ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); boolean isWifiConn = networkInfo.isConnected(); networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); boolean isMobileConn = networkInfo.isConnected(); Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn); Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);