美文网首页
11.《android编程权威指南》笔记一

11.《android编程权威指南》笔记一

作者: 为梦想战斗 | 来源:发表于2017-10-15 19:24 被阅读0次

    一、Android开发初体验

      监听器使用匿名内部类的好处:1,因为匿名内部类的使用,我们可在同一处实现监听器方法,代码更清晰可读。2,事件监听器一般只在同一处使用,使用匿名内部类可避免不必要的命名类实现。
    

    二、Android与MVC设计模式
    模型对象存储着应用的数据和业务逻辑。模型类通常用来映射与应用相关的一些事物,如用户、商店里的商品、服务器上的图片或者一些段电视节目,抑或GeoQuiz应用里的地理知识问题。模型对象不关心用户界面,它存在的唯一上的就是存储和管理应用数据。
    视图对象知道如何在屏幕上绘制自己,以及如何响应用户的输入,如触摸动作等。一个简单经验法则是,凡是能够在屏幕上看见的对象,就是视图对象。比如xml文件。
    控制器对象含有应用的逻辑单元,是视图与模型对象的联系纽带。控制器对象响应视图对象触发的种类事件,此外还管理着模型对象与视图间的数据流动。一般是Activity、Fragment或Service的一个子类。
    int question = mQuestonBank[mCurrentIndex].getTextResId();
    mQuestionTextView.setText(question); //setText(@StringRes int resid)的另一个重构方法
    公共代码抽取方法:refactor->extract->method。
    Button: android:drawableRight="@drawable...
    TextView点击事件
    ImageButton:android:src

    三、Activity的生命周期
    onCreate(内存),onStart(可视),onResume(前台),onPause(可视),onStop(内存),onDestroy,onRestart
    Log快捷键:logt、logd、logm
    LogCat过滤设置:选择Edit Filter Configuration选项,单击+按钮创建消息过滤器,在Filter Name处输入QuizActivity,Log Tag处同样输入QuizActivity,单击OK。现在,LogCat窗口仅显示Tag为QuizActivity的日志信息。
    设备处于水平方向时,Android会找到并使用res/layout-land目录下的布局资源。
    FrameLayout里面控件使用android:layout_gravity属性。
    设备旋转前保存数据:onSaveInstanceState / if(savedInstanceState != null)...

    四、Android应用的调试
    诊断应用异常(配合逻辑诊断)
    记录栈跟踪日志(查看方法在哪被调用):Log.d(TAG,"Updating question text ",new Exception());
    设置断点(Debug而不是Run),运行后可检查对象,变量的值,单击this可看到超类的值。
    使用异常断点(调试时会定位到异常抛出的代码行):Run --> View Breakpoints,单击+,选择Java Exception Breakpoints,输入RuntimeException并选择。异常断点影响大建议及时清除不需要的断点。
    使用Android Lint(会检查项目中所有潜在问题):Analyze --> Inspect Code...,选择Whole project。
    R类的问题(资源编译错误):重新检查资源文件中XML文件的有效性、清理项目、使用Gradle同步项目、运行Android Lint
    布局检查器(查看布局树):androd monitor --> hierarchy View
    内存分配跟踪:点击按钮启动后,在前台操作应用时,后台就开始记录内存分配状况,寻找可优化的点。

    五、第二个Activity
    tools和tools:text命名空间可以覆盖某个组件的任何属性。
    activity调用startActivity()方法时,调用请求发送给了操作系统的ActivityManager。ActivityManager负责创建Activity实例并调用其onCreate()方法。
    //CheatActivity
    private static final String EXTRA_ANSWER_IS_TRUE = "com.bignerdranch.android.geoquiz.answer_is_true";

    public static Intent newIntent(Context packageContext,boolean answerIsTrue) {
        Intent intent = new Intent(packageContext,CheatActivity.class);
        intent.putExtra(EXTRA_ANSWER_IS_TRUE,answerIsTrue);
        return intent;
    }
    //QuizActivity
    Intent intent = CheatActivity.newIntent(QuizActivity.this,answerIsTrue);
                startActivity(intent);
    //CheatActivity
        mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE,false);
    

    启动应用时,实际上是启动了应用的lanunch activity。
    ActivityManager维护着一个非特定应用独享的回退栈。所有应用的activity都共享该回退栈。这也是将ActivityManager设计成操作系统级的activity管理器来负责启动应用activity的原因之一。显然,回退栈是作为一个整体共享于操作系统及设备,而不单单用于某个应用。

    六、Android SDK版本与兼容
    使用Android Lint,在老版本的系统上调用新版本代码时,潜在问题在编译时就能被发现。也就是说,如果使用了高版本系统API中的代码,Android Lint会提示编译错误。
    根据系统版本编译:Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLTPOP
    Android文档在SDK安装目录中的docs目录中。

    七、UI fragment与fragment管理器
    activity托管fragment:activity在其视图层级里提供一处位置,用来放置fragment视图。fragment本身没有在屏幕上显示视图的能力。
    生命周期:(setContentView方法中调用)onAttach,inCreate,onCreateView,(创建)onActivityCreate,(停止)onStart,(暂停)onResume,(运行)onPause,(暂停)onStop,(停止)onDestroyView,(activity关闭)onDestroy,onDetach,销毁。
    activity托管UI fragment有两种方式:在activity布局中添加fragment;在activity代码中添加fragment。一般使用第二种方式。
    onCreateView(){ View v = inflater.inflate(R.layout.fragment_crime,container,false); return v;} //CrimeFragment
    EditText.addTextChangedListener

    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fm.findFragmentById(R.id.fragment_container); //看framelayout里面是否已经被添加过fragment。
    if(fragment == null) {
      fragment = new CrimeFragment();
      fm.beginTransaction().add(R.id.fragment_container,fragment).commit();
    
    在activity处于运行状态时,添加fragment时,FragmentManager立即驱赶fragment,调用从onAttach到onResume方法,追上activity步伐(与activity的最新状态保持同步)后,托管activity的FragmentManager就会边接收操作系统的调用指令,边调用其他生命周期方法,让fragment与activity的状态取得一致。
    
    要合理使用fragment。应用单屏最多使用2~3个fragment。在开发中尽量使用fragment,因为后期添加太麻烦。
    
    要使用支持库版fragment,应用的activity必须继承FragmentActivity。AppCompatActivity是FragmentActivity子类,FragmentActivity又是Activity的子类。
    

    八、使用RecyclerView显示列表
    单例是特殊的Java类,在创建实例时,一个单例类仅允许创建一个实例。应用能在内存里存活多久,单例就能存活多久。
    要创建单例,需创建一个带有私有构造方法及get()方法的类。如果实例已存在,get()方法就直接返回它;如果实例还不存在,get()方法就会调用构造方法创建它。

    使用抽象activity托管fragment,把通用的建立一个fragment的activity设置成超类,写一个createFragment的抽象方法。
    
    使用RecyclerView:ViewHolder只做一件事,容纳View视图。RecyclerView自身不会创建视图,它创建的是ViewHolder,而ViewHolder引用着itemView。
      Adapter是一个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,是沟通的桥梁。它负责创建必要的ViewHolder和绑定ViewHolder至模型层数据。RecyclerView需要显示视图对象时,就会去找它的Adapter。
      首先调用Adapter的getItemCount方法,询问数组列表中包含多少个对象;接着RecyclerView调用Adapter的onCreateViewHolder方法创建ViewHolder及其要显示的视图;最后,Recycler会传入ViewHolder及其位置,调用onBindViewHolder方法,Adapter会找到目标位置的数据并将其绑定到ViewHolder的视图上。所谓绑定,就是使用模型数据 填充视图。
      需要注意的是,相对于onBindViewHoler方法,onCreateViewHolder方法的调用并不频繁。一旦有了够用的ViewHolder,RecyclerView就会停止调用onCreateView方法,随后,它会回收利用旧的ViewHoler以节约时间和内存。
      Recycler类不会新版摆放屏幕上的列表项,实际上,摆放的任务被委托给了LayoutManager。LayoutManager还负责定义屏幕滚动行为。
    mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    列表滚动流畅归功于onBindViewHolder方法,任何时候,都要确保这个方法轻巧、高效。
    
    private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
            private Crime mCrime;
    
            private TextView mTitleTextView;
            private TextView mDateTextView;
    
            public CrimeHolder(LayoutInflater inflater,ViewGroup parent) {
                super(inflater.inflate(R.layout.list_item_crime,parent,false));
                itemView.setOnClickListener(this);
    
                mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
                mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);
            }
    
            public void bind(Crime crime) {
                mCrime = crime;
                mTitleTextView.setText(mCrime.getTitle());
                mDateTextView.setText(mCrime.getDate().toString());
            }
    
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(),mCrime.getTitle() + " clicked!",Toast.LENGTH_SHORT).show();
            }
        }
    
        private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
            private List<Crime> mCrimes;
    
            public CrimeAdapter(List<Crime> crimes) {
                mCrimes = crimes;
            }
    
            @Override
            public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
    
                return new CrimeHolder(layoutInflater,parent);
            }
    
            @Override
            public void onBindViewHolder(CrimeHolder holder, int position) {
                Crime crime = mCrimes.get(position);
                holder.bind(crime);
            }
    
            @Override
            public int getItemCount() {
                return mCrimes.size();
            }
        }
    
    RecyclerView可代替ListView和GridView。
    单例能方便地存储和控制模型对象。但是无法做到持久存储,也不利于单元测试(用依赖注入工具解决)。
    
    RecyclerView ViewType:可在RecyclerView中创建不同类的列表项。
    定义三种item的xml视图,对应的需要定义三种不同的ViewHolder。 然后根据不同的需求,在Adapter里面识别并运用这些ViewHolder,Adapter里面已经定义好了一些方法,只需要重写getItemViewType(int position)方法,给每个固定的position上的item返回一个固定的类型(ViewType)就能方便的表明每个item需要的ViewHolder:
    
    class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        //User是一个自定义类,代表封装的数据类型
        private List<User> mUsers;
    
        //三种不同的ViewType类型,事先用常量定义好
        public static final int VIEW_TYPE_ONE = 1;
        public static final int VIEW_TYPE_TWO = 2;
        public static final int VIEW_TYPE_THREE = 3;
    
        public MyAdapter(List<User> users) {
          mUsers = users;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
          RecyclerView.ViewHolder myViewHolder = null;
        //根据不同的ViewType类型,来返回不同的ViewHolder
          switch (viewType) {
    
            case VIEW_TYPE_ONE:
              myViewHolder = new ViewHolderOne
                  (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_one, parent, false));
            break;
    
            case VIEW_TYPE_TWO:
              myViewHolder = new ViewHolderTwo
                  (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_two, parent, false));
              break;
    
            case VIEW_TYPE_THREE:
              myViewHolder = new ViewHolderThree
                  (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_three, parent, false));
              break;
          }
    
          return myViewHolder;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        //根据不同的ViewType类型,进行不同的数据绑定操作
          switch (holder.getItemViewType()) {
    
            case VIEW_TYPE_ONE:
              ViewHolderOne holderOne = (ViewHolderOne) holder;
              holderOne.mUserAvatar.setImageResource(R.mipmap.ic_launcher);
              holderOne.mUserName.setText(mUsers.get(position).getUserName());
              holderOne.mUserInfo.setText(mUsers.get(position).getInfo());
              break;
    
            case VIEW_TYPE_TWO:
              ViewHolderTwo holderTwo = (ViewHolderTwo) holder;
              holderTwo.mUserAvatar.setImageResource(R.mipmap.user_avatar);
              holderTwo.mUserName.setText(mUsers.get(position).getUserName());
              holderTwo.mUserInfo.setText(mUsers.get(position).getInfo());
              break;
    
            case VIEW_TYPE_THREE:
              ViewHolderThree holderThree = (ViewHolderThree) holder;
              holderThree.mUserAvatar.setImageResource(R.mipmap.ic_launcher);
              holderThree.mUserName.setText(mUsers.get(position).getUserName());
              holderThree.mUserInfo.setText(mUsers.get(position).getInfo());
              break;
          }
        }
    
        @Override
        public int getItemCount() {
          if (null != mUsers) return mUsers.size();
          else return 0;
        }
        //重写方法,给每个position上的item返回一个固定的ViewType类型
        //如果返回的ViewType类型不固定,则会出现各种item布局变化的情况,可能还会触发bug
        @Override
        public int getItemViewType(int position) {
          return mUsers.get(position).getType();
        }
      }
    

    九、使用布局与组件创建用户界面
    ConstraintLayout:添加四个方向上的约束可摆放组件位置,组件大小有三个选择(组件自己决定wrap_content、手动调整、充满约束布局)。
    选中组件,在组件的属性视图容器设置大小,有三中:固定大小|--|、包裹内容>>>、动态适应。先把两个组件全设为wrap_content,然后拖ImageView组件到crime_date下面。在预览界面,拖住ImageView组件顶部的约束柄,将其拖向ConstraintLayout组件顶部,直到约束柄变绿,并弹出Release to Create Top Constraint这样的提示时,再松开鼠标,然后依次设置下部、右部约束。在xml中形式为:
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    对两个TextView设置约束和边距,宽度为动态适应(0dp),高度为wrap_content

    样式(style)是XML资源文件,含有用来描述组件行为和外观属性定义。主题是各种样式的集合,从结构上说,主题本身也是一种样式资源,只不过它的属性指向了其他样式资源。使用主题引用,可将预定义的应用主题样式添加给指定组件,并确定组件在应用中拥有正确一致的显示风格。
    
    边距属性的默认使用值是16dp或8dp。
    

    十、使用fragment argument
    直接获取extra信息的缺点:破坏了fragment的封装,不再是可复用的构建单元。更好的做法是使用fragment argument bundle。
    每个fragment实例都可附带一个Bundle对象。该bundle包含键-值对,可以像附加extra到Activity的intent中那样使用它们。一个键-值对即一个argument。
    创建fragment argument:先创建Bundle对象,然后使用put方法,将argument添加到bundle中。然后调用Fragment.setArguments方法把argument bundle附加给fragment:
    ```
    public static CrimeFragment newInstance(UUID crimeId) { //Fragment
    Bundle args = new Bundle();
    args.putSerializable(ARG_CRIME_ID,crimeId);

        CrimeFragment fragment = new CrimeFragment();
        fragment.setArguments(args);
        return fragment;
    }
    

    protected Fragment createFragment() { //Activity
    UUID crimeId = (UUID) getIntent().getSerializableExtra(EXTRA_CRIME_ID);
    return CrimeFragment.newInstance(crimeId);
    }

      
        列表数据变化: mAdapter.notifyDataSetChanged();
        高效刷新:mAdapter.notifyItemChange(int);
    
    十一、使用ViewPager
        ViewPager在某种程度上类似于RecyclerView,不过,相较于RecyclerView与Adapter间的协同工作,ViewPager与PagerAdapter间的配合要复杂得多,所以,一般使用Google提供的PagerAdapter的子类FragmentStatePagerAdapter:
    

    mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
    @Override
    public Fragment getItem(int position) {
    Crime crime = mCrimes.get(position);
    return CrimeFragment.newInstance(crime.getId());
    }

            @Override
            public int getCount() {
                return mCrimes.size();
            }
        });
    

    for (int i = 0;i < mCrimes.size();i++) {
    if (mCrimes.get(i).getId().equals(crimeId)) {
    mViewPager.setCurrentItem(i);
    break;
    }
    }

        FragmentStatePagerAdapter在卸载不需要的fragment时会销毁它。FragmentPagerAdapter会调用事务的detach而不是remove,只是销毁了fragment的视图,而实例还保存在FragmentManager中。前者更省内存,后者适合用户需要少量固定的fragment的情形。
    
        当需要ViewPager托管非fragment视图时,需要自己实现PagerAdapter接口,
    
        不推荐使用代码方式创建视图,不过如果只需一个视图时,可用代码创建。
    
    
    十二、对话框
        建议将AlertDialog封装在DialogFragment实例中使用,使用FragmentManager管理对话框,可以更灵活地显示对话框,旋转设备后对话框也不会消失:
    

    public class DatePickerFragment extends DialogFragment {
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new AlertDialog.Builder(getActivity())
    .setTitle(R.string.date_picker_title)
    .setPositiveButton(android.R.string.ok,null)
    .create();
    }
    }

    FragmentManager manager = getFragmentManager();
    DatePickerFragment dialog = new DatePickerFragment();
    dialog.show(manager,DIALOG_DATE);

    
        同一个activity托管的fragment间的数据传递:
    

    public static DatePickerFragment newInstance(Date date) {
    Bundle args = new Bundle();
    args.putSerializable(ARG_DATE,date);

        DatePickerFragment fragment = new DatePickerFragment();
        fragment.setArguments(args);
        return fragment;
    }
    

    DatePickerFragment dialog = DatePickerFragment.newInstance(mCrime.getDate());
    dialog.show(manager,DIALOG_DATE);

    dialog.setTargetFragment(CrimeFragment.this,REQUEST_DATE);

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != Activity.RESULT_OK) {
    return;
    }

        if (requestCode == REQUEST_DATE) {
            Date date = (Date) data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
            mCrime.setDate(date);
            mDateButton.setText(mCrime.getDate().toString());
        }
    }
    

    private void sendResult(int resultCode,Date date) {
    if (getTargetFragment() == null) {
    return;
    }

        Intent intent = new Intent();
        intent.putExtra(EXTRA_DATE,date);
    
        getTargetFragment()
                .onActivityResult(getTargetRequestCode(),resultCode,intent);
    }
    

    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
    int year = mDatePicker.getYear();
    int month = mDatePicker.getMonth();
    int day = mDatePicker.getDayOfMonth();
    Date date = new GregorianCalendar(year,month,day).getTime();
    sendResult(Activity.RESULT_OK,date);
    }
    })

        编写同样的代码用于全屏fragment或对话框fragment时,可选择覆盖DialogFragment.onCreateView方法,而非oncreateDialog方法,以实现不同设备上的信息呈现。
    
    
    十三、工具栏
        使用AppCompat库;
          android:theme="@style/AppTheme"
          <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    
        res下new-->android resource file,选择menu类型,命名为fragment_crime_list。
      <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto">
        <item
            android:id="@+id/new_crime"
            android:icon="@android:drawable/ic_menu_add"
            android:title="@string/new_crime"
            app:showAsAction="ifRoom|withText" />
    </menu>
    
        使用Android Studio内置的Android Asset Studio工具为工具栏创建或定制图片:右键单击drawable,选择New-->Image Asset,生成各类图标。
    
        创建菜单:
    

    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.fragment_crime_list,menu);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
    }

    public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.new_crime:
    Crime crime = new Crime();
    CrimeLab.get(getActivity()).addCrime(crime);
    Intent intent = CrimePagerActivity.newIntent(getActivity(),crime.getId());
    startActivity(intent);
    return true;
    default:
    return super.onOptionsItemSelected(item);
    }
    }

        实现层级导航:android:parentActivityName=".CrimeListActivity"
    
        修改标题:
    

    private void updateSubtitle() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    int crimeCount = crimeLab.getCrimes().size();
    String subtitle = getString(R.string.subtitle_format,crimeCount);

        AppCompatActivity activity = (AppCompatActivity) getActivity();
        activity.getSupportActionBar().setSubtitle(subtitle);
    }
    

    case R.id.show_subtitle:
    updateSubtitle();
    return true;

        复数字条串资源:
    

    <plurals name="subtitle_plural">
    <item quantity="one">%1d crime</item> <item quantity="other">%1d crimes</item>
    </plurals>

    int crimeSize = crimeLab.getCrimes().size();
    String subtitle = getResources().getQuantityString(R.plurals.subtitle_plural,crimeSize,crimeSize);

    
        用于RecyclerView的空视图:在布局中添加一个textview,默认不可见,当数据为零时显示可见。
    
    十四、SQLite数据库
        应用上下文在应用的全周期存在,而activity不一定存在,所以在单例中使用应用上下文:mContext = context.getApplicationContext();
    
    十五、使用隐式intent
        检查可响应任务的activity:
    PackageManager packageManager = getActivity().getPackageManager();
            if (packageManager.resolveActivity(pickContact,
                    PackageManager.MATCH_DEFAULT_ONLY) == null) {
                mSuspectButton.setEnabled(false);
            }
    
        过滤器验证代码:pickContact.addCategory(Intent.CATEGORY_HOME);
    
        ShareCompat类有个内部类IntentBuilder,使用这个类创建发送消息的Intent略微方便一些。
    
    
    十六、使用intent拍照
        使用FileProvider:
    

    <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.lewanjiang.criminalintent.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/files/"/>
    </provider>

    <paths>
    <files-path
    name="crime_photo"
    path="."/>
    </paths>

        使用相机intent:
    

    final Intent captureImage = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    boolean canTakePhoto = mPhotoFile != null &&
    captureImage.resolveActivity(packageManager) != null;
    mPhotoButton.setEnabled(canTakePhoto);
    mPhotoButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Uri uri = FileProvider.getUriForFile(getActivity(),
    "com.lewanjiang.criminalintent.fileprovider",mPhotoFile);

                List<ResolveInfo> cameraActivities = getActivity()
                        .getPackageManager().queryIntentActivities(captureImage,
                                PackageManager.MATCH_DEFAULT_ONLY);
    
                for (ResolveInfo activity:cameraActivities) {
                    getActivity().grantUriPermission(activity.activityInfo.packageName,
                            uri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                }
    
                startActivityForResult(captureImage,REQUEST_PHOTO);
            }
        });
    
        缩放的显示位图:
    

    public class PictureUtils {
    public static Bitmap getScaledBitmap(String path,int destWidth,int destHeight) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(path,options);

        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;
        
        int inSampleSize = 1;
        if (srcHeight > destHeight || srcWidth > destWidth) {
            float heightScale = srcHeight / destHeight;
            float widthScale = srcWidth / destWidth;
            
            inSampleSize = Math.round(heightScale > widthScale ? heightScale : widthScale);
        }
        
        options = new BitmapFactory.Options();
        options.inSampleSize = inSampleSize;
        
        return BitmapFactory.decodeFile(path,options);
    }
    
    public static Bitmap getScaledBitmap(String path, Activity activity) {
        Point size = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize();
        
        return getScaledBitmap(path,size.x,size.y);
    }
    

    }

    else if (requestCode == REQUEST_PHOTO) {
    Uri uri = FileProvider.getUriForFile(getActivity(),
    "com.lewanjiang.criminalintent.fileprovider",
    mPhotoFile);
    getActivity().revokeUriPermission(uri,
    Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    updatePhotoView();
    }

    private void updatePhotoView() {
    if (mPhotoFile == null || !mPhotoFile.exists()) {
    mPhotoView.setImageDrawable(null);
    } else {
    Bitmap bitmap = PictureUtils.getScaledBitmap(mPhotoFile.getPath(),getActivity());
    mPhotoView.setImageBitmap(bitmap);
    }
    }

        优化缩略图加载:ViewTreeObserver
    
    
    十七、双版面主从用户界面
    @LayoutRes
        protected int getLayoutResId() {
            return R.layout.activity_fragment;
        }
    setContentView(getLayoutResId());
    
      别名资源是一种指向其他资源的特殊资源,定义在refs.xml文件中。
    
    
    十八、应用本地化
    
    
    十九、Android辅助功能
      TalkBack
    
    
    二十、数据绑定与MVVM
      为什么需要用MVVM架构:当应用越来越复杂,fragment和activity开始膨胀,逐渐变得难以理解和扩展。这个时候,控制器层就需要做功能拆分了。
      怎么拆?先搞清楚控制器类到底做了哪些工作,再把这些工作拆分到独立的小类里。让一个个拆开的小类协同工作。
      如何确定控制器类的不同使用呢?你的架构可以给你答案,使用MVC/MVP时它们就是这个答案。
      每个视图模型应控制成多在规模,这要具体情况具体分析。如果 视图模型过大,你还可以继续拆分。总之,你的架构你把控。
    
      开启数据绑定:
        dataBinding {
            enabled = true
        }
      然后把一般布局改造为数据绑定布局:把布局定义放入<layout>标签。
      实例化绑定类:
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            FragmentBeatBoxBinding binding = DataBindingUtil
                    .inflate(inflater,R.layout.fragment_beat_box,container,false);
            binding.recyclerView.setLayoutManager(new GridLayoutManager(getActivity(),3));
            return binding.getRoot();
        }
      创建SoundHolder:
    private class SoundHolder extends RecyclerView.ViewHolder {
            private ListItemSoundBinding mBinding;
            
            private SoundHolder(ListItemSoundBinding binding) {
                super(binding.getRoot());
                mBinding = binding;
            }
        }
      创建SoundAdapter:
    private class SoundAdapter extends RecyclerView.Adapter<SoundHolder> {
    
            @Override
            public SoundHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                LayoutInflater inflater = LayoutInflater.from(getActivity());
                ListItemSoundBinding binding = DataBindingUtil
                        .inflate(inflater,R.layout.list_item_sound,parent,false);
                return new SoundHolder(binding);
            }
    
            @Override
            public void onBindViewHolder(SoundHolder holder, int position) {
    
            }
    
            @Override
            public int getItemCount() {
                return 0;
            }
        }
    
      使用assets有两面性:一方面,无需配置管理,可以随意命名,并按自己的文件结构组织它们;另一方面,没有配置管理,无法自动响应屏幕显示密度、语言这样的设置配置变更,自然也就无法在布局或其他资源里自动使用它们了。
      为何使用assets:如果使用resources系统要一个个去处理,效率非常低,因为这些文件不能全放在一个目录下管理,所以使用assets。assets可以看作是一个微型文件系统,支持任意层次的文件目录结构,常用来加载大师图片和违章资源。
    
    
    二一、音频播放与单元测试
      MMVM架构极大方便了一项关键编程工作:单元测试。
    
      利用SoundPool实现音频播放:
      先添加两个测试依赖:Mockito,Hamcrest。把compile改为testCompile。
      在类名上创建一个新测试类(光标移到类名上,按Command+Shift+T,选择Create New Test...),测试库选JUnit4,勾选setUp/@before,其他保持默认设置。选择test目录进行单元测试
      实现测试类,使用虚拟依赖项:
    public class SoundViewModelTest {
        private BeatBox mBeatBox;
        private Sound mSound;
        private SoundViewModel mSubject;
    
        @Before
        public void setUp() throws Exception {
            mBeatBox = mock(BeatBox.class);
            mSound = new Sound("assetPath");
            mSubject = new SoundViewModel(mBeatBox);
            mSubject.setSound(mSound);
        }
    }
      编写测试方法:
    @Test
        public void exposesSoundNameAsTitle() {
            assertThat(mSubject.getTitle(), is(mSound.getName()));
        }
    
      设备旋转和对象保存:实现Serializable或者Parcelable接口。
      保留fragment:setRetainInstance(true);。原理:当设备配置改变时,fragment视图被销毁,但fragment本身不会被销毁。然而,这个功能不推荐使用。第一是用起来更复杂,第二是因系统回收内存而被销毁时,就会数据丢失。
    
      Esspresso与整合测试,Espresso是Google开发的一个UI测试框架。
    
    
    二十二、样式与主题
      在resources是定义颜色资源,一处定义,整个应用中引用。colors.xml
    
      样式是能够应用于视图组件的一套属性。styles.xml
    <style name="BeatBoxButton">
            <item name="android:background">@color/dark_blue</item>
        </style>
    style="@style/BeatBoxButton"   //xml中Button属性中
      样式支持继承:
    <style name="BeatBoxButton.Strong">
            <item name="android:textStyle">bold</item>
        </style>
      也可以采用指定父样式的方式。
    
      主题可以看作是样式的进货加强版,会自动应用于整个应用,主题能引用外部和资源和其他样式:
    <style name="AppTheme" parent="Theme.AppCompat.Light">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/red</item>
            <item name="colorPrimaryDark">@color/dark_red</item>
            <item name="colorAccent">@color/gray</item>
        </style>
      覆盖主题属性:从现有主题往上找父主题,直到找到相关属性为止
      修改按钮属性:逐级向上查找父主题,找到buttonSytle样式,可以在Widget.AppCompat.Button中看到button的各个属性。修改BeatBosButton的父样式为Widget.AppCompat.Button。
    <style name="BeatBoxButton" parent="Widget.AppCompat.Button">
      在主题中覆盖buttonStyle属性:
    <item name="buttonStyle">@style/BeatBoxButton</item>
    
      如果是继承自己内部的主题,使用主题名指定父主题即可;如果继承android操作系统中的样式或主题,记得使用parent属性
      在主题中引用资源时,使用?符号。
    
    
    二十三、XML drawable
      在android中,凡是要在屏幕上绘制的东西都可以叫drawable,比如抽象图形、Drawable类的子类代码、位图图像等。
    
      shape drawable可以把按钮变成各种形状:
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
        <solid android:color="@color/dark_blue" />
    </shape>
      在styles中把上面的drawable作为按钮背景:
            <item name="android:background">@drawable/button_beat_box_normal</item>
    
      state list drawable:状态改变时变化。
      先定义一个用于按钮按下状态的shape drawable:
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
        <solid android:color="@color/red" />
    </shape>
      然后定义一个state list drawable:
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/button_beat_box_pressed"
              android:state_pressed="true"/>
        <item android:drawable="@drawable/button_beat_box_normal" />
    </selector>
      最后将上面的state list drawable作为按钮背景:
     <item name="android:background">@drawable/button_beat_box</item>
      除了按下状态,state list drawable还支持禁用、聚集以及激活等状态。
    
      layer list drawable能让两个drawable合二为一。比如为按下状态的按钮添加一个深色的圆环:
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <shape xmlns:android="http://schemas.android.com/apk/res/android"
                   android:shape="oval">
                <solid android:color="@color/red" />
            </shape>
        </item>
        <item>
            <shape android:shape="oval">
                <stroke
                    android:width="4dp"
                    android:color="@color/dark_red"/>
            </shape>
        </item>
    </layer-list>
    
      drawable用起来灵活方便,不仅用法多样,还易于更新维护,能做出复杂的背景图,连图像编辑器都省了,更改应用的配色也很简单。
      另外,drawable独立于屏幕像素密度,不需要准备多个版本来适合不同屏幕。
    
      把应用启动器图标放在mipmap目录中,其他图片都放在drawable目录中。
    
    
    二十四、深入学习intent和任务
      Intent另一个构造方法:setClassName(String packageName,String className)
    
      任务是一个activity栈,默认情况下,新activity都在当前任务中启动。
      启动新activity时启动新任务:addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
      进程是操作系统创建的、供应用对象生存以及应用运行的地方。通常会占用操作系统管理着的系统资源,在Android中,每个进程都需要一个虚拟机来运行。
      每一个activity实例都存在于一个进程之中,同一个任务关联。这也是进程与任务的唯一相似之处。任务只包含activity,这些activity通常来自于不同的应用进程;而进程则包含了应用的全部运行代码和对象
      并发文档(concurrent document),可以为运行的应用动态创建任意数目的任务:android:documentLaunchMode="intoExisting" 
    
    
    二十五、HTTP与后台任务
      线程是个单一招行序列。单个线程中的代码会逐步招行。所有的Android应用的运行都是从主线程开始的。然后,主线程不是线程那样的预订招行序列,相反,它处于一个无限循环的运行状态,等着用户或系统触发事件。一旦有事件触发,主线程便招行代码做出响应。
    
      清理AsyncTask:AsyncTask.cancel(true)。如果fragment/activity已销毁了,不撤销AsyncTask,可能会引发内存泄漏。
      第一个参数是输入参数类型,第二个参数是指定发送进度更新需要的类型,第三个参数是输出类型。
    
      相比AsyncTask,推荐使用loader。遇到配置变化 时会自动管理loader和加载的数据 ,而且LoaderManager负责启动和停止,以及管理生命周期。
    
      分页:RecyclerView.OnScrollListener
    
      动态调整网格:ViewTreeObserver.OnGlobalLayoutListener
    
    
    二十六、Looper、Handler和HandlerThread
      RecyclerView设置GridLayoutManager时,意味着宽度会变,而高度固定(宽高分别为match_parent和120dp),为最大化利用ImageView的空间,设置scaleType的属性值为centerCrop。
    
      在应用图片展示页面,很多应用通常会选择仅在需要显示 图片时都去下载。显然,RecyclerView及其adapter应负责实现按需下载。adapter触发图片下载就放在onBindViewHolder方法中实现。Async是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务。放弃AsyncTask,创建一个专用的后台线程。这是实现按需下载的最常用方式。
      创建并启动后台线程:
    public class ThumbnailDownloader<T> extends HandlerThread {
        private static final String TAG = "ThumbnailDownloader";
        
        
        private Boolean mHasQuit = false;
        
        public ThumbnailDownloader() {
            super(TAG);
        }
    
        @Override
        public boolean quit() {
            mHasQuit = true;
            return super.quit();
        }
        
        public void queueThunbnail(T target,String url) {
            Log.i(TAG, "queueThunbnail: URL " + url);
        }
    }
    
      图片下载可用Picasso、Glide、Fresco,Picasso小而类,Glide小巧,Fresco性能好。
    
      StrictMode.enableDefaults方法能发现以下问题:在主线程上发起网络请求、在主线程上做了磁盘读写、Activit未及时销毁(泄露)、SQLite数据库游标未关闭、网络通信使用明文(未使用SSL/TLS加密)。
    
      预加载以及缓存:LruCache。
    
    
    二十七、搜索
      SearchView是个可以嵌入工具栏的操作视图类(action view),用户可以输入查询关键字,提交查询请求搜索,返回结果将显示在RecyclerView中。
      创建和响应Menu菜单:
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <item android:id="@+id/menu_item_search"
              android:title="@string/search"
              app:actionViewClass="android.support.v7.widget.SearchView"
              app:showAsAction="ifRoom" />
    
        <item android:id="@+id/menu_item_clear"
              android:title="@string/clear_search"
              app:showAsAction="never" />
    </menu>
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            super.onCreateOptionsMenu(menu, inflater);
            inflater.inflate(R.menu.fragment_photo_gallery,menu);
    
            MenuItem searchItem = menu.findItem(R.id.menu_item_search);
            final SearchView searchView = (SearchView) searchItem.getActionView();
            
            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                    Log.d(TAG, "onQueryTextSubmit: " + query);
                    updateItems();
                    return true;
                }
    
                @Override
                public boolean onQueryTextChange(String newText) {
                    Log.d(TAG, "onQueryTextChange: " + newText);
                    return false;
                }
            });
        }
      
      使用SharedPreferences存储查询记录
    
      默认显示已保存查询信息:
    searchView.setOnSearchClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String query = QueryPreferences.getStoredQuery(getActivity());
                    searchView.setQuery(query,false);
                }
            });
    
    
    二十八、后台服务
      创建IntentService
    
      后台网络连接安全:
    private boolean isNetworkAvailableAndConnected() {
            ConnectivityManager cm = 
                    (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
            
            boolean isNetworkAvailable = cm.getActiveNetwork() != null;
            boolean isNetworkConnected = isNetworkAvailable &&
                    cm.getActiveNetworkInfo().isConnected();
            
            return isNetworkConnected;
        }
    
      使用AlarmManager延迟运行服务:
    public static void setServiceAlarm(Context context,boolean isOn) {
            Intent i = PollService.newIntent(context);
            PendingIntent pi = PendingIntent.getService(context,0,i,0);
    
            AlarmManager alarmManager = (AlarmManager)
                    context.getSystemService(Context.ALARM_SERVICE);
    
            if (isOn) {
                alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime(),POLL_INTERVAL_MS,pi);
            } else {
                alarmManager.cancel(pi);
                pi.cancel();
            }
        }
      setRepeting方法是非精准重复计时器。
      PendingIntent是一种token对象,将它交给其他应用使用时,它是代表当前应用发送token对象的。另外,它本身存在于操作系统而不是token里,因此实际上是你在控制着它。
      可以使用PendingIntent管理定时器,查看定时器激活与否:
    public static boolean isServiceAlarmOn(Context context) {
            Intent i = PollService.newIntent(context);
            PendingIntent pi = PendingIntent.getService(context,0,i,PendingIntent.FLAG_NO_CREATE);
            return pi != null;
        }
    
      控制定时器:
    boolean shouldStartAlarm = !PollService.isServiceAlarmOn(getActivity());
                    PollService.setServiceAlarm(getActivity(),shouldStartAlarm);
     
      通知信息:Notification notification = new NotificationCompat.Builder(this)
                        .setTicker(resources.getString(R.string.new_pictures_title))
                        .setSmallIcon(android.R.drawable.ic_menu_report_image)
                        .setContentTitle(resources.getString(R.string.new_pictures_title))
                        .setContentText(resources.getString(R.string.new_pictures_text))
                        .setContentIntent(pi)
                        .setAutoCancel(true)
                        .build();
    
                NotificationManagerCompat notificationManager =
                        NotificationManagerCompat.from(this);
                notificationManager.notify(0,notification);
    
      对于大多数服务任务,推荐使用IntentService。原生服务不能在后台线程上运行。这也是推荐使用IntentService的最主要原因。大多数重要的服务都需要在后台线程上运行,而IntentService已提供了一套标准支持方案。
      服务生命周期:onCreate,onStartCommand,onDestroy。
      IntentService是一种non-sticky服务。
      sticky服务适用于长时间运行的服务,如音乐播放器,即使这样,也就考虑一种使用non-sticky服务的替代架构方案。sticky服务的管理很不方便,因为很难判断服务是否已启动。
    
      在API 21  中,为更好地实现后台服务,引入了JobScheduler的全新API,除了能用来处理种类任务外,还能:发现没有网络时,禁止服务启动;如果请求失败或网络连接受限,提供稍后重试机制;控制只在设备充电的时候,才允许检查是否有新图片。还支持按场景、按条件运行后台服务。
    
      还可以使用sync adapter创建常规的polling网络服务。
    
    
    二十九、broadcast intent
      使用私有权限:在Manifest文件中定义,安全级别有四种
    
      如何用broadcast触发长时运行任务:将任务交给服务去处理,然后通过broadcast瞬时 启动服务。第二种是用goAsync,但不够灵活,不推荐使用。
    
      事件总线:EventBus、RxJava
    
      本地广播不支持有序,也不支持在独立线程上发送和接收广播。
    
    
    三十、网页浏览
      WebChromeClient是一个事件接口,用来响应那些改变浏览器中装饰元素的事件。
    
      自己处理配置更改,资源修改符无法自动工作。
    
      非HTTP链接支持:加载URI前,先检查它的scheme,如果不是HTTP或HTTPS,就发送一个针对目标URI的Intent.ACTION_VIEW。
    
    
    三十一、定制视图与触摸事件
      创建定制视图三大步骤:选择超类;覆盖超类的构造方法;覆盖其他关键方法。
    

    public class BoxDrawingView extends View {
    public BoxDrawingView(Context context) {
    this(context,null);
    }

    public BoxDrawingView(Context context, AttributeSet attrs) {
        super(context,attrs);
    }
    

    }

      监听触摸事件的一种方式是使用setOnTouchListener方法,不过,由于定制视图是View的子类,可走捷径覆盖以下方法:onTouchEvent:
    @Override
        public boolean onTouchEvent(MotionEvent event) {
            PointF current = new PointF(event.getX(),event.getY());
            String actioin = "";
            
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    actioin = "ACTION_DOWN";
                    break;
                case MotionEvent.ACTION_MOVE:
                    actioin = "ACTION_MOVE";
                    break;
                case MotionEvent.ACTION_UP:
                    actioin = "ACTION_UP";
                    break;
                case MotionEvent.ACTION_CANCEL:
                    actioin = "ACTION_CANCEL";
                    break;
            }
            Log.i(TAG, "onTouchEvent: x " + current.x + "y " + current.y);
            return true;
        }
      跟踪运行事件:
    public boolean onTouchEvent(MotionEvent event) {
            PointF current = new PointF(event.getX(),event.getY());
            String actioin = "";
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    actioin = "ACTION_DOWN";
                    mCurrentBox = new Box(current);
                    mBoxen.add(mCurrentBox);
                    break;
                case MotionEvent.ACTION_MOVE:
                    actioin = "ACTION_MOVE";
                    if (mCurrentBox != null) {
                        mCurrentBox.setCurrent(current);
                        invalidate();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    actioin = "ACTION_UP";
                    mCurrentBox = null;
                    break;
                case MotionEvent.ACTION_CANCEL:
                    actioin = "ACTION_CANCEL";
                    mCurrentBox = null;
                    break;
            }
            Log.i(TAG, "onTouchEvent: x " + current.x + "y " + current.y);
            return true;
        }
     onDraw方法内的图形绘制
    private Paint mBoxPaint;
        private Paint mBackgroundPaint;
    
        public BoxDrawingView(Context context) {
            this(context,null);
        }
    
        public BoxDrawingView(Context context, AttributeSet attrs) {
            super(context,attrs);
    
            mBoxPaint = new Paint();
            mBoxPaint.setColor(0x22ff0000);
    
            mBackgroundPaint = new Paint();
            mBackgroundPaint.setColor(0xfff8efe0);
        }
    protected void onDraw(Canvas canvas) {
            canvas.drawPaint(mBackgroundPaint);
    
            for (Box box:mBoxen) {
                float left = Math.min(box.getOrigin().x,box.getCurrent().x);
                float right = Math.max(box.getOrigin().x,box.getCurrent().x);
                float top = Math.min(box.getOrigin().y,box.getCurrent().y);
                float botton = Math.max(box.getOrigin().y,box.getCurrent().y);
    
                canvas.drawRect(left,top,right,botton,mBoxPaint);
            }
        }
    
      设备旋转问题:protected Parcelable onsavedInstanceState(),protected void onRestoreInstanceState(Pracelable state)
    
    
    三十二、属性动画
      ObjectAnimator heightAnimator = ObjectAnimator.ofFloat(mSunView,"y",sunYStart,sunYEnd).setDuration(3200);
    heightAnimator.setInterpolator(new AccelerateInterpolator());
    heightAnimator.start();
    
    
    三十三、地理位置和Play服务
    
    
    三十四、使用地图
    
    
    三十五、material design
      material surface:elevation和Z值、state list animator
     
       动画工具:circular reveal、shared element transition
    
      新的视图组件:card、floating action button、snackbar
    
    
    三十六、编后语

    相关文章

      网友评论

          本文标题:11.《android编程权威指南》笔记一

          本文链接:https://www.haomeiwen.com/subject/fmwcuxtx.html