精华内容
下载资源
问答
  • 7个代表性的Android应用程序完整源代码

    万次下载 热门讨论 2011-06-14 15:04:30
    7个比较具有代表性的Android应用程序源代码!
  • 反编译Android应用

    万人学习 2015-01-26 12:18:38
    学习技术的渠道多种多样,而通过反编译一些经典应用来学习是一种比较好的途径,在Android领域,有比较好的反编译工具,本课程将会教大家如何反编译Android应用
  • 88个经典Android应用程序打包下载【精品】

    千次下载 热门讨论 2012-09-11 13:07:30
    88个经典Android应用程序打包下载[源代码],程序经典,比较适合初学者。
  • Android | Android应用架构之MVVM模式

    万次阅读 多人点赞 2018-05-08 23:53:26
    早期的Android应用开发中,Activity/Fragment承担了过多的职责,它们不仅负责了应用界面的显示,而且负责了业务逻辑的处理。这样一来,Activity/Fragment很容易就变得臃肿、复杂,造成应用难以测试、维护和扩展。...

    前言

    早期的Android应用开发中,Activity/Fragment承担了过多的职责,它们不仅负责了应用界面的显示,而且负责了业务逻辑的处理。这样一来,Activity/Fragment很容易就变得臃肿、复杂,造成应用难以测试、维护和扩展。随着Android应用开发技术的不断发展和成熟,Android应用架构的设计得到了越来越多开发人员的关注和重视。目前,Android的应用架构主要有MVC、MVP和MVVM模式,本文将介绍一下MVVM模式。

    相关知识

    学习项目

    MVP模式

    MVVM模式可以说是MVP模式的进一步发展,所以先来了解一下MVP模式。

    MVP (Model-View-Presenter) 模式的结构如下图所示:

    MVP模式.png

    MVP模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,Presenter层主要负责业务逻辑的处理。

    在MVP模式中,Model层和View层不能直接通信,Presenter层负责充当中间人,实现Model层和View层之间的间接通信。View层和Presenter层互相持有对方的引用,实现View层和Presenter层之间的通信。

    MVP模式的主要优点是:分离了Model层和View层,分离了视图操作和业务逻辑,降低了耦合。

    MVVM模式

    MVVM (Model-View-ViewModel) 模式的结构如下图所示:

    MVVM模式.png

    MVVM模式与MVP模式一样,也将应用分为三层,并且各个对应的层的职责相似:

    • Model层,主要负责数据的提供。Model层提供业务逻辑的数据结构(比如,实体类),提供数据的获取(比如,从本地数据库或者远程网络获取数据),提供数据的存储。
    • View层,主要负责界面的显示。View层不涉及任何的业务逻辑处理,它持有ViewModel层的引用,当需要进行业务逻辑处理时通知ViewModel层。
    • ViewModel层,主要负责业务逻辑的处理。ViewModel层不涉及任何的视图操作。通过官方提供的Data Binding库,View层和ViewModel层中的数据可以实现绑定,ViewModel层中数据的变化可以自动通知View层进行更新,因此ViewModel层不需要持有View层的引用。ViewModel层可以看作是View层的数据模型和Presenter层的结合。

    MVVM模式与MVP模式最大的区别在于:ViewModel层不持有View层的引用。这样进一步降低了耦合,View层代码的改变不会影响到ViewModel层。

    MVVM模式相对于MVP模式主要有如下优点:

    • 进一步降低了耦合。ViewModel层不持有View层的引用,当View层发生改变时,只要View层绑定的数据不变,那么ViewModel层就不需要改变。而在MVP模式下,当View层发生改变时,操作视图的接口就要进行相应的改变,那么Presenter层就需要修改了。
    • 不用再编写很多样板代码。通过官方的Data Binding库,UI和数据之间可以实现绑定,不用再编写大量的findViewById()和操作视图的代码了。总之,Activity/Fragment的代码可以做到相当简洁。

    例子

    下面举一个简单的例子来实践MVVM模式。完整的项目代码可以去GitHub上查看:

    https://github.com/chongyucaiyan/MVVMDemo

    例子实现的主要功能是:点击按钮网络查询天气,查询成功后在界面上显示天气信息。主界面如下图所示:

    MVVMDemo界面.png

    MVVM模式的代码组织结构建议按照 业务功能 进行划分,具体操作是:每个业务功能独立一个包存放,每个业务功能包下面再按Model、View、ViewModel分包存放。所有的Model存放在model包下面,所有的Activity和Fragment存放在activity包下面,所有的ViewModel存放在viewmodel包下面。该例子比较简单,只有一个weather业务功能模块,最终的代码组织结构如下图所示:

    MVVMDemo代码组织结构.png

    编写Model

    查询杭州天气的URL为:

    http://www.weather.com.cn/data/cityinfo/101210101.html

    访问该URL将返回一串JSON字符串,如下所示:

    {"weatherinfo":{"city":"杭州","cityid":"101210101","temp1":"5℃","temp2":"20℃","weather":"晴转多云","img1":"n0.gif","img2":"d1.gif","ptime":"18:00"}}
    

    按照此JSON字符串,可以编写相应的实体类。WeatherData类的代码如下所示:

    public class WeatherData {
    
        private WeatherInfo weatherinfo;
    
        public WeatherInfo getWeatherinfo() {
            return weatherinfo;
        }
    
        public void setWeatherinfo(WeatherInfo weatherinfo) {
            this.weatherinfo = weatherinfo;
        }
    }
    

    WeatherInfo类的代码如下所示:

    public class WeatherInfo {
    
        private String city;
    
        private String cityid;
    
        private String temp1;
    
        private String temp2;
    
        private String weather;
    
        private String img1;
    
        private String img2;
    
        private String ptime;
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getCityid() {
            return cityid;
        }
    
        public void setCityid(String cityid) {
            this.cityid = cityid;
        }
    
        public String getTemp1() {
            return temp1;
        }
    
        public void setTemp1(String temp1) {
            this.temp1 = temp1;
        }
    
        public String getTemp2() {
            return temp2;
        }
    
        public void setTemp2(String temp2) {
            this.temp2 = temp2;
        }
    
        public String getWeather() {
            return weather;
        }
    
        public void setWeather(String weather) {
            this.weather = weather;
        }
    
        public String getImg1() {
            return img1;
        }
    
        public void setImg1(String img1) {
            this.img1 = img1;
        }
    
        public String getImg2() {
            return img2;
        }
    
        public void setImg2(String img2) {
            this.img2 = img2;
        }
    
        public String getPtime() {
            return ptime;
        }
    
        public void setPtime(String ptime) {
            this.ptime = ptime;
        }
    }
    

    编写ViewModel

    ViewModel不涉及任何的视图操作,只进行业务逻辑的处理。通过官方提供的Data Binding库,当ViewModel中的数据发生变化时,UI将自动更新。QueryWeatherViewModel的代码如下所示:

    public class QueryWeatherViewModel {
    
        private static final String TAG = "QueryWeatherViewModel";
    
        public final ObservableBoolean loading = new ObservableBoolean(false);
    
        public final ObservableBoolean loadingSuccess = new ObservableBoolean(false);
    
        public final ObservableBoolean loadingFailure = new ObservableBoolean(false);
    
        public final ObservableField<String> city = new ObservableField<>();
    
        public final ObservableField<String> cityId = new ObservableField<>();
    
        public final ObservableField<String> temp1 = new ObservableField<>();
    
        public final ObservableField<String> temp2 = new ObservableField<>();
    
        public final ObservableField<String> weather = new ObservableField<>();
    
        public final ObservableField<String> time = new ObservableField<>();
    
        private Call<WeatherData> mCall;
    
        public QueryWeatherViewModel() {
    
        }
    
        public void queryWeather() {
            loading.set(true);
            loadingSuccess.set(false);
            loadingFailure.set(false);
    
            mCall = RetrofitManager.get()
                    .create(QueryWeatherRequest.class)
                    .queryWeather();
            mCall.enqueue(new Callback<WeatherData>() {
    
                @Override
                public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
                    WeatherInfo weatherInfo = response.body().getWeatherinfo();
                    city.set(weatherInfo.getCity());
                    cityId.set(weatherInfo.getCityid());
                    temp1.set(weatherInfo.getTemp1());
                    temp2.set(weatherInfo.getTemp2());
                    weather.set(weatherInfo.getWeather());
                    time.set(weatherInfo.getPtime());
    
                    loading.set(false);
                    loadingSuccess.set(true);
                }
    
                @Override
                public void onFailure(Call<WeatherData> call, Throwable t) {
                    if (call.isCanceled()) {
                        Log.i(TAG, "call is canceled.");
                    } else {
                        loading.set(false);
                        loadingFailure.set(true);
                    }
                }
            });
        }
    
        public void cancelRequest() {
            if (mCall != null) {
                mCall.cancel();
            }
        }
    }
    

    编写View

    View不涉及任何的业务逻辑处理,只进行界面的显示。在xml布局文件中,通过官方提供的Data Binding库,将UI与ViewModel中的数据进行绑定,当ViewModel中的数据发生变化时,UI将自动更新。xml布局文件的代码如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
            <import type="android.view.View" />
    
            <variable
                name="viewModel"
                type="com.github.cyc.mvvmdemo.weather.viewmodel.QueryWeatherViewModel" />
        </data>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/default_content_padding"
            tools:context="com.github.cyc.mvvmdemo.weather.activity.QueryWeatherActivity">
    
            <Button
                android:id="@+id/btn_query_weather"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:text="@string/query_weather"
                android:enabled="@{viewModel.loading ? false : true}"
                android:onClick="@{() -> viewModel.queryWeather()}" />
    
            <RelativeLayout
                android:id="@+id/vg_weather_info"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/btn_query_weather"
                android:layout_marginTop="@dimen/query_weather_margin"
                android:visibility="@{viewModel.loadingSuccess ? View.VISIBLE : View.GONE}">
    
                <TextView
                    android:id="@+id/tv_city"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textStyle="bold"
                    android:text="@string/city" />
    
                <TextView
                    android:id="@+id/tv_city_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_city"
                    android:layout_alignBottom="@id/tv_city"
                    android:text="@{viewModel.city}"
                    tools:text="杭州" />
    
                <TextView
                    android:id="@+id/tv_city_id"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tv_city"
                    android:layout_marginTop="@dimen/query_weather_margin"
                    android:textStyle="bold"
                    android:text="@string/city_id" />
    
                <TextView
                    android:id="@+id/tv_city_id_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_city_id"
                    android:layout_alignBottom="@id/tv_city_id"
                    android:text="@{viewModel.cityId}"
                    tools:text="101210101" />
    
                <TextView
                    android:id="@+id/tv_temp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tv_city_id"
                    android:layout_marginTop="@dimen/query_weather_margin"
                    android:textStyle="bold"
                    android:text="@string/temperature" />
    
                <TextView
                    android:id="@+id/tv_temp1_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_temp"
                    android:layout_alignBottom="@id/tv_temp"
                    android:text="@{viewModel.temp1}"
                    tools:text="5℃" />
    
                <TextView
                    android:id="@+id/tv_tilde"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_temp1_value"
                    android:layout_alignBottom="@id/tv_temp"
                    android:text="@string/tilde" />
    
                <TextView
                    android:id="@+id/tv_temp2_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_tilde"
                    android:layout_alignBottom="@id/tv_temp"
                    android:text="@{viewModel.temp2}"
                    tools:text="10℃" />
    
                <TextView
                    android:id="@+id/tv_weather"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tv_temp"
                    android:layout_marginTop="@dimen/query_weather_margin"
                    android:textStyle="bold"
                    android:text="@string/weather" />
    
                <TextView
                    android:id="@+id/tv_weather_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_weather"
                    android:layout_alignBottom="@id/tv_weather"
                    android:text="@{viewModel.weather}"
                    tools:text="" />
    
                <TextView
                    android:id="@+id/tv_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/tv_weather"
                    android:layout_marginTop="@dimen/query_weather_margin"
                    android:textStyle="bold"
                    android:text="@string/release_time" />
    
                <TextView
                    android:id="@+id/tv_time_value"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tv_time"
                    android:layout_alignBottom="@id/tv_time"
                    android:text="@{viewModel.time}"
                    tools:text="10:00" />
            </RelativeLayout>
    
            <ProgressBar
                android:id="@+id/pb_progress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />
    
            <TextView
                android:id="@+id/tv_query_failure"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="@string/query_failure"
                android:visibility="@{viewModel.loadingFailure ? View.VISIBLE : View.GONE}" />
        </RelativeLayout>
    </layout>
    

    在Activity中,通过官方提供的Data Binding库加载布局文件,创建ViewModel,并绑定View和ViewModel。QueryWeatherActivity的代码如下所示:

    public class QueryWeatherActivity extends AppCompatActivity {
    
        // ViewModel
        private QueryWeatherViewModel mViewModel;
    
        // DataBinding
        private ActivityQueryWeatherBinding mDataBinding;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_query_weather);
            // 创建ViewModel
            mViewModel = new QueryWeatherViewModel();
            // 绑定View和ViewModel
            mDataBinding.setViewModel(mViewModel);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 取消请求
            mViewModel.cancelRequest();
        }
    }
    

    总结

    MVVM模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,ViewModel层主要负责业务逻辑的处理。各个层职责单一,结构清晰,应用可以很方便地进行测试、维护和扩展。

    参考

    展开全文
  • Android应用的调试

    万人学习 2015-02-09 16:33:39
    Android应用的调试
  • Android应用底部导航栏(选项卡)实例Demo

    千次下载 热门讨论 2012-04-18 14:17:55
    Android应用底部导航栏(选项卡)实例代码http://blog.csdn.net/cjjky/article/details/7209056
  • C#开发Android应用实战 使用Mono for Android和.NET C# PDF扫描版,希望对你有用处。 一般下载的都7.07MB的,那个只是一个样章,这个是书的全部。
  • Android应用自动更新代码实现

    千次下载 热门讨论 2012-04-27 21:37:34
    Android应用自动更新代码实现,完美实现代码的自动更新。
  • Android应用程序UI架构 高清PTT

    千次下载 热门讨论 2013-10-23 01:23:45
    Android应用程序中,每一个Activity组件都关联有一个或者若干个窗口,每一个窗口都对应有一个Surface。有了这个Surface之后,应用程序就可以在上面渲染窗口的UI。最终这些已经绘制好了的Surface都会被统一提交给...
  • Android应用程序消息处理机制

    千次下载 热门讨论 2013-10-23 01:22:30
    Android应用程序与传统的PC应用程序一样,都是消息驱动的。也就是说,在Android应用程序主线程中,所有函数都是在一个消息循环中执行的。Android应用程序其它线程,也可以像主线程一样,拥有消息循环。Android应用...
  • Android应用程序资源管理框架 PPT

    千次下载 热门讨论 2013-10-23 01:25:11
    Android应用程序主要由代码和资源组成。资源主要就是指那些与UI相关的东西,例如UI布局、字符串和图片等。代码和资源分开可以使得应用程序在运行时根据实际需要来组织UI。这样就可使得应用程序只需要编译一次,就...
  • Android应用程序进程管理

    千次下载 热门讨论 2013-10-23 01:21:26
    这个PPT讲Android应用程序进程的启动和回收,主要涉及到Zygote进程、System Server进程,以及组件管理服务ActivityManagerService、窗口服务WindowManagerService,还有专用驱动Low Memory Killer。通过了解Android...
  • Android应用开发进阶与实践

    千人学习 2017-03-16 22:21:12
    本课程是Android应用开发的进阶篇,以实践为主,学习本课程要求学习者首先掌握Java基础,Android开发基础,此课程在此基础上进一步探索,此课程不适合没有Java和Android基础的人员。
  • 使用Android Studio 创建第一个Android 应用

    万次阅读 多人点赞 2018-08-23 11:26:31
    一、使用Android Studio 开发Android 应用的流程 二、一个简单的登陆界面程序 1. 程序功能说明 2. 创建Android 工程 2. 1 了解Android 项目的组织结构 2. 2 修改布局文件 2. 3 核心逻辑的实现 2. 4 运行测试 ...

    一、使用Android Studio 开发Android 应用的流程

    • 创建一个Android 项目或Android 模块
    • 在XML 布局文件中定义应用程序的用户界面
    • 在Java 代码中编写业务实现

    二、一个简单的登陆界面程序

    1. 程序功能说明

    • 效果图:

    这里写图片描述

    • 主要功能:
      输入用户名和密码,点击登陆按钮做简单的判断:如果用户名和密码相同,则弹出一个登陆成功的提示

    2. 创建Android 工程

    注意:在创建工程的时候,根据业务需要选择合适的SDK版本,目前Android 5.0以上版本占据着很大的市场,所以我们从5.0 以上版本开始学习。

    2. 1 了解Android 项目的组织结构

    任何一个新建的项目都会 默认使用一个Android模式的项目结构,此结构是被Android Studio 转换过了的,适合快速开发,但不易于理解,切换到Project模式后如下:
    这里写图片描述

    大概了解项目的组织结构,重点认识一下重要的几个文件:

    • gradle 和 .idea : Android Studio 自动生成的一些文件,无需关心
    • app :项目中的代码、资源等内容全部都在该文件下,以后的开发也基本上是 在这个目录下进行的,主要有一下组成:

      • build : 不需要关心
      • libs : 第三方的 jar 包
      • AndroidTest ; 编写测试用例
      • java : 放置我们所有 Java 代码的地方。
      • res : 项目中使用的所有资源文件:

        • drawable : 图片
        • layout : 布局文件
        • values : 字符串
      • AndroidMainifest.xml : 整个项目的配置文件,四大组件都需要在这里注册使用,还包括应用程序权限申明
      • test : 编写单元测试
      • .gitingore : 将指定 的目录或者文件排除在版本控制之外。
      • app.iml : InteliJ IDEA 项目自动生成的文件,不需要关心
      • build.gradle : app 模块的 gradle 构建脚本, 指定很多项目的构建相关配置
      • proguard-rule.pro : 用于指定代码的混淆规则
    • Gradle: 包含了gradle wrapper的配置文件
    • .gitignore: 关于版本控制,将制定目录或文件排除在版本控制之外
    • build.gradle :项目全局的 gradle 构建脚本,通常不需要修该
    • gradle.properties: *全局的 gradle 配置文件,其中属性将会影响到项目的所有 gradle 编译脚本
    • gradlew 和gradlew.bat: 用来在命令行中使用gradle 命令,前者是在 Linux/Mac 中使用,后者是在Windows 中使用的。
    • xxx.iml :*用于标识这是一个 IntelliJ IDEA 项目
    • local.properties : 用于指定本机中的SDK 路径
    • settings.gradle : 指定项目中引入的所有模块

    然后来了解一下两个重要的 build.gradle 文件:首先要知道 Android Studio 项目是基于Gradle 构建的,至于这个Gradle 是个什么 ┏┛墓┗┓…(((m -__-)m,我也不知道。。。但是不影响我们学习,咱继续
    在我们的项目中有两个build.gradle 项目:一个在App目录下,一个在外层目录

    • 首先来看 app 下的build.gradle 文件:
      这里写图片描述
      由三部分组成:
      第一行 : 表明是一个应程序模块
      第二个 :是一个Android 闭包,主要内容有:

      • 指定项目的编译版本
      • 嵌套一个defaultConfig 闭包: 指定了应用程序的包名、最低兼容的Android 版本、在目标版本上做了充分测试、项目的版本号、项目的版本名
      • buildTypes 闭包:指定了安装文件的相关配置:一般有两个,一个是Debug版本(可省略),一个是release 版本,来了解一下release 版本的内容:
        • minifyEnabled :是否对代码进行混淆
        • proguardFiles : 混淆规则,有两个目录可选择:一个是SDK目录下的proguard-android.txt(通用规则),一个是当前目录proguard-rules.pro(自定义的一些混淆规则)

      第三个:dependencies 闭包:指定当前项目的所有依赖关系,有三种那个:本地依赖、库依赖和远程依:

      • implementation fileTree:本地依赖,表示将libs 目录下的所有.jar 后缀文件都添加到项目的构建路径当中
      • 第二行是个远程依赖声明:com.android.support:appcompat-v7:28.0.0-rc01 是标准的远程依赖格式,由 域名 + 组名 +版本号组成
      • testImplementation: 用于声明测试用例库,暂时用不到

    2. 2 修改布局文件

    在app>src>main>java>下找到:MainActivity.java文件
    这里写图片描述

    选中 activity_main 按住ctrl 键单击,可以跟踪到 activity_main.xml文件
    或者在pp>src>main>res>layout 中找到activity_main.xm文件, 这就是MainActivity配套的XML布局文件,打开该XML文件,进行修改。

    • 修改布局方式:先介绍最简单的布局LinearLayout 现行布局。修改如下:
      这里写图片描述
      跟默认的XML 文件相比,此处制作了两件事:

      • 修改布局方式为:LinearLayout
      • 添加了对其方式–为垂直:android:orientation=”Vertical”
    • 接着添加两个文本框控件
      这里写图片描述
      简单的制定控件的属性:

      • Id:制定该控件的唯一标识,用来获取界面控件
      • 控件高度、宽度:layout_width layout_height :match_parent(与父容器宽度相同),wrap_content(宽度取决于内容)
      • 控件输入类型以及输入框提示语属性:inputType hint
    • 最后添加Button 控件
      这里写图片描述
      指定属性,添加按钮点击事件:
      • 设置控件宽高,提示语等
      • 添加按钮点击事件:android:Onclick=” 事件处理函数名称 “
      • 在函数名称上按ALT+回车:选择Create event(view) in Mainactivity,之后转到ava代码处:
        这里写图片描述

    到此布局文件已完成,可以创建启动虚拟机(这里虚拟机的SDK版本必须和当初创建Android 项目的时候选择的SDK版本一致,否则会出错),根据预览效果再修改相应参数。

    2. 3 核心逻辑的实现

    • 首先要获取用户名和密码
    • 在按钮点击的处理函数中判断用户名和密码是否相等,并给出提示

    在MainActivity 中 定义两个EditText 的变量,获取用户名和密码,获取方式是通过findViewById,代码如下:

    public class MainActivity extends AppCompatActivity {
        EditText  name ;   //用户名
        EditText  pass;    //密码
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 从这里开始写我们自己的代码
            name =(EditText)findViewById(R.id.name);  //获取用户名
            pass =(EditText)findViewById(R.id.pass);  //获取密码
    

    登陆验证打代码:

        public void Check(View view) {
    
            String   mname ="Hello";
            String   mpass ="15pb";
            String user = name.getText().toString().trim();
            String pwd =  pass.getText().toString().trim();
            if (user.equals(mname) && pwd.equals(mpass))
            {
                Toast.makeText(this,
                        "恭喜,通过", Toast.LENGTH_SHORT).show();
            }
            else{
                Toast.makeText(this,
                        "很遗憾,继续努力", Toast.LENGTH_SHORT).show();
            }
        }

    2. 4 运行测试

    在工具栏找App旁边找到 绿色三角形或者 Shift +F10 运行App(选择符合自己项目SDK版本的模拟器)
    这里写图片描述

    展开全文
  • Android应用程序输入事件处理机制

    千次下载 热门讨论 2013-10-23 09:42:00
    Android应用程序中,有一类特殊的消息,是专门负责与用户进行交互的,它们就是触摸屏和键盘等输入事件。触摸屏和键盘事件是统一由系统输入管理器InputManager进行分发的。也就是说,InputManager负责从硬件接收...
  • Android应用图标微技巧,8.0系统中应用图标的适配

    万次阅读 多人点赞 2018-03-13 07:56:38
    大家好,2018年的第一篇文章到的稍微有点迟,也是因为在上一个Glide系列...三星今年推出的最新旗舰机Galaxy S9已经搭载了Android 8.0系统,紧接着小米、华为、OV等国产手机厂商即将推出的新年旗舰机也会搭载Android 8.0

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/79417483

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

    大家好,2018年的第一篇文章到的稍微有点迟,也是因为在上一个Glide系列结束之后一直还没想到什么好的新题材。

    现在已经进入了2018年,Android 8.0系统也逐渐开始普及起来了。三星今年推出的最新旗舰机Galaxy S9已经搭载了Android 8.0系统,紧接着小米、华为、OV等国产手机厂商即将推出的新年旗舰机也会搭载Android 8.0系统。因此,现在已经是时候需要让我们的应用程序对Android 8.0系统进行适配了。

    其实在去年Android 8.0系统刚推出的时候,我就仔细翻阅过Google官方的功能变更文档。变更项着实不少,但是真正需要我们去进行功能适配的地方却并不多。总结了一下,最主要需要进行适配的地方有两处:应用图标和通知栏。那么我们就分为上下两篇来分别对这两处功能适配进行讲解,先从应用图标开始。

    为什么要进行应用图标适配?

    可能有些朋友觉得困惑,应用图标这种东西从Android远古时代就已经有了,而且功能格外的简单,就是放张图片而已,这有什么好适配的呢?但实际上,在当前Android环境下,应用图标功能是极其混乱的。

    如果说要讲一讲手机应用图标的历史,其实要从苹果开始讲起。在上世纪80年代,苹果还在设计Lisa和Macintosh电脑的时候,乔布斯就是个圆角矩形的狂热支持者。当时苹果的工程师写出了一套绝妙的算法,可以在电脑上绘制出圆和椭圆,所有观看者都被震惊了,除了乔布斯,因为乔布斯觉得圆和椭圆虽然也不错,但是如果能绘制出带圆角的矩形就更好了。当时那位工程师觉得这是不可能实现的,而且也完全用不着圆角矩形,能满足基本的绘图需求就可以了。乔布斯愤怒地拉着他走了3条街,指出大街上各种应用圆角矩形的例子,最后那位工程师第二天就做出了绘制圆角矩形的功能。

    因此,在2007年一代iPhone诞生的时候,所有应用程序的图标都毫不出乎意料地使用了圆角矩形图标,即使是第三方应用也被强制要求使用圆角矩形图标,并且这一规则一直延续到了今天的iOS 11当中,如下图所示:

    相反,Android系统在设计的时候就不喜欢苹果这样的封闭与强制,而是选择了自由与开放,对应用图标的形状不做任何强制要求,开发者们可以自由进行选择:

    可以看到,在Android上,应用图标可以是方形、圆形、圆角矩形、或者是其他任意不规则图形。

    本来就是两家公司不同的设计理念,也说不上孰高孰低。但由于Android操作系统是开源的,国内一些手机厂商在定制操作系统的时候就把这一特性给改了。比如小米手机,就选择了向苹果靠拢,强制要求应用图标圆角化。如果某些应用的图标不是圆角矩形的呢?小米系统会自动给它加上一个圆角的效果,如下图所示:

    小米的这种做法看上去是向苹果学习,但实际上是相当恶心的。因为谁都可以看出来,这种自动添加的圆角矩形非常丑,因此很多公司就索性直接将应用的图标都设计成圆角矩形的,正好Android和iOS都用同一套图标还省事了。

    但是这就让Google不开心了,这不是变向强制要求开发者必须将图标设计成圆角矩形吗?于是在去年的Google I/O大会上,Google点名批评了小米的这种做法,说其违反了Android自由和开放的理念。

    除了变向强制要求应用图标圆角化,小米的这种处理方式还有一个弊端,就是如果应用图标的圆角弧度和小米系统要求的不同,那么会出现异常丑陋的效果:

    看到这样的应用图标,真的是一脸尴尬症都要犯了。就因为这两款应用图标的圆角弧度设计得大于了小米系统要求的圆角弧度,就被自动添加上了这样丑陋的白边。

    问题是已经存在了,那么应该怎么解决呢?说实话,这确实是一个长期以来都让人头疼的问题,Google多年来对此也是睁一只眼闭一只眼。终于在Android 8.0系统中,Google下定决心要好好整治一下Android应用图标的规范性了,今天我们就来学习一下。

    8.0系统的应用图标适配

    这个问题对于Google来说还是挺难解决的。因为Google一直在强调自由与开放,那么小米强制要求所有应用图标都必须圆角化也是人家的自由呀,你不准人家这么干是不是本身就违背了自由和开放的理念呢?当然我们在这里讨论这个,有点像讨论先有鸡还是先有蛋的感觉,不过Google还是想出了一套完美的解决方案。

    从Android 8.0系统开始,应用程序的图标被分为了两层:前景层和背景层。也就是说,我们在设计应用图标的时候,需要将前景和背景部分分离,前景用来展示应用图标的Logo,背景用来衬托应用图标的Logo。需要注意的是,背景层在设计的时候只允许定义颜色和纹理,但是不能定义形状。

    那么应用图标的形状由谁来定义呢?Google将这个权利就交给手机厂商了。不是有些手机厂商喜欢学习苹果的圆角图标吗?没问题,由于应用图标的设计分为了两层,手机厂商只需要在这两层之上再盖上一层mask,这个mask可以是圆角矩形、圆形或者是方形等等,视具体手机厂商而定,就可以瞬间让手机上的所有应用图标都变成相同的规范。原理示意图如下:

    可以看到,这里背景层是一张蓝色的网格图,前景层是一张Android机器人Logo图,然后盖上一层圆形的mask,最终就裁剪出了一张圆形的应用图标。

    我一定要适配吗?

    有些朋友可能会觉得这种分成两层的应用图标设计太过于麻烦,不适配可以吗?也有些朋友可能会说,自己的APP并没有做过应用图标适配,在Android 8.0手机上也照样跑得好好的。

    事实上,这个新功能Google是准备让它慢慢过渡的,而不是一次性就强推给所有的开发者。如果你的APP中的targetSdkVersion是低于26的,那么就可以不用进行应用图标适配,Android 8.0系统仍然是向下兼容的。但是如果你将targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的APP已经做好了8.0系统的适配工作,当然包括了应用图标的适配。

    如果你将targetSdkVersion指定到了26,但是却没有进行Android 8.0系统的应用图标适配,那么会出现什么样的效果呢?这里我举几个反面示例:

    这是Google Pixel手机上的截图,操作系统是Android 8.0。可以看到,这两个应用的图标都非常奇怪,本来设计的都是一个圆角矩形的图标,但是却又在外面套上了一个白色的圆圈。为什么会出现这种情况呢?就是因为这两个应用都将targetSdkVersion指定到了26以上,但是却又没有做8.0系统的应用图标适配,而Pixel手机设定的mask是圆形的,所以就自动在应用图标的外层套了一个白色的圆圈。

    由此可以看出,爱奇艺和饿了么这两款应用都是没有在Pixel上进行兼容性测试的。不过考虑到它们都是只在国内市场提供服务,因此也情有可原。

    当然了,国内的Android 8.0手机很快也要开始普及了,我相信没有任何人会希望自己的APP也出现上述的效果,因此下面我们就来开始具体学习,如何进行8.0系统的应用图标适配。

    新建一个项目

    如果有人问我8.0系统应用图标适配到底难不难?这里我会回答,一点都不难。相信所有看完这篇文章的人立马就能学会,但前提是你需要有一个好的工具——Android Studio 3.0或更高版本。

    很高兴告诉大家,Android Studio 3.0中已经内置了8.0系统应用图标适配的功能,如果你已经安装了Android Studio 3.0的话,那么恭喜你,你已经成功了百分之九十了。如果你还在用老版的Android Studio,那么赶快去升级一下,然后再接着看这篇文章。

    好的,那么现在我们就用Android Studio 3.0来新建一个项目,就叫它IconTest吧。

    创建好项目之后,打开app/build.gradle文件检查一下,确保targetSdkVersion已经指定到了26或者更高,如下所示:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 26
        defaultConfig {
            applicationId "com.example.icontest"
            minSdkVersion 15
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
    }

    可以看到,这里我在创建新项目的时候默认targetSdkVersion就是26,如果你是低于26的话,说明你的Android SDK有些老了,最好还是更新一下。当然如果你懒得更新也没关系,手动把它改成26就可以了。

    接下来打开AndroidManifest.xml文件,代码如下所示:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.icontest">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    这里我们需要关注的点是android:icon这个属性,通过这个属性,我们将应用的图标指定为了mipmap目录下的ic_launcher文件。另外大家可能注意到还有一个android:roundIcon属性,这是一个只适用在Android 7.1系统上的过渡版本,很快就被8.0系统的应用图标适配所替代了,我们不用去管它。

    刚才说了,应用图标被指定为了mipmap目录下的ic_launcher文件,那么我们快去看下这个文件吧:

    这里虽然目录很多,但是相信任何只要是入了门的Android开发者都能看得懂。唯一需要我们留意的就是mipmap-anydpi-v26这个目录,这个目录表示什么意思呢?就是Android 8.0或以上系统的手机,都会使用这个目录下的ic_launcher来作为图标。

    你会发现,mipmap-anydpi-v26目录下的ic_launcher并不是一张图片,而是一个XML文件,我们打开这个文件看一下,代码如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
        <background android:drawable="@drawable/ic_launcher_background" />
        <foreground android:drawable="@drawable/ic_launcher_foreground" />
    </adaptive-icon>

    这是一个8.0系统应用图标适配的标准写法,在<adaptive-icon>标签中定义一个<background>标签用于指定图标的背景层,定义一个<foreground>标签用于指定图标的前景层。

    那么我们分别来看一下背景层和前景层分别都是些什么内容吧,首先打开ic_launcher_background文件,内容如下图所示:

    这是一个使用SVG格式绘制出来的带纹理的底图。当然如果你看不懂这里面的代码也没有关系,因为我也看不懂。SVG格式的图片都是使用AI、PS等图像编辑软件制作之后导出的,基本没有人可以手工编写SVG图片。

    当然,背景层并不是一定要用SVG格式的图片,你也可以使用普通的PNG、JPG等格式的图片,甚至是直接指定一个背景色都可以。

    看完了背景层接着我们来看前景层,打开ic_launcher_foreground文件,内容如下所示:

    类似地,这里也是使用SVG格式绘制出了一个Android机器人的Logo,并且这个机器人还是带投影效果的。当然了,前景层我们也是可以使用PNG、JPG等格式的图片的,待会儿会进行演示。

    好的,现在已经把应用图标相关部分的代码都解释完了,那么这样一个刚刚创建完成的空项目运行起来到底会是什么样的效果呢?我们跑一下看看就知道了,如下图所示:

    可以看到,这就是一个前景层盖在背景层上,然后再被圆形mask进行裁剪之后的效果。

    好的,那么现在剩下的问题就是,我们如何才能对自己的应用图标在Android 8.0系统上进行适配?

    开始适配

    看到爱奇艺的8.0系统应用图标适配工作做得这么差,我就准备拿爱奇艺来做为例子了,我们一起来帮爱奇艺的Android版做个漂亮的应用图标适配吧。

    那么很显然,根据8.0系统的应用图标设计,我们需要准备一个前景层和一个背景层才行。

    前景层也就是爱奇艺的Logo了,这里我通过Photoshop把爱奇艺的Logo图取了出来。

    由于这是一张背景透明的图片,如果直接贴到文章里面就一片白色,啥也看不见了,于是我只好在文章里贴了一张带灰色背景的图片。如果大家需要获取爱奇艺这张前景图的原图,可以点击 这里 获取。

    解决了前景层,接下来我们来看背景层。其实背景层比前景层就简单多了,一般如果没有什么特殊需求的话,背景层直接使用某种纯色就可以了。

    这里我用Photoshop吸取了一下爱奇艺原始应用图标的背景色,值是#04ca00。当然,爱奇艺的背景色并不是完全的纯色,而是有细微的颜色渐变的。不过这里我们只是举例讲解而已,就不追究这些细节了。

    那么现在前景层和背景层都准备好了,接下来我们正式开始进行8.0系统的应用图标适配。重新回到IconTest项目当中,然后按下Windows:Ctrl+Shift+A / Mac:command+shft+A 快捷键,并输入Image Asset,如下所示:

    点击回车键打开Asset Studio编辑器,在这里就可以进行应用图标适配了。

    这个Asset Studio编辑器非常简单好用,一学就会。左边是操作区域,右边是预览区域。

    先来看操作区域,第一行的Icon Type保持默认就可以了,表示同时创建兼容8.0系统以及老版本系统的应用图标。第二行的Name用于指定应用图标的名称,这里也保持默认即可。接下来的三个页签,Foreground Layer用于编辑前景层,Background Layer用于编辑背景层,Legacy用于编辑老版本系统的图标。

    再来看预览区域,这个就十分简单了,用于预览应用图标的最终效果。在预览区域中给出了可能生成的图标形状,包括圆形、圆角矩形、方形等等。注意每个预览图标中都有一个圆圈,这个圆圈叫作安全区域,必须要保证图标的前景层完全处于安全区域当中才行,否则可能会出现图标被手机厂商的mask裁剪掉的情况。

    为了让大家能够更加直观地看到操作,这里我使用一张GIF图来演示操作的过程:

    最终,Android Studio会自动帮我们生成适配8.0系统的应用图标,以及适配老版本系统的应用图标,我们甚至一行代码都不用写,一切工作就已经完成了。感兴趣的朋友可以自己到mipmap目录下面去观察一下Android Studio帮我们生成了哪些东西,这里就不带着大家一一去看了。

    最后,让我们来运行一下程序,并且和正版爱奇艺的应用图标放在一起对比一下吧:

    可以看到,做过8.0系统应用图标适配之后,效果明显要好看太多了,也希望爱奇艺的官方APP也能早日完成适配吧。

    好了,今天这篇文章就到这里,相信大家都已经轻松掌握了Android 8.0系统的应用图标适配,下篇文章会讲解Android 8.0系统的通知栏适配,感兴趣的朋友请继续阅读 Android通知栏微技巧,8.0系统中通知栏的适配

    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

    20160507110203928         20161011100137978

    展开全文
  • Android应用程序组件Content Provider应用实例

    万次阅读 多人点赞 2011-11-21 00:58:44
    上文简要介绍了Android应用程序组件Content Provider在应用程序间共享数据的原理,但是没有进一步研究它的实现。本文将实现两个应用程序,其中一个以Content Provider的形式来提供数据访问入口,另一个通过这个...

            上文简要介绍了Android应用程序组件Content Provider在应用程序间共享数据的原理,但是没有进一步研究它的实现。本文将实现两个应用程序,其中一个以Content Provider的形式来提供数据访问入口,另一个通过这个Content Provider来访问这些数据。本文的例子不仅可以为下文分析Content Provider的实现原理准备好使用情景,还可以学习到它的一个未公开接口。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            本文中的应用程序是按照上一篇文章Android应用程序组件Content Provider简要介绍和学习计划中提到的一般应用程序架构方法来设计的。本文包含两个应用程序,其中,第一个应用程序命名为ArticlesProvider,它使用了SQLite数据库来维护一个文章信息列表,同时,它定义了访问这个文章信息列表的URI,这样,我们就可以通过一个Content Provider组件来向第三方应用程序提供访问这个文章信息列表的接口;第二个应用程序命名为Article,它提供了管理保存在ArticlesProvider应用程序中的文章信息的界面入口,在这个应用程序中,用户可以添加、删除和修改这些文章信息。接下来我们就分别介绍这两个应用程序的实现。

            1. ArticlesProvider应用程序的实现

             首先是参照在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文,在packages/experimental目录下建立工程文件目录ArticlesProvider。在继续介绍这个应用程序的实现之前,我们先介绍一下这个应用程序用来保存文章信息的数据库的设计。

             我们知道,在Android系统中,内置了一款轻型的数据库SQLite。SQLite是专门为嵌入式产品而设计的,它具有占用资源低的特点,而且是开源的,非常适合在Android平台中使用,关于SQLite的更多信息可以访问官方网站http://www.sqlite.org

             ArticlesProvider应用程序就是使用SQLite来作为数据库保存文章信息的,数据库文件命名为Articles.db,它里面只有一张表ArticlesTable,表的结构如下所示:

             -------------------------------------------------------------

             | -- _id -- | --  _title -- | -- _abstrat -- | -- _url -- |

             -------------------------------------------------------------

             |               |                    |                        |                  | 

            它由四个字段表示,第一个字段_id表示文章的ID,类型为自动递增的integer,它作为表的key值;第二个字段_title表示文章的题目,类型为text;第三个字段_abstract表示文章的摘要,类型为text;第四个字段_url表示文章的URL,类型为text。注意,当我们打算将数据库表的某一列的数据作为一个数据行的ID时,就约定它的列名为_id。这是因为我们经常需要从数据库中获取一批数据,这些数据以Cursor的形式返回,对这些返回来的数据我们一般用一个ListView来显示,而这个ListView需要一个数据适配器Adapter来作为数据源,这时候就我们就可以以这个Cursor来构造一个Adapter。有些Adapter,例如android.widget.CursorAdapter,它们在实现自己的getItemId成员函数来获取指定数据行的ID时,就必须要从这个Cursor中相应的行里面取出列名为_id的字段的内容出来作为这个数据行的ID返回给调用者。当然,我们不在数据库表中定义这个_id列名也是可以的,不过这样从数据库中查询数据后得到的Cursor适合性就变差了,因此,建议我们在设计数据库表时,尽量设置其中一个列名字_id,并且保证这一列的内容是在数据库表中是唯一的。

            下面我们就开始介绍这个应用程序的实现了。这个应用程序只有两个源文件,分别是Articles.java和ArticlesProvider,都是放在shy.luo.providers.articles这个package下面。在Articles.java文件里面,主要是定义了一些常量,例如用来访问文章信息数据的URI、MIME(Multipurpose Internet Mail Extensions)类型以及格式等,这些常量是第三方应用程序访问这些文章信息数据时要使用到的,因此,我们把它定义在一个单独的文件中,稍后我们会介绍如果把这个Articles.java文件打包成一个jar文件,然后第三方应用程序就可以引用这个常量了,这样也避免了直接把这个源代码文件暴露给第三方应用程序。

            源文件Articles.java位于src/shy/luo/providers/articles目录下,它的内容如下所示:

    package shy.luo.providers.articles;
    
    import android.net.Uri;
    
    public class Articles {
            /*Data Field*/
            public static final String ID = "_id";
            public static final String TITLE = "_title";
            public static final String ABSTRACT = "_abstract";
            public static final String URL = "_url";
    
            /*Default sort order*/
            public static final String DEFAULT_SORT_ORDER = "_id asc";
    
            /*Call Method*/
            public static final String METHOD_GET_ITEM_COUNT = "METHOD_GET_ITEM_COUNT";
            public static final String KEY_ITEM_COUNT = "KEY_ITEM_COUNT";
    
            /*Authority*/
            public static final String AUTHORITY = "shy.luo.providers.articles";
    
            /*Match Code*/
            public static final int ITEM = 1;
            public static final int ITEM_ID = 2;
            public static final int ITEM_POS = 3;
    
            /*MIME*/
            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.shy.luo.article";
            public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.shy.luo.article";
    
            /*Content URI*/
            public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item");
            public static final Uri CONTENT_POS_URI = Uri.parse("content://" + AUTHORITY + "/pos");
    }
            ID、TITLE、ABSTRACT和URL四个常量前面已经解释过了,它是我们用来保存文章信息的数据表的四个列名;DEFAULT_SORT_ORDER常量是调用ContentProvider接口的query函数来查询数据时用的,它表示对查询结果按照_id列的值从小到大排列;METHOD_GET_ITEM_COUNT和KEY_ITEM_COUNT两个常量是调用ContentProvider接口的一个未公开函数call来查询数据时用的,它类似于微软COM中的IDispatch接口的Invoke函数,使用这个call函数时,传入参数METHOD_GET_ITEM_COUNT表示我们要调用我们自定义的ContentProvider子类中的getItemCount函数来获取数据库中的文章信息条目的数量,结果放在一个Bundle中以KEY_ITEM_COUNT为关键字的域中。

            剩下的常量都是跟数据URI相关的,这个需要详细解释一下。URI的全称是Universal Resource Identifier,即通用资源标志符,通过它用来唯一标志某个资源在网络中的位置,它的结构和我们常见的HTTP形式URL是一样的,其实我们可以把常见的HTTP形式的URL看成是URI结构的一个实例,URI是在更高一个层次上的抽象。在Android系统中,它也定义了自己的用来定痊某个特定的Content Provider的URI结构,它通常由四个组件来组成,如下所示:

            [content://][shy.luo.providers.articles][/item][/123]

            |------A------|-----------------B-------------------|---C---|---D--|

            A组件称为Scheme,它固定为content://,表示它后面的路径所表示的资源是由Content Provider来提供的。

            B组件称为Authority,它唯一地标识了一个特定的Content Provider,因此,这部分内容一般使用Content Provider所在的package来命名,使得它是唯一的。

            C组件称为资源路径,它表示所请求的资源的类型,这部分内容是可选的。如果我们自己所实现的Content Provider只提供一种类型的资源访问,那么这部分内部就可以忽略;如果我们自己实现的Content Provider同时提供了多种类型的资源访问,那么这部分内容就不可以忽略了。例如,我们有两种电脑资源可以提供给用户访问,一种是笔记本电脑,一种是平板电脑,我们就把分别它们定义为notebook和pad;如果我们想进一步按照系统类型来进一步细分这两种电脑资源,对笔记本电脑来说,一种是安装了windows系统的,一种是安装了linux系统的,我们就分别把它们定义为notebook/windows和notebook/linux;对平板电脑来说,一种是安装了ios系统的,一种是安装了android系统的,我们就分别把它们定义为pad/ios和pad/android。

            D组件称为资源ID,它表示所请求的是一个特定的资源,它通常是一个数字,对应前面我们所介绍的数据库表中的_id字段的内容,它唯一地标志了某一种资源下的一个特定的实例。继续以前面的电脑资源为例,如果我们请求的是编号为123的装了android系统的平板电脑,我们就把它定义为pad/android/123。当忽略这部分内容时,它有可能是表示请求某一种资源下的所有实例,取决于我们的URI匹配规则,后面我们将会进一步解释如何设置URI匹配规则。

            回到上面的Articles.java源文件中,我们定义了两个URI,分别用COTENT_URI和CONTENT_POS_URI两个常量来表示,它们的Authority组件均指定为shy.luo.providers.articles。其中,COTENT_URI常量表示的URI表示是通过ID来访问文章信息的,而CONTENT_POS_URI常量表示的URI表示是通过位置来访问文章信息的。例如,content://shy.luo.providers.articles/item表示访问所有的文章信息条目;content://shy.luo.providers.articles/item/123表示只访问ID值为123的文章信息条目;content://shy.luo.providers.articles/pos/1表示访问数据库表中的第1条文章信息条目,这条文章信息条目的ID值不一定为1。通过常量CONTENT_POS_URI来访问文章信息条目时,必须要指定位置,这也是我们设置的URI匹配规则来指定的,后面我们将会看到。

            此外,我们还需要定义与URI对应的资源的MIME类型。每个MIME类型由两部分组成,前面是数据的大类别,后面定义具体的种类。在Content Provider中,URI所对应的资源的MIME类型的大类别根据同时访问的资源的数量分为两种,对于访问单个资源的URI,它的大类别就为vnd.android.cursor.item,而对于同时访问多个资源的URI,它的大类别就为vnd.android.cursor.dir。Content Provider的URI所对应的资源的MIME类型的具体类别就需要由Content Provider的提供者来设置了,它的格式一般为vnd.[company name].[resource type]的形式。例如,在我们的例子中,CONTENT_TYPE和COTENT_ITEM_TYPE两个常量分别定义了两种MIME类型,它们的大类别分别为vnd.android.cursor.dir和vnd.android.cursor.item,而具体类别均为vdn.shy.luo.article,其中shy.luo就是表示公司名了,而article表示资源的类型为文章。这两个MIME类型常量主要是在实现ContentProvider的getType函数时用到的,后面我们将会看到。

            最后,ITEM、ITEM_ID和POS_ID三个常量分别定了三个URI匹配规则的匹配码。如果URI的形式为content://shy.luo.providers.articles/item,则匹配规则返回的匹配码为ITEM;如果URI的形式为content://shy.luo.providers.articles/item/#,其中#表示任意一个数字,则匹配规则返回的匹配码为ITEM_ID;如果URI的形式为#也是表示任意一个数字,则匹配规则返回的匹配码为ITEM_POS。这三个常量的用法我们在后面也将会看到。

            这样,Articles.java文件的内容就介绍完了。下面我们再接着介绍位于src/shy/luo/providers/articles目录下的ArticlesProvider.java文件,它的内容如下所示:

    import java.util.HashMap;
    
    import android.content.ContentValues;
    import android.content.Context;
    import android.content.UriMatcher;
    import android.content.ContentProvider;
    import android.content.ContentUris;
    import android.content.ContentResolver;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteDatabase.CursorFactory;
    import android.database.sqlite.SQLiteException;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.database.sqlite.SQLiteQueryBuilder;
    import android.net.Uri;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.util.Log;
    
    public class ArticlesProvider extends ContentProvider {
            private static final String LOG_TAG = "shy.luo.providers.articles.ArticlesProvider";
    
            private static final String DB_NAME = "Articles.db";
            private static final String DB_TABLE = "ArticlesTable";
            private static final int DB_VERSION = 1;
    
            private static final String DB_CREATE = "create table " + DB_TABLE +
                                    " (" + Articles.ID + " integer primary key autoincrement, " +
                                    Articles.TITLE + " text not null, " +
                                    Articles.ABSTRACT + " text not null, " +
                                    Articles.URL + " text not null);";
    
            private static final UriMatcher uriMatcher;
            static {
                    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
                    uriMatcher.addURI(Articles.AUTHORITY, "item", Articles.ITEM);
                    uriMatcher.addURI(Articles.AUTHORITY, "item/#", Articles.ITEM_ID);
                    uriMatcher.addURI(Articles.AUTHORITY, "pos/#", Articles.ITEM_POS);
            }
    
            private static final HashMap<String, String> articleProjectionMap;
            static {
                    articleProjectionMap = new HashMap<String, String>();
                    articleProjectionMap.put(Articles.ID, Articles.ID);
                    articleProjectionMap.put(Articles.TITLE, Articles.TITLE);
                    articleProjectionMap.put(Articles.ABSTRACT, Articles.ABSTRACT);
                    articleProjectionMap.put(Articles.URL, Articles.URL);
            }
    
            private DBHelper dbHelper = null;
            private ContentResolver resolver = null;
    
            @Override
            public boolean onCreate() {
                    Context context = getContext();
                    resolver = context.getContentResolver();
                    dbHelper = new DBHelper(context, DB_NAME, null, DB_VERSION);
    
                    Log.i(LOG_TAG, "Articles Provider Create");
    
                    return true;
            }
    
            @Override
            public String getType(Uri uri) {
                    switch (uriMatcher.match(uri)) {
                    case Articles.ITEM:
                            return Articles.CONTENT_TYPE;
                    case Articles.ITEM_ID:
                    case Articles.ITEM_POS:
                            return Articles.CONTENT_ITEM_TYPE;
                    default:
                            throw new IllegalArgumentException("Error Uri: " + uri);
                    }
            }
    
            @Override
            public Uri insert(Uri uri, ContentValues values) {
                    if(uriMatcher.match(uri) != Articles.ITEM) {
                            throw new IllegalArgumentException("Error Uri: " + uri);
                    }
    
                    SQLiteDatabase db = dbHelper.getWritableDatabase();
    
                    long id = db.insert(DB_TABLE, Articles.ID, values);
                    if(id < 0) {
                            throw new SQLiteException("Unable to insert " + values + " for " + uri);
                    }
    
                    Uri newUri = ContentUris.withAppendedId(uri, id);
                    resolver.notifyChange(newUri, null);
    
                    return newUri;
            }
    
            @Override
            public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
                    SQLiteDatabase db = dbHelper.getWritableDatabase();
                    int count = 0;
    
                    switch(uriMatcher.match(uri)) {
                    case Articles.ITEM: {
                            count = db.update(DB_TABLE, values, selection, selectionArgs);
                            break;
                    }
                    case Articles.ITEM_ID: {
                            String id = uri.getPathSegments().get(1);
                            count = db.update(DB_TABLE, values, Articles.ID + "=" + id
                                            + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                            break;
                    }
                    default:
                            throw new IllegalArgumentException("Error Uri: " + uri);
                    }
    
                    resolver.notifyChange(uri, null);
    
                    return count;
            }
    
            @Override
            public int delete(Uri uri, String selection, String[] selectionArgs) {
                    SQLiteDatabase db = dbHelper.getWritableDatabase();
                    int count = 0;
    
                    switch(uriMatcher.match(uri)) {
                    case Articles.ITEM: {
                            count = db.delete(DB_TABLE, selection, selectionArgs);
                            break;
                    }
                    case Articles.ITEM_ID: {
                            String id = uri.getPathSegments().get(1);
                            count = db.delete(DB_TABLE, Articles.ID + "=" + id
                                            + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                            break;
                    }
                    default:
                            throw new IllegalArgumentException("Error Uri: " + uri);
                    }
    
                    resolver.notifyChange(uri, null);
    
                    return count;
            }
    
            @Override
            public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
                    Log.i(LOG_TAG, "ArticlesProvider.query: " + uri);
    
                    SQLiteDatabase db = dbHelper.getReadableDatabase();
    
                    SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
                    String limit = null;
    
                    switch (uriMatcher.match(uri)) {
                    case Articles.ITEM: {
                            sqlBuilder.setTables(DB_TABLE);
                            sqlBuilder.setProjectionMap(articleProjectionMap);
                            break;
                    }
                    case Articles.ITEM_ID: {
                            String id = uri.getPathSegments().get(1);
                            sqlBuilder.setTables(DB_TABLE);
                            sqlBuilder.setProjectionMap(articleProjectionMap);
                            sqlBuilder.appendWhere(Articles.ID + "=" + id);
                            break;
                    }
                    case Articles.ITEM_POS: {
                            String pos = uri.getPathSegments().get(1);
                            sqlBuilder.setTables(DB_TABLE);
                            sqlBuilder.setProjectionMap(articleProjectionMap);
                            limit = pos + ", 1";
                            break;
                    }
                    default:
                            throw new IllegalArgumentException("Error Uri: " + uri);
                    }
    
                    Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Articles.DEFAULT_SORT_ORDER : sortOrder, limit);
                    cursor.setNotificationUri(resolver, uri);
    
                    return cursor;
            }
      
            @Override
            public Bundle call(String method, String request, Bundle args) {
                    Log.i(LOG_TAG, "ArticlesProvider.call: " + method);
    
                    if(method.equals(Articles.METHOD_GET_ITEM_COUNT)) {
                            return getItemCount();
                    }
    
                    throw new IllegalArgumentException("Error method call: " + method);
            }
    
            private Bundle getItemCount() {
                    Log.i(LOG_TAG, "ArticlesProvider.getItemCount");
    
                    SQLiteDatabase db = dbHelper.getReadableDatabase();
                    Cursor cursor = db.rawQuery("select count(*) from " + DB_TABLE, null);
    
                    int count = 0;
                    if (cursor.moveToFirst()) {
                            count = cursor.getInt(0);
                    }
    
                    Bundle bundle = new Bundle();
                    bundle.putInt(Articles.KEY_ITEM_COUNT, count);
    
                    cursor.close();
                    db.close();
    
                    return bundle;
            }
    
            private static class DBHelper extends SQLiteOpenHelper {
                    public DBHelper(Context context, String name, CursorFactory factory, int version) {
                            super(context, name, factory, version);
                    }
    
                    @Override
                    public void onCreate(SQLiteDatabase db) {
                            db.execSQL(DB_CREATE);
                    }
    
                    @Override
                    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                            db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
                            onCreate(db);
                    }
            }
    }

            我们在实现自己的Content Provider时,必须继承于ContentProvider类,并且实现以下六个函数:

            -- onCreate(),用来执行一些初始化的工作。

            -- query(Uri, String[], String, String[], String),用来返回数据给调用者。

            -- insert(Uri, ContentValues),用来插入新的数据。

            -- update(Uri, ContentValues, String, String[]),用来更新已有的数据。

            -- delete(Uri, String, String[]),用来删除数据。

            -- getType(Uri),用来返回数据的MIME类型。

            这些函数的实现都比较简单,这里我们就不详细介绍了,主要解释五个要点。

            第一点是我们在ArticlesProvider类的内部中定义了一个DBHelper类,它继承于SQLiteOpenHelper类,它用是用辅助我们操作数据库的。使用这个DBHelper类来辅助操作数据库的好处是只有当我们第一次对数据库时行操作时,系统才会执行打开数据库文件的操作。拿我们这个例子来说,只有第三方应用程序第一次调用query、insert、update或者delete函数来操作数据库时,我们才会真正去打开相应的数据库文件。这样在onCreate函数里,就不用执行打开数据库的操作,因为这是一个耗时的操作,而在onCreate函数中,要避免执行这些耗时的操作。

            第二点是设置URI匹配规则。因为我们是根据URI来操作数据库的,不同的URI对应不同的操作,所以我们首先要定义好URI匹配规则,这样,当我们获得一个URI时,就能快速地判断出要如何去操作数据库。设置URI匹配规则的代码如下所示:

    private static final UriMatcher uriMatcher;
    static {
          uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
          uriMatcher.addURI(Articles.AUTHORITY, "item", Articles.ITEM);
          uriMatcher.addURI(Articles.AUTHORITY, "item/#", Articles.ITEM_ID);
          uriMatcher.addURI(Articles.AUTHORITY, "pos/#", Articles.ITEM_POS);
    }

            在创建UriMatcher对象uriMatcher时,我们传给构造函数的参数为UriMatcher.NO_MATCH,它表示当uriMatcher不能匹配指定的URI时,就返回代码UriMatcher.NO_MATCH。接下来增加了三个匹配规则,分别是content://shy.luo.providers.articles/item、content://shy.luo.providers.articles/item/#和content://shy.luo.providers.articles/pos/#,它们的匹配码分别是Articles.ITEM、Articles.ITEM_ID和Articles.ITEM_POS,其中,符号#表示匹配任何数字。

            第三点是SQLiteQueryBuilder的使用。在query函数中,我们使用SQLiteQueryBuilder来辅助数据库查询操作,使用这个类的好处是我们可以不把数据库表的字段暴露出来,而是提供别名给第三方应用程序使用,这样就可以把数据库表内部设计隐藏起来,方便后续扩展和维护。列别名到真实列名的映射是由下面这个HashMap成员变量来实现的:

     private static final HashMap<String, String> articleProjectionMap;
     static {
           articleProjectionMap = new HashMap<String, String>();
           articleProjectionMap.put(Articles.ID, Articles.ID);
           articleProjectionMap.put(Articles.TITLE, Articles.TITLE);
           articleProjectionMap.put(Articles.ABSTRACT, Articles.ABSTRACT);
           articleProjectionMap.put(Articles.URL, Articles.URL);
     }
           在上面的put函数中,第一个参数表示列的别名,第二个参数表示列的真实名称。在这个例子中,我们把列的别名和和真实名称都设置成一样的。

           第四点是数据更新机制的使用。执行insert、update和delete三个函数时,都会导致数据库中的数据发生变化,所以这时候要通过调用ContentResolver接口的notifyChange函数来通知那些注册了监控特定URI的ContentObserver对象,使得它们可以相应地执行一些处理,例如更新数据在界面上的显示。在query函数中,最终返回给调用者的是一个Cursor,调用者获得这个Cursor以后,可以通过它的deleteRow或者commitUpdates来执行一些更新数据库的操作,这时候也要通知那些注册了相应的URI的ContentObserver来作相应的处理,因此,这里在返回Cursor之前,要通过Cursor类的setNotificationUri函数来把当前上下文的ContentResolver对象保存到Curosr里面去,以便当通过这个Cursor来改变数据库中的数据时,可以通知相应的ContentObserver来处理。不过这种用法已经过时了,即不建议通过这个Cursor来改变数据库的数据,要把Cursor中的数据看作是只读数据。这里调用Cursor类的setNotificationUri函数还有另外一个作用,我们注意到它的第二个参数uri,对应的是Cursor中的内容,当把这个uri传给Cursor时,Cursor就会注册自己的ContentObserver来监控这个uri对应的数据的变化。一旦这个uri对应的数据发生变化,这个Cursor对应的数据就不是再新的了,这时候就需要采取一些操作来更新内容了。

             第五点我们实现了ContentProvider的call函数。这个函数是一个未公开的函数,第三方应用程序只有Android源代码环境下开发,才能使用这个函数。设计这个函数的目的是什么呢?我们知道,当我们需要从Content Provider中获得数据时,一般都是要通过调用它的query函数来获得的,而这个函数将数据放在Cursor中来返回给调用者。以前面一篇文章Android应用程序组件Content Provider简要介绍和学习计划中,我们提到,Content Provider传给第三方应用程序的数据,是通过匿名共享内存来传输的。当要传输的数据量大的时候,使用匿名共享内存来传输数据是有好处的,它可以减入数据的拷贝,提高传输效率。但是,当要传输的数据量小时,使用匿名共享内存来作为媒介就有点用牛刀来杀鸡的味道了,因为匿名共享内存并不是免费的午餐,系统创建和匿名共享内存也是有开销的。因此,Content Provider提供了call函数来让第三方应用程序来获取一些自定义数据,这些数据一般都比较小,例如,只是传输一个整数,这样就可以用较小的代价来达到相同的数据传输的目的。

            至此,ArticlesProvider的源代码就分析完了,下面我们还要在AndroidManifest.xml文件中配置这个ArticlesProvider类才能正常使用。AndroidManifest.xml文件的内容如下所示:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="shy.luo.providers.articles">
        <application android:process="shy.luo.process.article"
            android:label="@string/app_label"
            android:icon="@drawable/app_icon">
            <provider android:name="ArticlesProvider"
                android:authorities="shy.luo.providers.articles"
                android:label="@string/provider_label"
                android:multiprocess="false">
            </provider>
        </application>
    </manifest>
            在配置Content Provider的时候,最重要的就是要指定它的authorities属性了,只有配置了这个属性,第三方应用程序才能通过它来找到这个Content Provider。这要需要注意的,这里配置的authorities属性的值是和我们前面在Articles.java文件中定义的AUTHORITY常量的值是一致的。另外一个属性multiprocess是一个布尔值,它表示这个Content Provider是否可以在每个客户进程中创建一个实例,这样做的目的是为了减少进程间通信的开销。这里我们为了减少不必要的内存开销,把属性multiprocess的值设置为false,使得系统只能有一个Content Provider实例存在,它运行在自己的进程中。在这个配置文件里面,我们还可以设置这个Content Provider的访问权限,这里我们为了简单起见,就不设置权限了。有关Content Provider的访问权限的设置,可以参考官方文档http://developer.android.com/guide/topics/manifest/provider-element.html

            这个应用程序使用到的字符串资源定义在res/values/strings.xml文件中,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_label">Articles Storage</string>
        <string name="provider_label">Articles</string>
    </resources>
            由于Content Provider类型的应用程序是没有用户界面的,因此,我们不需要在res/layout目录下为程序准备界面配置文件。

            程序的编译脚本Android.mk的内容如下所示:

    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_MODULE_TAGS := optional
    
    LOCAL_SRC_FILES := $(call all-subdir-java-files)
    
    LOCAL_PACKAGE_NAME := ArticlesProvider
    
    include $(BUILD_PACKAGE)
            下面我们就可以参照如何单独编译Android源代码中的模块一文来编译和打包这个应用程序了:

    USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/ArticlesProvider    
    USER-NAME@MACHINE-NAME:~/Android$ make snod 

            这样,打包好的Android系统镜像文件system.img就包含我们这里所创建的ArticlesProvider应用程序了。

            前面说过,在Articles.java文件中定义的常量是要给第三方应用程序使用的,那么我们是不是直接把这个源文件交给第三方呢?这样就显得太不专业了,第三方拿到这个文件后,还必须要放在shy/luo/providers/articles目录下或者要把这个Articles类所在的package改掉才能正常使用。正确的做法是把编译好的Articles.java文件打包成一个jar文件交给第三方使用。编译ArticlesProvider这个应用程序成功后,生成的中间文件放在out/target/common/obj/APPS/ArticlesProvider_intermediates目录下,我们进入到这个目录中,然后执后下面的命令把Articles.class文件提取出来:

    USER-NAME@MACHINE-NAME:~/Android/out/target/common/obj/APPS/ArticlesProvider_intermediates$ jar -xvf classes.jar shy/luo/providers/articles/Articles.class

            然后再单独打包这个Articles.class文件:

    USER-NAME@MACHINE-NAME:~/Android/out/target/common/obj/APPS/ArticlesProvider_intermediates$ jar -cvf ArticlesProvider.jar ./shy
            这样,我们得到的ArticlesProvider.jar文件就包含了Articles.java这个文件中定义的常量了,第三方拿到这个文件后,就可以开发自己的应用程序来访问我们在ArticlesProvider这个Content Provider中保存的数据了。接下来我们就介绍调用这个ArticlesProvider来获取数据的第三方应用程序Article。

            2. Article应用程序的实现

            首先是参照前面的ArticlesProvider工程,在packages/experimental目录下建立工程文件目录Article。这个应用程序的作用是用来管理ArticlesProvider应用程序中保存的文章信息的,因此,它需要获得相应的Content Provider接口来访问ArticlesProvider中的数据。我们首先在工程目录Article下面创建一个libs目录,把上面得到的ArticlesProvider.jar放在libs目录下,后面我们在编译脚本的时候,再把它引用到工程上来。下面我们就开始分析这个应用程序的实现。

            这个应用程序的主界面MainActivity包含了一个ListView控件,用来显示从ArticlesProvider中得到的文章信息条目,在这个主界面上,可以浏览、增加、删除和更新文章信息。当需要增加、删除或者更新文章信息时,就会跳到另外一个界面ArticleActivity中去执行具体的操作。为了方便开发,我们把每一个文章信息条目封装成了一个Article类,并且把与ArticlesProvider进交互的操作都通过ArticlesAdapter类来实现。下面介绍每一个类的具本实现。

            下面是Article类的实现,它实现在src/shy/luo/Article.java文件中:

    package shy.luo.article;
    
    public class Article {
            private int id;
            private String title;
            private String abs;
            private String url;
    
            public Article(int id, String title, String abs, String url) {
                    this.id = id;
                    this.title = title;
                    this.abs = abs;
                    this.url = url;
            }
    
            public void setId(int id) {
                    this.id = id;
            }
    
            public int getId() {
                    return this.id;
            }
    
            public void setTitle(String title) {
                    this.title = title;
            }
    
            public String getTitle() {
                    return this.title;
            }
    
            public void setAbstract(String abs) {
                    this.abs = abs;
            }
    
            public String getAbstract() {
                    return this.abs;
            }
    
            public void setUrl(String url) {
                    this.url = url;
            }
    
            public String getUrl() {
                    return this.url;
            }
    }
           下面是ArticlesAdapter类的实现,它实现在src/shy/luo/ArticlesAdapter.java文件中:

    package shy.luo.article;
    
    import java.util.LinkedList;
    
    import shy.luo.providers.articles.Articles;
    import android.content.ContentResolver;
    import android.content.ContentUris;
    import android.content.ContentValues;
    import android.content.Context;
    import android.content.IContentProvider;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.RemoteException;
    import android.util.Log;
    
    
    public class ArticlesAdapter {
            private static final String LOG_TAG = "shy.luo.article.ArticlesAdapter";
    
            private ContentResolver resolver = null;
    
            public ArticlesAdapter(Context context) {
                    resolver = context.getContentResolver();
            }
    
            public long insertArticle(Article article) {
                    ContentValues values = new ContentValues();
                    values.put(Articles.TITLE, article.getTitle());
                    values.put(Articles.ABSTRACT, article.getAbstract());
                    values.put(Articles.URL, article.getUrl());
    
                    Uri uri = resolver.insert(Articles.CONTENT_URI, values);
                    String itemId = uri.getPathSegments().get(1);
    
                    return Integer.valueOf(itemId).longValue();
            }
    
            public boolean updateArticle(Article article) {
                    Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, article.getId());
    
                    ContentValues values = new ContentValues();
                    values.put(Articles.TITLE, article.getTitle());
                    values.put(Articles.ABSTRACT, article.getAbstract());
                    values.put(Articles.URL, article.getUrl());
    
                    int count = resolver.update(uri, values, null, null);
    
                    return count > 0;
            }
    
            public boolean removeArticle(int id) {
                    Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, id);
    
                    int count = resolver.delete(uri, null, null);
    
                    return count > 0;
            }
    
            public LinkedList<Article> getAllArticles() {
                    LinkedList<Article> articles = new LinkedList<Article>();
    
                    String[] projection = new String[] {
                            Articles.ID,
                            Articles.TITLE,
                            Articles.ABSTRACT,
                            Articles.URL
                    };
    
                    Cursor cursor = resolver.query(Articles.CONTENT_URI, projection, null, null, Articles.DEFAULT_SORT_ORDER);
                    if (cursor.moveToFirst()) {
                            do {
                                    int id = cursor.getInt(0);
                                    String title = cursor.getString(1);
                                    String abs = cursor.getString(2);
                                    String url = cursor.getString(3);
    
                                    Article article = new Article(id, title, abs, url);
                                    articles.add(article);
                            } while(cursor.moveToNext());
                    }
    
                    return articles;
            }
    
            public int getArticleCount() {
                    int count = 0;
    
                    try {
                            IContentProvider provider = resolver.acquireProvider(Articles.CONTENT_URI);
                            Bundle bundle = provider.call(Articles.METHOD_GET_ITEM_COUNT, null, null);
                            count = bundle.getInt(Articles.KEY_ITEM_COUNT, 0);
                    } catch(RemoteException e) {
                            e.printStackTrace();
                    }
    
                    return count;
            }
    
            public Article getArticleById(int id) {
                    Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, id);
    
                    String[] projection = new String[] {
                                    Articles.ID,
                        Articles.TITLE,
                        Articles.ABSTRACT,
                        Articles.URL
                    };
    
                    Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);
    
                    Log.i(LOG_TAG, "cursor.moveToFirst");
    
                    if (!cursor.moveToFirst()) {
                            return null;
                    }
    
                    String title = cursor.getString(1);
                    String abs = cursor.getString(2);
                    String url = cursor.getString(3);
    
                    return new Article(id, title, abs, url);
            }
    
            public Article getArticleByPos(int pos) {
                    Uri uri = ContentUris.withAppendedId(Articles.CONTENT_POS_URI, pos);
    
                    String[] projection = new String[] {
                                    Articles.ID,
                        Articles.TITLE,
                        Articles.ABSTRACT,
                        Articles.URL
                    };
    
                    Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);
                    if (!cursor.moveToFirst()) {
                            return null;
                    }
    
                    int id = cursor.getInt(0);
                    String title = cursor.getString(1);
                    String abs = cursor.getString(2);
                    String url = cursor.getString(3);
    
                    return new Article(id, title, abs, url);
            }
    }
             这个类首先在构造函数里面获得应用程序上下文的ContentResolver接口,然后通过就可以通过这个接口来访问ArticlesProvider中的文章信息了。成员函数insertArticle、updateArticle和removeArticle分别用来新增、更新和删除一个文章信息条目;成员函数getAllArticlese用来获取所有的文章信息;成员函数getArticleById和getArticleByPos分别根据文章的ID和位置来获得具体文章信息条目;成员函数getArticleCount直接使用ContentProvider的未公开接口call来获得文章信息条目的数量,注意,这个函数要源代码环境下编译才能通过。

            下面是程序主界面MainActivity类的实现,它实现在src/shy/luo/article/MainActivity.java文件中:

    package shy.luo.article;
    
    import shy.luo.providers.articles.Articles;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.database.ContentObserver;
    import android.os.Bundle;
    import android.os.Handler;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.BaseAdapter;
    import android.widget.Button;
    import android.widget.ListView;
    import android.widget.TextView;
    
    public class MainActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener {
            private final static String LOG_TAG = "shy.luo.article.MainActivity";
    
            private final static int ADD_ARTICAL_ACTIVITY = 1;
            private final static int EDIT_ARTICAL_ACTIVITY = 2;
    
            private ArticlesAdapter aa = null;
            private ArticleAdapter adapter = null;
            private ArticleObserver observer = null;
    
            private ListView articleList = null;
            private Button addButton = null;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.main);
    
                    aa = new ArticlesAdapter(this);
    
                    articleList = (ListView)findViewById(R.id.listview_article);
                    adapter = new ArticleAdapter(this);
                    articleList.setAdapter(adapter);
                    articleList.setOnItemClickListener(this);
    
                    observer = new ArticleObserver(new Handler());
                    getContentResolver().registerContentObserver(Articles.CONTENT_URI, true, observer);
    
                    addButton = (Button)findViewById(R.id.button_add);
                    addButton.setOnClickListener(this);
    
                    Log.i(LOG_TAG, "MainActivity Created");
            }
    
            @Override
            public void onDestroy() {
                    super.onDestroy();
                    getContentResolver().unregisterContentObserver(observer);
            }
    
            @Override
            public void onClick(View v) {
                    if(v.equals(addButton)) {
                            Intent intent = new Intent(this, ArticleActivity.class);
                            startActivityForResult(intent, ADD_ARTICAL_ACTIVITY);
                    }
            }
    
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
                    Intent intent = new Intent(this, ArticleActivity.class);
    
                    Article article = aa.getArticleByPos(pos);
                    intent.putExtra(Articles.ID, article.getId());
                    intent.putExtra(Articles.TITLE, article.getTitle());
                    intent.putExtra(Articles.ABSTRACT, article.getAbstract());
                    intent.putExtra(Articles.URL, article.getUrl());
    
                    startActivityForResult(intent, EDIT_ARTICAL_ACTIVITY);
            }
    
            @Override
            public void onActivityResult(int requestCode,int resultCode, Intent data) {
                    super.onActivityResult(requestCode, resultCode, data);
    
                    switch(requestCode) {
                    case ADD_ARTICAL_ACTIVITY: {
                            if(resultCode == Activity.RESULT_OK) {
                                    String title = data.getStringExtra(Articles.TITLE);
                                    String abs = data.getStringExtra(Articles.ABSTRACT);
                                    String url = data.getStringExtra(Articles.URL);
    
                                    Article article = new Article(-1, title, abs, url);
                                    aa.insertArticle(article);
                            }
    
                            break;
                    }
    
                    case EDIT_ARTICAL_ACTIVITY: {
                            if(resultCode == Activity.RESULT_OK) {
                                    int action = data.getIntExtra(ArticleActivity.EDIT_ARTICLE_ACTION, -1);
                                    if(action == ArticleActivity.MODIFY_ARTICLE) {
                                            int id = data.getIntExtra(Articles.ID, -1);
                                            String title = data.getStringExtra(Articles.TITLE);
                                            String abs = data.getStringExtra(Articles.ABSTRACT);
                                            String url = data.getStringExtra(Articles.URL);
    
                                            Article article = new Article(id, title, abs, url);
                                            aa.updateArticle(article);
                                    } else if(action == ArticleActivity.DELETE_ARTICLE)     {
                                            int id = data.getIntExtra(Articles.ID, -1);
    
                                            aa.removeArticle(id);
                                    }
    
                            }
    
                            break;
                    }
                    }
            }
    
            private class ArticleObserver extends ContentObserver {
                    public ArticleObserver(Handler handler) {
                            super(handler);
                    }
    
                    @Override
                    public void onChange (boolean selfChange) {
                            adapter.notifyDataSetChanged();
                    }
            }
    
            private class ArticleAdapter extends BaseAdapter {
                    private LayoutInflater inflater;
    
                    public ArticleAdapter(Context context){
                              inflater = LayoutInflater.from(context);
                    }
    
                    @Override
                    public int getCount() {
                            return aa.getArticleCount();
                    }
    
                    @Override
                    public Object getItem(int pos) {
                            return aa.getArticleByPos(pos);
                    }
    
                    @Override
                    public long getItemId(int pos) {
                            return aa.getArticleByPos(pos).getId();
                    }
    
                    @Override
                    public View getView(int position, View convertView, ViewGroup parent) {
                            Article article = (Article)getItem(position);
    
                            if (convertView == null) {
                                    convertView = inflater.inflate(R.layout.item, null);
                            }
    
                            TextView titleView = (TextView)convertView.findViewById(R.id.textview_article_title);
                            titleView.setText("Title: " + article.getTitle());
    
                            TextView abstractView = (TextView)convertView.findViewById(R.id.textview_article_abstract);
                            abstractView.setText("Abstract: " + article.getAbstract());
    
                            TextView urlView = (TextView)convertView.findViewById(R.id.textview_article_url);
                            urlView.setText("URL: " + article.getUrl());
    
                            return convertView;
                    }
            }
    }

            在应用程序的主界面中,我们使用一个ListView来显示文章信息条目,这个ListView的数据源由ArticleAdapter类来提供,而ArticleAdapter类又是通过ArticlesAdapter类来获得ArticlesProvider中的文章信息的。在MainActivity的onCreate函数,我们还通过应用程序上下文的ContentResolver接口来注册了一个ArticleObserver对象来监控ArticlesProvider中的文章信息。一旦ArticlesProvider中的文章信息发生变化,就会通过ArticleAdapter类来实时更新ListView中的文章信息。

            下面是ArticleActivity类的实现,它实现在src/shy/luo/article/ArticleActivity.java文件中:

    package shy.luo.article;
    
    import shy.luo.providers.articles.Articles;
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    
    public class ArticleActivity extends Activity implements View.OnClickListener {
            private final static String LOG_TAG = "shy.luo.article.ArticleActivity";
    
            public final static String EDIT_ARTICLE_ACTION = "EDIT_ARTICLE_ACTION";
            public final static int MODIFY_ARTICLE = 1;
            public final static int DELETE_ARTICLE = 2;
    
            private int articleId = -1;
    
            private EditText titleEdit = null;
            private EditText abstractEdit = null;
            private EditText urlEdit = null;
    
            private Button addButton = null;
            private Button modifyButton = null;
            private Button deleteButton = null;
            private Button cancelButton = null;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.article);
    
                    titleEdit = (EditText)findViewById(R.id.edit_article_title);
                    abstractEdit = (EditText)findViewById(R.id.edit_article_abstract);
                    urlEdit = (EditText)findViewById(R.id.edit_article_url);
    
                    addButton = (Button)findViewById(R.id.button_add_article);
                    addButton.setOnClickListener(this);
    
                    modifyButton = (Button)findViewById(R.id.button_modify);
                    modifyButton.setOnClickListener(this);
    
                    deleteButton = (Button)findViewById(R.id.button_delete);
                    deleteButton.setOnClickListener(this);
    
                    cancelButton = (Button)findViewById(R.id.button_cancel);
                    cancelButton.setOnClickListener(this);
    
                    Intent intent = getIntent();
                    articleId = intent.getIntExtra(Articles.ID, -1);
    
                    if(articleId != -1) {
                            String title = intent.getStringExtra(Articles.TITLE);
                            titleEdit.setText(title);
    
                            String abs = intent.getStringExtra(Articles.ABSTRACT);
                            abstractEdit.setText(abs);
    
                            String url = intent.getStringExtra(Articles.URL);
                            urlEdit.setText(url);
    
                            addButton.setVisibility(View.GONE);
                    } else {
    
                            modifyButton.setVisibility(View.GONE);
                            deleteButton.setVisibility(View.GONE);
                    }
    
                    Log.i(LOG_TAG, "ArticleActivity Created");
            }
    
            @Override
            public void onClick(View v) {
                    if(v.equals(addButton)) {
                            String title = titleEdit.getText().toString();
                            String abs = abstractEdit.getText().toString();
                            String url = urlEdit.getText().toString();
    
                            Intent result = new Intent();
                            result.putExtra(Articles.TITLE, title);
                            result.putExtra(Articles.ABSTRACT, abs);
                            result.putExtra(Articles.URL, url);
    
                            setResult(Activity.RESULT_OK, result);
                            finish();
                    } else if(v.equals(modifyButton)){
                            String title = titleEdit.getText().toString();
                            String abs = abstractEdit.getText().toString();
                            String url = urlEdit.getText().toString();
    
                            Intent result = new Intent();
                            result.putExtra(Articles.ID, articleId);
                            result.putExtra(Articles.TITLE, title);
                            result.putExtra(Articles.ABSTRACT, abs);
                            result.putExtra(Articles.URL, url);
                            result.putExtra(EDIT_ARTICLE_ACTION, MODIFY_ARTICLE);
    
                            setResult(Activity.RESULT_OK, result);
                            finish();
                    } else if(v.equals(deleteButton)) {
                            Intent result = new Intent();
                            result.putExtra(Articles.ID, articleId);
                            result.putExtra(EDIT_ARTICLE_ACTION, DELETE_ARTICLE);
    
                            setResult(Activity.RESULT_OK, result);
                            finish();
                    } else if(v.equals(cancelButton)) {
                            setResult(Activity.RESULT_CANCELED, null);
                            finish();
    
                    }
            }
    }
             在ArticleActivity窗口中,我们可以执行新增、更新和删除文章信息的操作。如果启动ArticleActivity时,没有把文章ID传进来,就说明要执行操作是新增文章信息;如果启动ArticleActivity时,把文章ID和其它信自都传进来了,就说明要执行的操作是更新或者删除文章,根据用户在界面点击的是更新按钮还是删除按钮来确定。

             程序使用到的界面文件定义在res/layout目录下,其中,main.xml文件定义MainActivity的界面,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:gravity="bottom">
            <ListView
                    android:id="@+id/listview_article"
                    android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1" 
                    android:background="@drawable/border"
                    android:choiceMode="singleChoice">
            </ListView>
            <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:gravity="center"
            android:layout_marginTop="10dp">
            <Button 
                    android:id="@+id/button_add"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingLeft="15dp"
                    android:paddingRight="15dp"
                    android:text="@string/add">
            </Button>
        </LinearLayout>
    </LinearLayout>
             item.xml文件定义了ListView中每一个文章信息条目的显示界面,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">
            <TextView
                    android:id="@+id/textview_article_title"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content">
            </TextView>
            <TextView
                    android:id="@+id/textview_article_abstract"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content">
            </TextView>
        <TextView
                    android:id="@+id/textview_article_url"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="10dp">
            </TextView>
    </LinearLayout>
             article.xml文件定义了ArticleActivity的界面,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        android:gravity="center">
        <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content" 
            android:layout_width="fill_parent">
            <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" 
                    android:layout_marginRight="24dp"
                    android:text="@string/title">
            </TextView>
            <EditText 
                    android:id="@+id/edit_article_title"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content">
            </EditText>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content" 
            android:layout_width="fill_parent">
            <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" 
                    android:text="@string/abs">
            </TextView>
            <EditText 
                    android:id="@+id/edit_article_abstract"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" >
            </EditText>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content" 
            android:layout_width="fill_parent">
            <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" 
                    android:layout_marginRight="27dp"
                    android:text="@string/url">
            </TextView>
            <EditText 
                    android:id="@+id/edit_article_url"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" >
            </EditText>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_height="wrap_content" 
            android:layout_width="match_parent" 
            android:gravity="center"
            android:layout_marginTop="10dp">
            <Button 
                    android:id="@+id/button_modify"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/modify">
            </Button>
            <Button 
                    android:id="@+id/button_delete"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/delete">
            </Button>
            <Button 
                    android:id="@+id/button_add_article"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:paddingLeft="16dp"
                    android:paddingRight="16dp"
                    android:text="@string/add">
            </Button>
            <Button 
                    android:id="@+id/button_cancel"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/cancel">
            </Button>
        </LinearLayout>
    </LinearLayout>
            在res/drawable目录下,有一个border.xml文件定义了MainActivity界面上的ListView的背景,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="rectangle">
            <solid android:color="#ff0000ff"/>
            <stroke android:width="1dp" 
                    android:color="#000000">
            </stroke>
            <padding android:left="7dp"  
                    android:top="7dp"
                    android:right="7dp"  
                    android:bottom="7dp">
            </padding>
            <corners android:radius="10dp" />
    </shape>
            程序使用到的字符串资源文件定义在res/values/strings.xml文件中,它的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">Article</string>
        <string name="article">Article</string>
        <string name="add">Add</string>
        <string name="modify">Modify</string>
        <string name="delete">Delete</string>
        <string name="title">Title:</string>
        <string name="abs">Abstract:</string>
        <string name="url">URL:</string>
        <string name="ok">OK</string>
        <string name="cancel">Cancel</string>
    </resources>
            接下来再来看程序的配置文件AndroidManifest.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="shy.luo.article"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".MainActivity"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity 
                    android:name=".ArticleActivity"
                    android:label="@string/article">
            </activity>
        </application>
    </manifest>
            编译脚本Android.mk的内容如下所示:

    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_MODULE_TAGS := optional
    
    LOCAL_STATIC_JAVA_LIBRARIES := libArticlesProvider
    
    LOCAL_SRC_FILES := $(call all-subdir-java-files)
    
    LOCAL_PACKAGE_NAME := Article
    
    include $(BUILD_PACKAGE)
    ###################################################
    include $(CLEAR_VARS)
    
    LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := libArticlesProvider:./libs/ArticlesProvider.jar
    
    include $(BUILD_MULTI_PREBUILT)
            这个编译脚本包含了两个部分的指令,一个是把libs目录下的预编译静态库ArticlesProvider.jar编译成一本地静态库libArticlesProvider,它的相关库文件保存在out/target/common/obj/JAVA_LIBRARIES/libArticlesProvider_intermediates目录下;另一个就是编译我们的程序Article了,它通过LOCAL_STATIC_JAVA_LIBRARIES变量来引用前面的libArticlesProvider库,这个库包含了所有我们用来访问ArticlesProvider这个Content Provider中的数据的常量。

            下面我们就可以编译和打包这个应用程序了:

    USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Article    
    USER-NAME@MACHINE-NAME:~/Android$ make snod 

            这样,打包好的Android系统镜像文件system.img就包含我们这里所创建的Article应用程序了。

            最后,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
            执行以下命令启动模拟器:

    USER-NAME@MACHINE-NAME:~/Android$ emulator  
            这个应用程序的主界面如下图所示:

             点击下面的Add按钮,可以添加新的文章信息条目:


             在前一个界面的文件列表中,点击某一个文章条目,便可以更新或者删除文章信息条目:


     
            这样,Content Provider的使用实例就介绍完了。这篇文章的目的是使读者对Content Provider有一个大概的了解和感性的认识,在下一篇文章中,我们将详细介绍Article应用程序是如何获得ArticlesProvider这个ContentProvider接口的,只有获得了这个接口之后,Article应用程序才能访问ArticlesProvider的数据,敬请关注。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    展开全文
  • 最近开始学习Android应用开发,和我的底层结合起来,为了工作,咬着牙也要学下去!!! 首先,我使用的是Android Studio这个软件。 上谷歌中国网就可以下载到了,地址如下: ...
  • Android应用的默认Activity配置

    万次阅读 2017-01-24 12:00:13
    Android应用的默认Activity 对一个Android应用来说,一般都会将某个Activity配置为默认启动的Activity。默认Activity作为应用的入口,会在桌面中显示一个图标和名字。这里稍作说明一下,Android原生系统采用二级...
  • 本文是go语言android应用开发实战中的第一篇,主要介绍gomobile环境搭建,下一篇会介绍如何编译出对应的动态库相关内容
  • Android 应用广告接入- 穿山甲

    千次阅读 2019-09-03 20:33:14
    Android 应用广告接入- 穿山甲 参考 SDK接入流程 头条网盟Android SDK——接入说明文档(记得申请账号登录)
  • Android应用双开实现

    万次阅读 2017-10-23 16:44:20
    Android应用双开是某些特殊人群的需要,目前已知的双开方案个人总结为3种: 1.反编译apk,然后修改uid等相关信息让系统弄认为apk有不同,然后重新安装。这个方法是简单粗暴的,不过目前主流的app估计都会预防这种...
  • android应用安装包大小优化

    千次阅读 2017-02-18 13:32:26
    最近在做android应用性能优化,查阅了网上很多资料,为了加深理解以及便于后面查阅,因此在博客中记录下来。  应用性能优化将会有一系列文章,本篇主要说下android应用安装包大小的优化。  首先来了解下android...
  • Android应用网络限制功能实现

    万次阅读 2017-09-19 20:39:52
    在之前的博文Android防火墙功能实现与原理分析中分析了Android防火墙实现原理,这里继续讲解Android 应用网络限制实现流程。 先看看需求:现在4G流量费用比较昂贵,为了避免流量的消耗,我们可以限制某个应用连接...
  • Android应用案例开发大全 吴亚峰等 PDF扫描版

    千次下载 热门讨论 2014-09-26 12:28:12
    Android应用案例开发大全》是以Android手机综合应用程序开发为主题 通过11个典型范例全面且深度地讲解了单机应用 网络应用 商业案例 2D和3D游戏等多个开发领域 全书共分12章 主要以范例集的方式来讲述Android的...
  • Android应用优化方案三

    万次阅读 2016-12-22 14:40:23
    Android应用优化方案一  点击进入  Android应用优化方案二  点击进入  Android应用优化方案三   布局优化 布局优化的思想很简单,尽量减少布局的层级,布局层级减少绘制时间就会跟着减少,...
  • SurfaceFlinger服务负责绘制Android应用程序的UI,它的实现相当复杂,要从正面分析它的实现不是一件容易的事。既然不能从正面分析,我们就想办法从侧面分析。说到底,无论SurfaceFlinger服务有多复杂,它都是为...
  • 我们很多人对Android理解只限于Android应用对andriod系统很少去了解,所以对Android应用的学习每个人都有不同的看法吧!对我个人而言想入门Android开发这行业必须先要学会Java,不要求要对Java有多深的了解,只要对...
  • 第五章 Android 应用层安全 来源:Yury Zhauniarovich | Publications 译者:飞龙 协议:CC BY-NC-SA 4.0 虽然在这一节中我们描述了应用层的安全性,但是实际的安全实施通常出现在到目前为止描述的底层。 ...
  • 前言 为了方便大家的阅读以及自己的知识体系的建立,特意来写出这个引导文章。以前我是遇到什么写什么,想到什么写什么,从2016年开始我将围绕这个知识体系来写...1.Android应用层 Android网络编程(完结)

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 270,348
精华内容 108,139
关键字:

android应用